是收录在攻防世界真题区的刷题记录,在网上找到的wp大多是各战队比赛时的简单思路,没有很具体的做法,所以想着写个刷题小记当伪wp,顺便也想趁机给自己总结和记录一下。
题目指路:https://adworld.xctf.org.cn/competition
刷题范围:2019年以来的比赛中>=20解的逆向题,循序渐进吧。
P.S. 起步tcl,会有参考其他wp的思路,什么时候没有参考wp了就说明我终于能单刷了(yep!
[2019 *CTF] fanoGo
- 2019 *CTF / Reverse / 28 solved
- Completed:2020.12.08
- 参考wp:
题目描述
Do you kown go & fano encode?
1 | nc 34.92.37.22 10001 |
wp
相关工具/插件:[IDA] IDAGolangHelper
用了插件以后,主函数一般是main_main,所以找到主函数。
然后看到一行fano___Fano__Decode()
于是猜测是让我们输入数据,经过fanoDecode解码后等于该字符串(就是说输入数据=该字符串的fano编码)。
(我摊牌,Go实在看不懂,这里参考wp的思路了。不过如果实在做不出来我可能也会这么猜(?
然后在函数栏搜索能看到,打包的时候把fanoEncode函数也装进来了,所以想到patch call直接复用。
然后patch。
这里整理一下x86的call机器码格式(x64向下兼容:
E8 xxxxxxxx
xxxxxxxx是偏移地址
偏移地址=目标地址-当前地址-5(取完当前指令之后,pc先自增,程序再跳转;E8 xxxxxxxx有5字节
FF 15 [xxxxxxxx]
xxxxxxxx是绝对地址,FF15会对当前的这个绝对地址解*号,也就是绝对地址[目标地址]
原来的:
先找到fanoEncode的地址:
可以看到fanoEncode的地址为0x45C970。
然后计算偏移地址:
patch(别忘了小端序):
patch完以后愉快地开始动态调试,配置完Debugger以后,为了看加密结果在fanoEncode函数的这里下断点。
然后动态调试
断点处打开Locals窗口(Debugger->Debugger windows->Locals)查看编码结果:
v3这里是结果的地址,我们复制下来并在IDA view里按G跳转
把这个数组dump下来就是要输入的数据。
最后的exp:
1 | #!/usr/bin/env python |
flag
因为要nc没拿到flag(
本地打通就好
[2019 RCTF] babyre1
- 2019 RCTF / Reverse / 40 solved
- Completed:2021.03.24
- 参考wp:
- [原创]微型加密算法实现及逆向分析-密码应用-看雪论坛(查XXTEA的时候看到的
题目描述
It’s just a baby…
There are multiple answers, please ensure MD5(rctf{your answer}) == 5f8243a662cf71bf31d2b2602638dc1d
wrap your answer in rctf{}
wp
主函数这里逻辑比较明显,flag长度限定到16,puts(v6)很容易可以猜测是题目里说的输出Bingo!
。
sub_C00()
函数点进去能看到:
而sub_BE0()
实际上是取数组对应值,取ASCII码为48(ord('0')
)到48+55-1=102(ord('f')
)在byte_1620数组里对应的值,稍加分析就可以知道sub_C00()
是unhexlify的作用。
然后为了方便后续分析,根据函数内容和参数给变量重命名。
接着往下走到sub_1180()
,主要逻辑在sub_CE0()
这里:
而sub_CE0()
的整个大逻辑大概是:
1 | if ( a2 > 1 ) |
也就是说通过调用sub_CE0()
时的第二个参数a2来确定函数走哪一部分的流程。
而这个a2是可以算出来的,即sub_1180()
的-(v10 >> 2)
:
也就是说v10=a2=8
(这里a2是上一层函数传进来的hexLen,输入被限制在16字符,故hexLen一定为8),所以传进去的a2=-(v10>>2)=-(8>>2)=-2
,直接走下面那层a2 < -1
的逻辑。
其实这个sub_CE0()
函数长得不就像XXTEA的加密/解密函数吗(长得这么别致的真的不多见= =。
关于TEA系列加解密:TEA、XTEA、XXTEA加密解密算法_gsls200808的专栏-CSDN博客
在上面链接摘取一下XXTEA的实现代码:
1 |
|
很明显地,Decoding Part跟sub_CE0()
的a2<-1
部分基本是一模一样的,也就是我们要走的这个流程。
1 | if ( a2 < -1 ) |
所以可以确定这是个XXTEA的解密过程。
接着回到sub_1180()
,可以看到XXTEA decrypt后还有一些操作:
最后一个byte的值即v11要<=0x04才能进入到这个关键return,并且把让v8[a5]=0x00截断,此时a5=8-v11。
同时这里出现的这个关键变量a5
,就是传进来的第五个参数即主函数的v9
,这个变量控制着主函数下面的输出:
前面题目也说了,"Input right flag you can got 'Bingo!' :"
,所以这里就是输出Bingo!
的地方,长度不对等,有两种可能:
- v9=6,按字节xor 0x17并输出前6字节。
- v9>=7,第七字节xor 0x17后=0x00,即第七字节=0x17,把字符串在puts的时候截断。
无论如何,前六字节都已经确定了,分别是Bingo!
每字节xor 0x17的值。
加密(解密)方法确定,接下来,我们只需要确定各参数里放的数据就行了。
从调用sub_CE0()
处可以依次确定主函数里的unk_202010
是XXTEA的key,v5
存着解密后的字符串,v9
是解密后字符串的长度。
于是原关键部分标记成:
在sub_13D0()
中,我们可以发现一个用得很频繁的常数——0x1021
:
通过常数和8轮的xor可以猜测是CRC的其中一种(计网刚学完hhh),在搜索引擎查一下0x1021
这个常数也能知道是一种CRC-16:
看了一下算法对比感觉是CRC-16/X25
(见CRC16常见几个标准的算法及C语言实现_leumber的专栏-CSDN博客),然而是什么其实都不重要,反正校验算法就意味着不能通过逆向计算来通过27106反推出字符串。
那么,汇总一下:现在相当于我们拿到了这个字符串前六字节(xor 0x17以后等于Bingo!
),并且这个字符串通过CRC-16/X25
计算后出27106,由题目描述知道MD5(rctf{your answer}) == 5f8243a662cf71bf31d2b2602638dc1d
。而这个字符串是由一个十六字节的输入经过unhexlify以后再经过XXTEA的解密后得到的。
既然有校验存在(md5和crc),那我们就愉快地爆破吧。
理论上说应该是只用crc爆破的,但crc容易有多解,所以题目描述又多加了一层md5防止多解。
所以其实只用md5来爆破就好。
先用[hex(ord(x)^0x17) for x in 'Bingo!']
算一下前六字节:
按小端组合,可以得到这两个解密出来的数字分别是0x70797e55
和0xZZZZ3678
(高两字节未知)。
于是写得爆破的exp:
1 | // MD5.c来自https://github.com/pod32g/MD5 |
爆破就能得到flag啦~
flag
rctf{05e8a376e4e0446e}
[2019 RCTF] babyre2
- 2019 RCTF / Reverse / 28 solved
- Completed:
- 参考wp:
- 有?
题目描述
yet another baby reverse for u…
nc 139.180.215.222 20000
nc 123.206.174.203 20000
wp
是一个变量巨多函数也居多的题。
分析过程不再赘述,要么比较简单要么跟前面挺像的,直接贴出最后改了名的分析结果好了:
其中有一个需要忽略的函数sub_564B320C6B90()
,这个函数主要是用于输入输出用的,相当于一个VM的I/O机,可以不用管。
整个程序的流程是依次让我们输入account
、password
、data
三个数据,如果经历了一系列加密以后通过了check
,则输出flag。
flag
[2020 GACTF] EasyRe
- 2020 GACTF / Reverse / 38 solved
- Completed:2021.01.02
- 参考wp:
- 无(ohhhh!
题目描述
Layers of protection~
Overseas:https://drive.google.com/file/d/1TobAUm_innt7RoQ7ZI4q1CNUWKuH-Tsw/view?usp=sharing
wp
用ida打开发现main看起来没什么特别的,就是输入一个lld。
不过一个一个函数往下翻可以看到sub_8049065()这个,调用了一个mprotect函数(警觉
mprotect的作用一般是改某段内存的读/写/执行权限,并且下面sub_8048EA5()还用了一个函数(就是主函数中即将调用的函数)作为参数,这就要立刻想到smc了,sub_8048EA5()就是加密函数。
这边一看就感觉很复杂的样子,那就懒得逆了,直接动态调试看看过完加密以后sub_8048838()是什么样子。在这个函数下个断点,执行到的时候直接步入。
可以看到这边比较散乱,因为数据全部被处理过了,要看到反汇编和反编译代码就要首先把它们undefined,然后重新分析。
这个函数的起始地址是0x8048838,结束地址是0x8048DE1,算一下长度是0x8048DE1-0x8048838+1=1450。
然后用ida_bytes的del_items函数(7.4的api开始不再兼容6.x的api了,在7.4以前还能用idc.MakeUnkn,见Porting from IDAPython 6.x-7.3, to 7.4)或者选这个区域直接右键->Undefined把这部分undefined掉,再在0x8048838出按p
创建函数。
可以看到这其实是个vm题,smc+vm真不愧是Layers of protection呢hhh。vm代码如下:
1 | unsigned int __cdecl sub_8048838(_DWORD *a1) |
仔细研究可以知道,a1这个数组里,a1[1]~a1[4]和a1[9]是一组寄存器,a1[8]指向的地方实际上是opcode(a1[8]其实就是PC),dword_804B28C则是刚刚输入的数据(myInput),byte_804B2E0就是输入的flag。
整理可以得到以下伪代码:
1 | //arr->4bytes |
然后去a1[8]的地方拿到opcode,反汇编。
把opcode即unk_804B080处的数据dump下来,convert成python样式的byte数组,写反汇编脚本:
1 | opcode=[0x09, 0x10, 0x80, 0x02, 0x0D, 0x00, 0x00, 0x00, 0x22, 0x77, 0x10, 0x80, 0x02, 0x09, 0x00, 0x00, 0x00, 0x23, 0x80, 0x02, 0x00, 0x96, 0xF3, 0x78, 0x31, 0x77, 0x10, 0x80, 0x02, 0x11, 0x00, 0x00, 0x00, 0x23, 0x80, 0x02, 0x00, 0x00, 0xD4, 0x85, 0x31, 0x77, 0x10, 0x80, 0x02, 0x13, 0x00, 0x00, 0x00, 0x22, 0x77, 0xA0, 0x09, 0x80, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x31, 0x80, 0x03, 0x02, 0x00, 0x00, 0x00, 0x43, 0x80, 0x02, 0x18, 0x00, 0x00, 0x00, 0x41, 0xA4, 0x00, 0x00, 0x00, 0x09, 0x80, 0x02, 0x08, 0x00, 0x00, 0x00, 0x22, 0x80, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x31, 0x80, 0x05, 0x07, 0x00, 0x00, 0x00, 0x44, 0x80, 0x02, 0x21, 0x00, 0x00, 0x00, 0x41, 0xA4, 0x01, 0x00, 0x00, 0x09, 0x80, 0x02, 0x10, 0x00, 0x00, 0x00, 0x22, 0x80, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x31, 0x80, 0x09, 0xBB, 0x00, 0x00, 0x00, 0x77, 0x80, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x41, 0xA4, 0x02, 0x00, 0x00, 0x09, 0x80, 0x02, 0x18, 0x00, 0x00, 0x00, 0x22, 0x80, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x31, 0x80, 0x04, 0xA0, 0x00, 0x00, 0x00, 0x42, 0x80, 0x02, 0x77, 0x00, 0x00, 0x00, 0x41, 0xA4, 0x03, 0x00, 0x00, 0xA1, 0xC1, 0x00, 0xB1, 0x77, 0xC2, 0x0B, 0x01, 0x00, 0x00, 0xC1, 0x01, 0xB2, 0x77, 0xC2, 0x7A, 0x00, 0x00, 0x00, 0xC1, 0x02, 0xB4, 0x77, 0xC2, 0x95, 0x00, 0x00, 0x00, 0xC1, 0x03, 0xB3, 0x77, 0xC2, 0x06, 0x01, 0x00, 0x00, 0xC1, 0x04, 0xB2, 0x77, 0xC2, 0x7D, 0x00, 0x00, 0x00, 0xC1, 0x05, 0xB4, 0x77, 0xC2, 0xAD, 0x00, 0x00, 0x00, 0xC1, 0x06, 0xB1, 0x77, 0xC2, 0x2F, 0x01, 0x00, 0x00, 0xC1, 0x07, 0xB3, 0x77, 0xC2, 0x65, 0x01, 0x00, 0x00, 0xC1, 0x08, 0xB1, 0x77, 0xC2, 0x2D, 0x01, 0x00, 0x00, 0xC1, 0x09, 0xB1, 0x77, 0xC2, 0x2F, 0x01, 0x00, 0x00, 0xC1, 0x0A, 0xB3, 0x77, 0xC2, 0x39, 0x01, 0x00, 0x00, 0xC1, 0x0B, 0xB3, 0x77, 0xC2, 0x0D, 0x01, 0x00, 0x00, 0xC1, 0x0C, 0xB4, 0x77, 0xC2, 0xBB, 0x00, 0x00, 0x00, 0xC1, 0x0D, 0xB2, 0x77, 0xC2, 0x08, 0x00, 0x00, 0x00, 0xC1, 0x0E, 0xB3, 0x77, 0xC2, 0x0D, 0x01, 0x00, 0x00, 0xC1, 0x0F, 0xB1, 0x77, 0xC2, 0x3F, 0x01, 0x00, 0x00, 0xC1, 0x10, 0xB3, 0x77, 0xC2, 0x3A, 0x01, 0x00, 0x00, 0xC1, 0x11, 0xB3, 0x77, 0xC2, 0x61, 0x01, 0x00, 0x00, 0xC1, 0x12, 0xB2, 0x77, 0xC2, 0x57, 0x00, 0x00, 0x00, 0xC1, 0x13, 0xB1, 0x77, 0xC2, 0x20, 0x01, 0x00, 0x00, 0xC1, 0x14, 0xB3, 0x77, 0xC2, 0x0D, 0x01, 0x00, 0x00, 0xC1, 0x15, 0xB1, 0x77, 0xC2, 0x3F, 0x01, 0x00, 0x00, 0xC1, 0x16, 0xB3, 0x77, 0xC2, 0x3F, 0x01, 0x00, 0x00, 0xC1, 0x17, 0xB4, 0x77, 0xC2, 0xB5, 0x00, 0x00, 0x00, 0xC1, 0x18, 0xB1, 0x77, 0xC2, 0x13, 0x01, 0x00, 0x00, 0xC1, 0x19, 0xB4, 0x77, 0xC2, 0xA0, 0x00, 0x00, 0x00, 0xC1, 0x1A, 0xB1, 0x77, 0xC2, 0x21, 0x01, 0x00, 0x00, 0xC1, 0x1B, 0xB3, 0x77, 0xC2, 0x0D, 0x01, 0x00, 0x00, 0xC1, 0x1C, 0xB2, 0x77, 0xC2, 0x0B, 0x00, 0x00, 0x00, 0xC1, 0x1D, 0xB3, 0x77, 0xC2, 0x39, 0x01, 0x00, 0x00, 0xC1, 0x1E, 0xB1, 0x77, 0xC2, 0x73, 0x01, 0x00, 0x00, 0xC1, 0x1F, 0xB2, 0x77, 0xC2, 0x46, 0x00, 0x00, 0x00, 0x99] |
得到:
1 | r1=myInput |
根据上半部分的分析写出爆破脚本把这个数字爆破出来:
1 |
|
可以爆出这个数是4293442714,输入,发现果然出现了输入flag的提示:
接下来是flag的部分,逻辑很清楚啦,就是用这个数字生成一个arr数组,然后用flag[i] xor arr中的某一个等于已知值,基操xor不用多说。
这里为了更快处理就直接把check2下边的部分复制到code.txt
中了,直接进行自动化计算。
1 | # x=4293442714 |
flag就出来啦~
flag
GACTF{c7ack_m3_sh3ll_smc_vm_0k?}
TBC…