一次与CTF的邂逅 Nov, 2015
机缘巧合做了两道CTF二进制题目,谨以此为记。
第一题 recho
主要参考这篇博客,ruby实现,也有人推荐了这篇python实现版,使用了pwntools。
第一题中handle()函数buf大小256Byte,但是recv_line()函数接受用户输入没有限制长度,存在BOF漏洞可以利用。
为了方便本地调试,将源代码中关于drop_priv()相关函数去除后,本地编译。
为了能够在64位linux上编译和执行32位文件,需要安装32位环境。以Ubuntu14.04为例,执行
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 gcc-multilib
即可。
编译时使用命令
gcc -fno-stack-protector -g -o recholocal -m32 recholocal.c
注意使用-m32参数编译成32位,-fno-stack-protector关闭stack canary检测,-g方便gdb调试。
运行./recholocal后通过命令
ps -aux | grep recholocal
查看其进程号,通过
gdb atach pid
来调试该进程。
set follow-fork-mode child
可以使gdb在程序fork()后跟随子进程。
在程序中找到recvline()和sendlen(),使用
objdump -d recholocal | grep recvline
获取地址,使用他们来对内存进行写和读。
注意recv_line最后以\n结束。
还有程序最开始调用的sendstr()函数会将是将payload的strlen()长度发送,如果payload中有0x00就会被截断发送。
使用objdump -x recholocal可以查看各个section的位置和布局,找到一个可读可写又足够大的section来存放我们的字符串参数,如.bss或.dynamic等。
发现.dynamic的位置是0x0804a10c。
为了对付ALSR,需要先知道libc中某个函数的运行时地址,使用sendlen()将其发送过来,再加上system()相对这个函数的偏移,写入某个函数got表项,在调用该函数就是相当于调用了system()。
使用
objdump -R recholocal | grep __libc_start_main
发现__libc_start_main()的got表项地址为0x0804a040。
ldd recholocal
可发现本地链接库libc.so.6的位置,对其objdump后找到__libc_start_main()和__libc_system()的地址,计算其偏移。
使用objdump -d recholocal | egrep 'pop|ret'发现ppppr如下
8048d1c: 5b pop %ebx
8048d1d: 5e pop %esi
8048d1e: 5f pop %edi
8048d1f: 5d pop %ebp
8048d20: c3 ret
在堆砌堆栈时需要使用,使用时截取需要部分即可。
本地跑通后将地址改为服务器端地址即可。
刚开始本来想利用这篇博客中的方法获取reverse shell,后来发现由于recho程序中将标准输入输出都复制到了socket中,所以只需cat ~/flag然后再read()出来即可。
第二题 weapon_shop
这一题只有二进制文件,先使用IDA Pro反编译,按F5可以看到部分C伪码,结合程序对函数进行理解。
可以看到该程序输入时都限制了长度,因此不方便BOF。
但是找到在输入Credit Card Number时长度限制为200Byte,而且写入了可执行的.bss段,因此可以在这里写入一些shellcode,地址为0x0804b1e0。
同时注意到在买武器过程中输入数字,使用了strtol()函数,它会扫描字符串,跳过前面的空格,将后面的字符转换成数字。而函数只检查了第一个字符不是负号,以及不大于8,因此可以输入空格加一个任意负数。
后面它使用数组起始地址加这个数得到的地址对其自增,因此输入一个合理的偏移量就可以对任意高于该数组起始地址的地址进行自增。
该数组位于主循环函数的栈上,因此可以对主循环函数的返回地址改写位shellcode所在的地址。
注意该自增只增加一个字节,因此需要对返回地址每个字节分别自增。
shellcode最后选取了拿到/bin/sh的shellcode。
因此最后使用了pwntools里的interactive()函数和远端shell进行交互。