call_it

main函数:

program函数:


gift函数:

execute函数:

这题没有后门函数,有canary
我们开始看代码,先看main函数,setbuf(stdout, 0)是是关闭 stdout(标准输出)的缓冲区,与解题关系不大,然后进入program函数,n_gestures是一个全局变量,未初始化,会被自动存放在.bss段(已初始化的会被放在.data段),程序被操作系统加载到内存中准备运行时,所有 .bss 段中的变量都会被自动初始化为 0,scanf读取格式错误返回0,读取失败返回-1,此时程序会停止2,goto LABEL_11说明我们选择1到4会到LABEL_11,选择1到5都会使n_gestures,而选择其他的会跳出switch,我们看看LABEL_11,getchar()会把scanf没有读取的\n清除,保证下面fgets正常工作,fgets(&talks[16 v2], 16, stdin)的意思是读取前15字节到talks[16 v2],因为talks也是未初始化的全局变量,所以也在.bss段,而这里存在越界漏洞,fgets 成功读取到了字符串,它会返回一个指向“目的地”的指针,即为真,相反则返回NULL,即为假,然后retun,在switch语句外有一个++v2,也就是说我们每次选择一个大于零的数,都会使v2加1,当我们输入0时program退出
我们暂时还没发现明显的漏洞,execute((__int64)gestures);之后main函数调用这个,我们看execute函数,这里用n_gesture的值当作循环次数,重点在(*(void (**)(void))(8 i + a1))();这个的意思是执行8i+gestures(这里参数a1就是gestures,gestures被放在rdi中)存放的内容,(void (**)(void))这个是声明类型,*是取出存放的内容,()是执行,这里也就是说我们可以连续执行gestures的内容,而gestrues也是未初始化的全局变量,那和talks都在.bss上,而前面我们可以看到我们可以通过控制v2的值来越界向.bss段的变量写入内容,我们来看他们两个相差多少

也就是输入8个6使v2等于8,这样我们写入就是gestures[0]和[1]的位置了,虽然fgets只读取15个字节,但是它会自动在结尾加上\x00,不会影响我们写入两个地址
但是现在我们还不够解题,我们构造不了rop链,因为我们写入的都是.bss段而不是栈,这个时候我们就要考虑是否要用jop,这是jop和rop的对比

那我们就要找一个gadget,但是这里gift函数直接就实现了,我们看gift函数的汇编代码

注意我们execute调用后的rdi是&gestrues[0],如果我们调用gift函数的话,rdi变为&gestures[1],r然后调用gestures[2]的函数,非常完美的gadget,那我们的思路就很明显了,这里有个细节,mov rdi, [rax+8]有中括号(解引用)说明gesture[1]里面要放个指针,所以这里我们不能直接放/bin/sh,要把/bin/sh放在gestrue[3],然后gestrue[1]中放gesture[3]的地址
那我们的思路就清晰了,首先输入8个6仅使v2到达8,而n_gesture为0,这时我们随便选择一个(1-5)到LABEL_11,向gestures[0]和[1]分别写入gift地址和gesture[3]地址(这里只输入15字节),v2到达9,n_gesture为1,然后再重复一次,向gesture[2]和[3](因为v2加了1)写入system地址和/bin/sh,v2变成10,n_gesture为2,然后退出program函数,执行execute函数,循环开始,i=0时调用gift(gesture[0]),然后gift把/bin/sh放入rdi,执行system,然后我们就getshell。
虽然我们execute也可以执行gestures数组,但是我们无法改变rdi,所以我们就要找到gadget,这里gift是直接给我们的完美gadget。
EXP:
from pwn import *
context(arch='amd64', os='linux')
io = process('./pwn')
for _ in range(8):
io.sendlineafter(b'gesture: ', b'6')
io.sendlineafter(b'gesture: ', b'1')
io.sendafter(b'gesture? ', p64(0x401235) + p64(0x4040f8)[0:7])
io.sendlineafter(b'gesture: ', b'1')
io.sendlineafter(b'gesture? ', p64(0x401228) + b'/bin/sh')
io.sendlineafter(b'gesture: ', b'0')
io.interactive()
题目链接:
CTF-Writeups/MoeCTF/ call_it at main · ZenDuk17/CTF-Writeups