L3HCTF WP
L3HCTF Reverse WP
放假生物钟已经倒过来了,每天早上10点都起不来,都是下午1-2点起床做题,全程就拿了snake的2血,其他的都因为生物钟没争取到😢,这次的XCTF强度不高,都是可以做的。
TemporalParadox
通过字符串交叉调用找到main函数,IDA无法F5反编译,包含了许多异常。直接Delete函数,然后忽略开头这些汇编,从下面的开始按P还原函数就可以F5反编译。
一些参数不确定、由指针调用表示的函数,都可以直接跳转到对应函数按F5,再转回main,再按F5让IDA重新识别。
这边开头是判断了时间戳是否在某个范围内,然后执行里面的代码。
底下要求输入query,然后MD5后判断是否与目标值相等,那这边主要就研究上面时间戳范围内执行的代码。
时间戳范围内执行代码的第一个函数进来发现就是构造query的地方。
将获取的时间戳传入函数,赋值给一个全局变量。
然后循环调用一个函数进行计算,这个函数就是利用刚刚那个全局变量进行的计算,最后给v45、v48、v49、v50、v51赋值。
通过两个全局静态值以及刚刚上面得到的值进行计算,最后比较两个计算值是否相等,然后走不同的query构造代码。
由于全程计算仅仅由时间戳控制,并且已经给出时间戳范围,就可以采用时间戳爆破方法来爆破符号目标MD5的query字串。
红框处是参数值对应的变量,salt是固定的可以直接调试取到,tlkyeueq7fej8vtzitt26yl24kswrgm5
解密代码
1 |
|
SHA1(query) = 5cbbe37231ca99bd009f7eb67f49a98caae2bb0f
L3HCTF{5cbbe37231ca99bd009f7eb67f49a98caae2bb0f}
终焉之门
OpenGL程序,核心代码在着色器代码里面,断点此处就可以得到解密后的着色器代码,
着色器代码:
1 |
|
可以发现是一个简易VM,通过开头几个参数的binding值,02345可以在IDA找到对应的变量,然后在上面代码就可以找到对应的资源数据。
这边就是判断输入flag长度、格式,然后将括号内每两个字节进行unhex存入co_consts前十六个位置。
如:”1f3d22” -> {0x1f,0x3d,0x22}
这边采用同构VM代码,通过输出流程来研究加密。
同构代码:
1 |
|
输出:
1 | stack_data[0] = co_consts[0]; |
通过研究上面流程输出,可以发现co_consts里面存在的那十五个整数是作为一个key的作用,然后分为两种加密。
- 密文=密钥[i-1]-(明文[i]^明文[i-1])
- 密文=(明文[i]^明文[i-1])+密钥[i-1]
1,2,5,12,13,15下标是第一种,其余是第二种加密。
解密代码
1 |
|
L3HCTF{df9d4ba41258574ccb7155b9d01f5c58}
snake
IDA调试发现存在反调试直接退出,直接断点runtime_main_func1开头,用ScyllaHide就可以过反调试。
发现运行后过一会报time error错误退出,可能是存在其他反调试,这边直接断点runtime_exit,然后运行游戏马上按ESC手动退出,通过栈回溯就可以找到调用方。
发现是这个函数这边调用的,那么就重点调试这边上面部分的代码。调试发现sub_50D220执行游戏就开始,那么游戏核心函数就在这里面。
之后会遇到很多函数无法反编译,解决方法是直接delete函数,跳过开头这两行汇编,对下面的剩余代码按P还原成函数即可。
动调sub_50D220内部,最后发现sub_509EA0这边调用了游戏Call,步入进去,走到call rcx再步入,就可以跳到游戏部分,sub_50BAC0。
在游戏Call开头会发现一个时间检测,检测时间差是否大于80ms,直接在v4判断处断点赋值rcx为0,就可以跳过if里面的代码,也就绕过了时间检测。
可以看到该函数里面有一些类RC4的代码。
通过动调、patch,发现该处是加分的地方,只要满足外面两个if进来就是score+1,然后下面进行一次类RC4的加解密。
直接将这两处的判断都去掉,就可以让他进入加分代码。
在此处断点,随便修改个分数,0xAA,然后运行代码。
发现游戏Call调用方函数下面这个函数被调用了。
步入函数,发现这边判断了分数是否>=100,然后执行里面的一些相关操作,会输出一些数据,但是发现执行后并没有输出什么在控制台。
猜测可能是要手动一次一次执行加分代码到100,因为加分代码里面存在类RC4加解密代码,可能是需要执行100次才能让这边成功输出数据。
这边采用patch加分条件代码来让程序跑的时候一直自动加分,还需要patch时间检测相关代码,不然程序正常运行的时候跑一半就会报time error错误。
一下截图为需要nop的两处地方。
保存patch到程序,运行让他跑到100,控制台就会自动输出Flag。
L3HCTF{ad4d5916-9697-4219-af06-014959c2f4c9}
ez_android
java层太多东西找不到核心,直接看lib,在greet函数可以找到加密代码以及密文比较,直接编写解密即可。
解密代码
1 |
|
L3HCTF{ez_rust_reverse_lol}
easyvm
主函数可以看到先调用了VM,然后再调用了一个密文比较,密文这边就可以取到,共32字节。
这里就是核心VM代码。
可以看到VM里面存在共8种运算,我的做法是只关注运算。直接右键同步到汇编界面,对8个运算的汇编都断点,写条件代码,将运算的两个值以及结果都输出出来。
以加法为例子,汇编界面这边两个加数是从rbp里面取的,然后计算结果存在eax里面,所以就设置断点在add下一行,然后写如下代码,获取两个加数以及计算结果,按”%X = %X + %X;”格式化输出,其余7个运算同样这样做就可以。
运行程序输入”11112222333344445555666677778888”,方便观察加密过程。可以得到约5000行输出,但是我们只需要观察其中十几二十行就可以。
这是第一轮运算,我已经注释上了对应的代码,可以通过运算清楚看出是一个魔改TEA代码,而且Key就在运算中可以得到。
Key:{ 0x0, 0xA56BABCD, 0xFFFFFFFF, 0xABCDEF01 }
Delta:0x11223344
轮数:64
1 | // Key{ 0x0, 0xA56BABCD, 0xFFFFFFFF, 0xABCDEF01 } |
还原加密代码:
1 | uint32_t key[]{ 0x0, 0xA56BABCD, 0xFFFFFFFF, 0xABCDEF01 }; |
需要注意一点,观察输出数据可以发现不同组加密之间共用一个Sum值,也就是第一组数据加密后的Sum值会作为下一组数据加密的初始Sum值,所以解密的时候要注意Sum值。
解密代码
1 |
|
L3HCTF{9c50d10ba864bedfb37d7efa4e110bf2}
obfuscate
调试程序发现会退出,应该有反调试,找到其中一个exit函数,交叉调用发现多处调用,直接把这边的jmp改成retn就可以过反调试。
单步调,发现函数里面翻到这两个相邻函数有大量的计算代码。
250函数是传入一个字符串”WelcometoL3HCTF!”,然后生成26个DWORD密钥,然后再将明文和生成的密钥传入E80函数进行加密。
生成的密钥可以再250函数结尾取得。
调试E80加密函数,第一个这边循环是将输入的两个DWORD值分别加上Key[0]和Key[1]。
下面是从1开始到0xC的循环加密,这边是核心加密,去除混淆无用计算,动调就可以还原代码。
加密代码还原:
1 | void Encrypt_(uint32_t* Input) |
密文的话,查printf的交叉调用就可以找到,这边有进行密文比较,动调取v3数组数据即可。
解密代码
1 |
|
L3HCTF{5fd277be39046905ef6348ba89131922}