Security Fest CTF 2016-tvstation

1.题目给了libc库,需要查看一下版本,直接拖到Linux中运行一下./libc.so.6_x64,就可以知道是libc2.24的,但Linux中的libc没有该版本,所以用pwndocker来连接运行。具体怎么用看下方链接,同样docker也自行学习。

https://github.com/skysider/pwndocker

如果需要使用到这个libc调试,则在python中设置下列代码:

1
2
3
#注释头

io = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './tvstation'], env={"LD_PRELOAD":"./libc.so.6_x64"})

2.然后开始分析文件,常规checksec,开了NX,IDA打开文件找漏洞,发现输入4进入debug函数后可以泄露system的内存地址:

1
2
3
4
5
6
#注释头

v0 = dlsym((void *)0xFFFFFFFFFFFFFFFFLL, "system");
sprintf(fmsg, info, v0);
v1 = strlen(fmsg);
write(1, fmsg, v1);

dlsym()的函数原型是

void* dlsym(void* handle,const char* symbol);

该函数在<dlfcn.h>文件中,handle是由dlopen打开动态链接库后返回的指针,symbol就是要求获取的函数的名称,函数返回值是void*,指向函数的地址,供调用使用。write函数的fd是1,所以就相当于直接打印在屏幕上,这里涉及linux系统调用号内容,可以直接查linux下的目录/usr/include/asm/中的unistd_32.h和unistd_64.h。

这段代码的意思就是把指向system函数的指针返回给v0,然后将v0格式化输出给fmsg,之后将fmsg原封不动打印在屏幕上,第一次看到猜不出来可以直接运行该文件试试呗。之后会进入一个debug_func(),这里存在栈溢出:

1
2
3
4
#注释头

__int64 buf; // [rsp+0h] [rbp-20h]
return read(0, &buf, 0xC8uLL);

3.现在有了system的内存地址和栈溢出,就差/bin/sh字符串了。这里用IDA打开题目给的libc文件,可以找到bin/sh字符串的地址binsh_libc_addr和system的地址system_libc_addr。所以这就相当于有system的被libc加载的真实地址,那么system的真实system_addr减去system_libc_addr就可以得到Libc被加载进来的首地址libc_start_addr。即现掌握地址:libc_start_addr,system_addr,system_libc_addr,binsh_libc_addr通过计算可得:binsh_addr = system_addr - system_libc_addr + binsh_libc_addr。这不是栈溢出有了,system函数和binsh字符串的真实地址有了,这不直接getshell就完事了吗,闹啥呢,这破题目,没点技术含量。

4.但程序还是得走走,64位程序,所以需要使用ROPgadget表来查找pop rdi ; ret这个代码所在的地址,也是在Libc中查找到,然后加上libc_start_addr就可得到pop_rdi_addr。

5.之后计算偏移量,远程调试下进行,payload依次为padding + pop_rdi_addr + binsh_addr + system_addr。

6.再考虑输入情况:先在Linux下运行,所以能看到需要在接收到”: ”时可以输入4,然后进入到打印system_addr,打印完之后,需要从打印出来的system地址读取进我们设定的system_addr。

7.由于打印格式是@x0x7ffffff,所以在recvuntil”@x”,之后开始获取打印的system_addr:system_addr = int(io.recv(12), 16),以十六进制总共读取12位

8.读取完成system_addr后就可以开始输入payload,之后就可以interactive()。

参考资料:

https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157