题目还不是很难,所以还是放在一起来写
tt3441810
这题并不知道是在干什么,IDA打开是个dumpfile,打开给了很多16进制,看到了0x68
这个经典数字(push
指令的编码),后面接了两个字符,看到fl
,感觉有问题,找到后面很多个0x68
,每个后买你都跟了两个字符,像是把flagpush
进栈的操作,所以把这些数据导出,然后写个脚本跑一下,验证猜想
1 | #include <iostream> |
输出flag
1 | flag{poppopret} |
提交的时候只需要中间的部分
re2-cpp-is-awesome
这题有很多string类,所以还是要慢慢分析
1 | __int64 __fastcall main(int a1, char **a2, char **a3) |
真正有用的内容只有一个for循环,在这之前我们输入的字符串被传入了v12
,然后用迭代器进行循环,遍历整个字符串,每个字符被赋值给了v9
,然后进行判断,如果判断可以通过,我们输入的就是正确的flag,判断的条件是v9 = off_6020A0[dword_6020C0[v15]]
,一个嵌套索引,把数据导出之后很容易得到结果
1 | target = "L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t" |
输出结果
1 | ALEXCTF{W3_L0v3_C_W1th_CL45535} |
流浪者
这题是MFC,题目中的函数很多,打开发现输入有错误提示,还是从字符串入手,找到了几个很有用的函数
1 | BOOL __cdecl sub_4017F0(int a1) |
更改了一些函数和变量名,更容易辨认,这个函数传入了a1
之后,对a1
之后的地址上的内容作为索引值依次连接组成了一个新的字符串,所以a1
显然应该是一个地址,或者说是一个数组的首地址,而这个数组的内容我们很容易就可以得到,接着再看到底在哪里引用了这个函数
1 | int __thiscall sub_401890(CWnd *this) |
这个函数也很简单,把输入的内容赋给Str
,然后对str里面的字符进行一个变换,把变换后的数组传给刚刚分析的那个函数,所以想要逆向解出输入的字符就很简单了
1 | str1 = 'KanXueCTF2019JustForhappy' |
根据题目要求把输出的内容套上flag
1 | flag{j0rXI4bTeustBiIGHeCF70DDM} |
easyRE1
毫无意义的题目,打开就能看到flag,套上flag{}
直接交上去就行了
debug
题如其名,peid打开发现是c#,所以直接dnspy打开,发现flag是直接计算出来的,并且没有进行任何的反调试,所以直接调试运行几步就得到了flag
1 | flag{967DDDFBCD32C1F53527C221D9E40A0B} |
Guess-the-Number
这题是java逆向,cfr反编译之后,查看源代码
1 | import java.io.PrintStream; |
程序很简单,我们当然可以根据算法来算出来flag的值,但是完全可以得到需要输入的数,所以直接运行就好了
1 | >java -jar Guess-the-Number.jar 309137378 |
输出结果
1 | your flag is: a7b08c546302cc1fd2a4d48bf2bf2ddb |
直接提交即可
EASYHOOK
这题很有意思,值得好好分析一下,IDA打开,找到main函数
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
第一反应当然是查看一下sub_401240()
的内容,毕竟这是最后一个调用NumberOfBytesWritten
的函数,而这个值,经过writeFile()
之后应该是19
1 | signed int __cdecl sub_401240(const char *a1, _DWORD *a2) |
看到This_is_not_the_flag
感觉有些不对,仔细观察发现这个字符串有20的字符,而下面的判断需要我们的输入和这个字符串相等,但是我们只输入19个字符,所以这个函数永远不可能返回我们想要的结果,肯定是有什么东西被漏掉了,所以返回main
,前面还有一个sub_401220()
没有看
1 | int sub_401220() |
在这里发现了问题,这里找到了储存writefile()
地址的位置,然后保存前五个字节的内容,后面进行了一些赋值,看到0xE9
就发现这里想要修改writefile()
函数地址,跳转到另一个函数sub_401080()
,然后函数进入sub_4010D0()
1 | BOOL sub_4010D0() |
这个函数把刚刚的修改写进了相应的地址,此时程序一旦调用writefile()
,就会跳转到sub_401080()
,所以转到这个函数
1 | int __stdcall sub_401080(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped) |
如果sub_401000()
返回值是1,就会输出我们的输入是正确的,sub_401140()
是用来恢复writefile()
函数的地址,就不再赘述,所以重点就是sub_401000()
1 | signed int __cdecl sub_401000(int a1, signed int a2) |
这里的操作就很简单了,也不需要再多说,我们的输入经过处理后如果和内存中的字符串相同,就对了
1 | #include <iostream> |
这里有一点需要注意,这个程序是没法检测第一位输入的,中间的过程也完全没有用到第一位,所以根据输出结果手动添加了’f’,输出
1 | flag{Ho0k_w1th_Fun} |
可以验证一下,第一位其实并不影响结果
发现第一位改成其他的字符也是正确的
reverse-for-the-holy-grail-350
这题还算是比较简单的题目,很容易就可以找到关键函数,然后发现所有的数据处理和验证全部都在这个函数里面
1 | __int64 __fastcall stringMod(__int64 *a1) |
发现把输入分成了三个部分,处理起来也很简单,写脚本处理
1 | #include <iostream> |
输出flag,再套上main
里面提供的格式即可
1 | tuctf{AfricanOrEuropean?} |
android-app-100
反编译之后发现还调用了c的库,于是IDA打开so文件,看到了混淆,但是内容比较好猜,所以直接找到字符串计算md5或者在apk里面提交就好了
1 | Sharif_CTF(833489ef285e6fa80690099efc5d9c9d) |
dmd-50
打开直接找到输入的flag的md5值,md5解密找到flag
1 | b781cbb29054db12f88f08c6e161c199 |
Windows_Reverse1
这道题很有意思,首先查壳,发现是upx,直接脱壳,IDA打开,F5查看
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
表面上看我们需要输入一个字符串,然后经过sub_401000
的变换之后变成DDCTF{reverseME}
,然后查看一下加密函数
1 | unsigned int __cdecl sub_401000(const char *a1) |
看起来是一个换表操作,但是在这里遇到了两个让人疑惑的事情,第一个,我们输入的字符串是怎么传入这个函数里面来的,第二个,在0x402FF8
处什么都没有,这个表在哪里
所以还是需要仔细看看汇编代码
1 | .text:004010A4 lea edx, [esp+824h+var_404] |
这里应该是人为修改了代码,可以看到我们输入的字符串地址被赋给了ecx
,然后再我们的解密函数最开始,先把ecx
里面的值压进了栈,所以这个函数用了ecx
寄存器来传参,而这是个32位的程序,所以IDA这里检测的时候出现了一点问题,第一个问题解决了,再看看第二个,先来研究里面的索引值
1 | if ( result ) |
v1[v4]
这个表达形式可以看得出来就是a1
的首地址,而a1
就是我们输入的字符串,都是可见字符,所以每个字符的值都要大于32,所以在0x402FF8
这个位置向下偏移至少32的位置寻找,找到了真正的字母表
分析结束,可以直接动手逆向了,不过在导出这个字母表的时候很凑巧的发现这个字母表的值刚好是从126-32的排列,这说明我们输入的和变换出来的值相加正好为158,这就提供了一个更简单的逆向思路
1 | #include <iostream> |
输出套上flag即可
1 | ZZ[JX#,9(9,+9QY! |
babymips
这个题本身没有什么,很简单的算法,也没有什么加密、加壳,主要是有些受不了IDA+Retdec反汇编出来的代码,所以试了一下Ghidra
1 | void FUN_004009a8(void) |
首先是输入flag,然后进行一个变换,先比较前5位,如果一致进入下一个变换
1 | void FUN_004007f0(char *param_1) |
后面对奇数位和偶数位进行了两个不同的位运算,很清晰,仔细分析一下会发现实际上是前后位置的一个交换,并且奇偶运算就是互为逆运算,逆向的时候更方便处理了。最后是进行简单的字符串比较,然后程序就结束了。
1 | #include <iostream> |
需要注意的两点,运算符优先级的问题,加减运算要先于移位运算先于位运算,处理的时候需要注意,还有,移位的时候要注意把多余的位置清0,这里用的是与运算
输出flag
1 | qctf{ReA11y_4_B@89_mlp5_4_XmAn_} |
Comments