N1CTF-oflo WriteUp

如题.

使用IDA打开题目文件后, 发现没有识别出main函数, 可以初步判断是由于花指令造成的.

在左侧的函数列表里发现了mprotect函数, 这个函数是用来更改内存读写权限的, 隐约感觉有smc(Self-Modifying Code)的存在.

先从start函数定位到main函数所在的区段, 发现了明显的花指令特征:

image-20201026135433040

试图用GDB调试到此位置:

image-20201026135656530

于是在IDA里将光标定位至0x400BB1的位置, 按下U键取消定义, 然后在0x400BB2的位置按下C键Make Code, 然后再用IDAPython0x400BB1变成nop

1
PatchByte(0x400BB1, 0x90)

image-20201026140032321

继续往下分析, 0x400BBC处还有几个神必数据莫得头绪. 不过从BB7跳转到了BBF, 所以继续往下看, 然后… 它retn了.

继续用gdb断点到0x400BB7, 然后接着调试:

image-20201026140355769

可以看到程序控制流从0x400BD0跳到了0x400BBD, 接着跳转到了0x400BD1. 由此可以推测出0x400BBC处也是一个花指令. 先在0x400BBD的位置变成汇编, 再用相同的办法把BBCnop掉:

image-20201026142128162

由此可以看出来, 从0x400BB70x400BD0的一大段代码实际上全是无效代码. 用IDAPython写个循环全部变成nop就完事了:

image-20201026142313218

0x400CBA处还有一个长得一模一样的东西, 同样处理之, 0x400D04还有一坨, nop掉即可.

全部搞定之后把光标移到最上面的main处, 戳一下F5, 代码就反编译成功了:

image-20201026142819634

20行弄了个函数指针, 21行用mprotect函数将某个text段的读写执行权限改成了7, 也就是b111, 即可读可写可执行. 22~23行通过函数指针, 将该函数的前十个字节与v4的前五位异或, 然后第24行用刚刚修改过的函数对输入进行验证. 对部分变量重命名一下, 好康点.

image-20201026143407379

可以发现是用了flag的前五位. 这个比赛的所有flag格式均为n1ctf{xxx}, 那么前五位必然是n1ctf.

定位到evaluate函数所在的位置, 用IDAPython把正确代码处理回来, 否则没法F5.

image-20201026144002533

好极了, 代码恢复成功, F5试一下…失败了, 往下面一看, 还是有花指令.

想试着调试一下, 结果发现了

image-20201026143407379

17行那个函数, 里面调用了ptrace对程序的运行状态进行了监视. 而gdb也是通过ptrace进行调试的, 同一时间只能有一个调试器挂在程序上, 所以起到了反调试的作用. 不过往下看, read函数是在反调函数下面的, 所以可以先运行程序, 跑起来之后等待输入时用gdbattach上去.

接下来定位到0x400ABC, 然后跟踪一下函数流程, 并把花指令都给处理掉. 有如下几个地方:

1
2
3
4
for i in range(0x400AC4, 0x400AC9):
PatchByte(i, 0x90)
for i in range(0x400B0E, 0x400B28):
PatchByte(i, 0x90)

搞完了之后再F5一下, evaluate函数的真容就浮现出来了. 因为smc和花指令的影响, 之前main函数中给evaluate传的参数乱七八糟, 重新F5一下main函数, 找到这几行:

image-20201026145602058

然后再看重命名之后的evaluate函数:

image-20201026150058722

你可以右键参数, 然后选择set lvar type, 改成指针之后就能显示出来数组形式的表达式了.

39行处理的地址是&v20+1-32, 看一下上面的变量声明, 这个地方就是v5, 那么这个循环就是遍历了v5~v18.

还有个难题就是space里面存的到底是个什么东西了.

答案其实很简单, 我们点进那个反调试函数里面瞅一眼, 发现他运行了一下/usr/bin/cat /proc/version, 动态调试到evaluate里面, 输出一下栈里面的东西,

image-20201026152439543

发现space存的实际上就是/usr/bin/cat /proc/version指令的输出, 即你的linux版本信息, 然后取前13位. 因为是linux程序, 所以不管在什么系统上, 前13位必然是Linux Version, 那就保证唯一性了.

接下来就是快乐的拿flag时间了.

image-20201026152313624

脚本:

1
2
3
4
5
6
7
8
#!/bin/python3
# from __future__ import print_function
magic = [53, 45, 17, 26, 73, 125, 17, 20, 43, 59, 62, 61, 60, 95]
space = 'Linux Version '
head = 'n1ctf'
print (head,end='')
for i in range(len(magic)):
print(chr(magic[i] ^ (ord(space[i]) + 2)), end='')

flag: n1ctf{Fam3_Is_NULL}

评论

:D 一言句子获取中...