*CTF
RE
Simple File System
给了一个file文件,一个png文件,一个elf文件
我们对elf文件进行分析
1 | __int64 __fastcall sub_5615516A1488(int a1, const char **a2) |
其实就是跟一个终端一样,我们发现还有一个记事本,记录了一些操作:
1 | # instructions |
那么我们很自然的怀疑到plantflag里有东西,我们查看一下,定位到关键函数
1 | // bad sp value at call has been detected, the output may be wrong! |
一个函数一个函数看,发现a3 = 1时,里面的函数可逆,我们查看一下:
1 | __int64 __fastcall sub_5615516A21B2(__int64 a1, int a2) |
现在有几个问题是,没找到原来的数据或者对比的数据,(这个数据哦经过我的调试应该是从图片的文件中读取的)还不知道v4是啥,v4这个值的函数太恶心了,所以我们动调一下,把所有文件都扔到目录下:
先输入:
1 | ./simplefs image.flag 500 |
然后进行attach一下,程序就动起来了,最好在sub_5615516A21B2函数进行断点。
然后按照指示的命令调下去,然后点F9,进入断点
调试出v4的值,然后我们其中能动调出v5的每个值,前三个0x00和0xD2和0xFC,那么我们直接去flag图片里找就行了,当然我们可以直接动态调试出原来的flag,可以不用写脚本。
当然还是写一下逆向的脚本:
1 | a = [0x00,0xD2,0xFC,0xD8,0xA2,0xDA,0xBA,0x9E,0x9C,0x26,0xF8,0xF6,0xB4,0xCE,0x3C,0xCC,0x96,0x88,0x98,0x34,0x82,0xDE,0x80,0x36,0x8a,0xd8,0xc0,0xf0,0x38,0xae,0x40] |
NaCl
这个题看一早上,汇编没学好,出不来,近些天上课正在看汇编,等我汇编看的差不多了,就开始整一下这个题的汇编。
函数逻辑很清晰,但这个函数里的内容就不敢苟同了,很杂很乱:
我只能说不太好看懂,经过动态调试后,发现其到38行后进入下面的汇编:
然后会进入一个巨大的循环,里面进行着加密,经过43次循环后,进入下一个汇编,这个汇编是xtea加密,这个xtea改了两处,第一是循环轮次,第二是delta。
参考了PZ师傅的解题思路,我们可以采用idapython去掉花指令的方法来生成可视的代码,从而避免看汇编。
不能反汇编的原因是因为,在这些汇编的后面是很奇怪的,他们有着统一的格式
1 | nop |
1 | 还有地方似乎更像是call指令 |
那么我们要用idapython修改的地方已经出来了,我们只需要将像上面第一个的改成retn,第二个改成call指令就行了。
1 | import idc |
然后我们点edit–patch program–最后一项,重进ida刷新,就会发现进入一片有反汇编的世界!
1 | __int64 __fastcall sub_8080900(__int64 a1) |
我学习到了比如像*(v3+36),*(v3+18),这样的,我们其实可以将其定义成一个结构体右键–creat new struct即可。
先进入第一个函数:
1 | __int64 __fastcall sub_8080720(__int64 a1) |
这个是先将8个字节又分为2组,每组4字节并将其倒置,然后对其低位左移,然后进行异或,这个异或很复杂,我们可以通过动调把xorkey调试出来。然后再看第二个加密
1 | __int64 __fastcall sub_8080100(int a1, __int64 a2) |
其中我们还是可以通过右键–convert to struct把v3转换成内含12个int的结构体,不难发现这是个xtea加密,传入参数第一个是循环轮次,这个轮次是2,4,8,16,delta变化了,所以逻辑理清了,就把脚本搞出来:
1 |
|
*CTF{mM7pJIobsCTQPO6R0g-L8kFExhYuivBN}
Jump
属实是把这题整明白了,看了一下午一直在想为什么这个会一直为0,原来是有setjump和longjump函数。
1 | int setjmp(jmp_buf env); |
众所周知,env一般代表的是上下文环境,在这也是如此,什么时候会用到这类函数捏。我们知道goto只能用于函数的局部跳转,跨函数是不行的,那么我们采用setjump和longjump就很好的解决了这个问题,这两个函数保存当前上下文,比如保存现有堆栈环境,在嵌套函数中进行随心所欲的跳转。
setjump返回值永远是0,这就说明了为什么在题中ifelse语句中,永远只进else语句。当先调用了setjump后,再调用longjump,那么返回回值由value参数确定,然后setjump也要返回非0值。这个和信号处理的一些函数很相似。
1 | __int64 __fastcall sub_40293E(__int64 a1, __int64 a2) |
我们先看de函数:
1 | void de() |
我们首先要盯着input里的变化,这个函数只在我们输入的字符串前增加2,字符串后加3
我是先看到了对比函数,所以先查看一下:
1 | void __fastcall sub_402826(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6) |
对比处藏着我们需要逆向的字符串,然后我们在byte_4C9420发现我们输入的字符串变化了,但变化我们不清楚,返回主函数,继续找,找到sub_401F62,其参数是经过de函数后的字符串,我们进去查看:
1 | unsigned __int64 __fastcall sub_401F62(char *a1) |
这个排序函数很长,很难搞,我们先动调搞一下,发现经过sub_401EF6(v10, v6, 8); 函数时会让字符串循环左移一位,然后我们直接略过这个循环函数,直接看循环后的结果,发现在藏字符串的地方存了许多长字符串,那么我们经过观察,能发现几个规律,这个字符串的第一个字符是经过从小到大排列的,而且第一个字符和最后一个字符是永远绑定在一起的,而我们要解密的字符串经过:
1 | byte_4C9420[v7 - 1] = *(_BYTE *)(dword_4C613C - 1LL + *(_QWORD *)(8LL * v7 - 8 + qword_4C9408)); |
也就是取字符串最后一位连起来并且首位是2,末位是3,所以很简单了,直接手算就出了。
1 | \x02\x03+1246=BCDEFGLNRSTVZ_acjkmnpquvwx #第一位字符,按升序排列 |