*CTF 2021 Reverse*5
文章目录
乱七八糟的考试终于结束了,这次是时隔半年重新回来和AAA的队友们一起打比赛,又回归了熬夜的生活。这次比赛6个Reverse做出5个,剩下的一个RL_Env貌似是个机器学习题,真不愧是自由而无用的灵魂,我甚至都没有看懂题目在做什么。
stream
rust题目,还好有符号,拖到IDA里打开,std::rt::lang_start_internal
的第一个参数是rust里的主函数。
|
|
读入文件后主要加密过程在这个循环中完成。试着写了两行rust,发现rand_chacha::guts::{init_chacha, refill_wide}
两个函数都不是pub的,说明并非直接调用这两个函数,而是编译时内联了进来。看guts.rs的源码可知refill_wide
的第二个参数是round,可以发现10轮的是同一个包里的ChaCha20Rng
。IDA调试一下可知生成的随机字节填充在v22的buffer里,最后*(_BYTE *)(v3 + v6) ^= LOBYTE(v22[0]);
表示只用了第一个字节异或到原先的明文上。上面的init_chacha
是ChaCha20Rng::from_seed
展开而成,key是v9开始的32字节。*((_BYTE *)&v13 + (v4 & 0x1F)) = *(_BYTE *)(v3 + v6);
表示每次从明文取一个字节,循环放入key中,最终生成的伪随机字节重新和这个字节异或,那么每轮只有256种可能性,可以爆破。从明文取字节的顺序是4,11,18…最开始的时候key全为0,只放入一个明文字节爆破第一个字节后继续爆破第二个,依次类推,最后有多解情况,把flag限制在ascii内减少尝试。
由于我不会rust,又没有找到合适的等价实现,就用rust包装了一个随机数生成程序,主程序用Python实现。
|
|
|
|
flag:*ctf{EbXZCOD56vEHNSofFvRHG7XtgFJXcUXUGnaaaaaa}
wherekey
从网上找到一个libc 2.31的signature安上去,主函数花指令处jnz改为jmp可以反汇编。发现这个程序中间还有一个listen和send,自己给自己发送数据来增加工作量。
|
|
上面的或操作是宏FD_SET(fd, set)
展开后的代码,下面的与操作是FD_ISSET
宏,都是和select一起使用的。tty收到数据后交给sub_402072()
处理,看出发送的数据包应该是5个5字节的。看sub_40223C()
处理socket收到的数据包,该数据包又交给sub_4022DE(__int64 a1)
处理,每5个字节和’flag{are_you_sure_friend}‘抠出来的字节相乘,实际上是一个线性方程组,常量字符串是系数矩阵,共有5组线性方程组。去掉花指令后找到比较对象是unk_4C5150
。翻出自己的陈年老代码用高斯消去法找出逆矩阵,再分别和5个向量相乘,即可得出明文。
|
|
flag:*CTF{Ha23_f0n_9nd_G0od-1uck-OH}
ChineseGame
同样是静态链接,找到libc和libstdc++的signature安上。有一个10元素的链表,链表节点存放了一个数,只需要关心这个数是否大于100,若将大于100简记作+,小于100为-,初始状态是+-++++++++,最后要求全为-。输入内容是01字符串,分别用来选两种操作,冥冥之中感觉像是九连环游戏。每种操作都有一个位置参数,从dword_5D5140
开始取。两种操作的内容是:如果该位置后面节点的状态是+---…(或者根本没有后面的节点),则将该位置变为+/-,否则什么都不干。经队友提示,发现dword_5D5140
的位置参数恰好是正确解法需要操作的位置,所以只需找出进行此操作时是由+翻转到-还是-翻转到+即可。
|
|
flag:*CTF{4ncient_G4me_Fr0m_4ncient_Ch1na!}
Favourite Architecure flag0
加入此题之前,队友已经逆向了一部分。flag长度为0x59,前0x29字节进行Chacha20加密(从函数的密钥扩展中的常量字符串’expand 32-byte k'看出。同样由D. J. Bernstein设计的Salsa20、Ed25519、Ed448都有特殊的字符串),提取固定的key和ciphertext解密即可。后0x30字节是TEA加密,但被修改成了16轮,维基百科上有如下示例代码:
|
|
decrypt中的sum=0xC6EF3720是加密时sum每轮加上delta得出的累积值,若轮数减少为16轮,则sum=(0x9e3779b9*16)&0xffffffff=0xe3779b90,修改后解密即可。
flag:flag{have_you_tried_ghidra9.2_decompiler_if_you_have_hexriscv_plz_share_it_with_me_thx:P}
1rep
这是一个perlcc编译perl产生的程序,自己搞了一个perl脚本试着编译了一下,发现所有的这种程序的主函数都依次调用如下函数:
|
|
从自己编译的源码中看出,v20是一个PerlInterpreter*
,说明这种编译器实际上不是像cython那样把脚本代码转为等价的C代码,而是预先设置好变量、字节码之后,整体交给perl虚拟机运行,此处的perl虚拟机实例就是这个PerlInterpreter
对象。最初的解题思路是借助调试器的appcall机制,设法从这个虚拟机实例里提取字节码。发现libperl里有一个perl_dump_all()
,gdb在perl_run
设好断点后,将rip改为perl_dump_all
的地址并继续,结果dump出一大堆optree,依旧没法看。后来发现perl的B::Deparse
模块有反编译功能,参考StackOverflow上的提问,利用Perl_eval_pv
向PerlInterpreter
里注入代码运行。
|
|
但是注入以上代码后,只反编译了开始的一小点检查。flag去掉外壳后,里面16字节交给kwvIJu
检查。再注入B::Deparse
的其他功能反编译特定函数:
|
|
不知道为什么kwvIJu
没法反编译,但是试着反编译别的函数发现,所有函数都是检查当前flag的首字符(位于0-f范围),然后根据此字符switch到16个不同的分支,每个分支再调用不同的其他函数,调用的函数用来处理去掉首字符后剩下的flag。观察IDA中存放字符串的地方,发现.rodata:00000000032E6CAF夹杂着一个Correct,试着反汇编上方的afRNDz
,发现这个函数是打印Correct,所以需要找出是哪个函数经switch语句调用了afRNDz
。试着再反汇编上方的xmecUK
,发现它调用了afRNDz
,再继续往上翻找,可以确定flag的最后14字节,但是后面再往上就找不到调用这一串函数的函数了,只好爆破最前面两字节,侥幸拿到flag。
|
|
flag:*ctf{7a4bb0982b39baa2}