L3H_Sec招新2024-RE出题人wp
EasyReverse
题目附件为exe文件,使用exeinfo查看文件信息,64位、无壳、msvc编译器
使用IDA Pro加载exe文件,自动定位到main函数,使用F5
反编译查看伪c代码
1 |
|
使用N
键对部分函数及变量进行重命名(Y
键可修改变量类型)
不难看出sub_1400010C0
是加密函数,sub_1400013C0
是校验函数
1 |
|
加密过程
1 |
|
加密过程首先将输入flag与密钥REVERSE{sagashite_honmono_no_flag_ganbatte!}
进行异或,接下来每次3字节,与tmp相加,异或后tmp与flag进行交换
校验过程
1 |
|
校验时将加密后的flag与ans进行比较,完全相同返回0
从check
中获得加密后的flag,根据encrypt
的加密逻辑逆向求解得到解密算法(解密时flag后3字节作为tmp初始值)
1 |
|
正确flag:L3HSEC{We1c0me_7o_7he_W0r1d_0f_Rever5e}
空字符串也能使程序输出right
,应该不会有人把空字符串当作flag吧
JunkCode
花指令(junk code)是一种专门用来迷惑反编译器的指令片段,这些指令片段不会影响程序的原有功能,但会使得反汇编器的结果出现偏差,从而使破解者分析失败。花指令 - CTF Wiki
题目附件为ELF文件,可在linux平台运行
使用file命令查看附件信息,x64可执行ELF文件、动态链接
使用IDA Pro加载附件,自动识别到main
,无法使用F5
进行反编译,提示positive sp value has been found
,分析其汇编代码
发现如下异常指令,jmp loc_174A+1
指令长度2字节,跳转的目标地址为0x174B
,目标地址位于该jmp
指令内部,对jmp
指令使用U
键取消定义,对0x174B
处使用C
键转换为汇编指令
分析发现该部分指令无实际意义,只是对IDA反汇编功能的干扰,将这部分代码nop掉(推荐使用patching插件或keypatch插件)
除了上面的jmp
花指令外,还有call
花指令,处理方法类型
对于其它花指令,使用相似的方法进行处理
patch后仍然无法直接使用F5
对main函数反编译,因为IDA没有正确识别main函数的范围,右键main函数,选择修改函数,更改main范围,随后即可正常反编译
main函数
1 |
|
关注到loc_11C9
sub_14A0
sub_164B
三个函数,猜测前两个与加密相关,第三个与校验相关
这三个函数中同样存在花指令,patch后设置函数范围并反编译
1 |
|
1 |
|
1 |
|
熟悉常见加密算法的应该不难看出该加密算法是对RC4的魔改,修改S盒大小为128,并且将加密中的异或替换为加法
完成加密后,将加密flag与正确的值进行对比
根据上面的流程写出对应的解密代码(不必要逆向S盒初始化的代码,动态调试dump内存效率更高)
1 |
|
解密得到正确的flag:L3HSEC{Patch_15_vsefu1_1n_rev_w0r1d}
MoreJunkCode
题如其名,更多的花指令
main函数能够正常反编译
1 |
|
加密之后的校验函数也能正常反编译
1 |
|
loc_4B587
和loc_7CBD5
可能是加密相关函数,被大量插花,无法直接反编译
分析发现0x14D1
~`0x85C44`的部分都被大量插花
手动去除花指令显然不可行,因此接下来使用IDA Python脚本进行批量化操作
程序中的花指令可大致分为下面两种类型
影响反汇编的跳转指令
不影响反汇编的混淆指令
不影响反汇编的混淆指令可以先不考虑,先将影响反汇编的跳转指令patch掉使IDA能将所有代码正确反汇编
1 |
|
去除掉影响反汇编的跳转指令后,发现已经可以通过指令特征确定每一个函数的区域
函数入口
函数出口
根据函数入口和出口创建每一个函数
1 |
|
完成上面的patch后,保存patch,查看程序是否能正常运行
通过动态调试dump加密前后的数据进行对比可以确定patch是否影响到程序正常逻辑
能够正常运行,说明patch并未影响程序逻辑,并且IDA已经可以生成正确的CFG
但是无法进行反编译,因为代码中还有大量影响堆栈的花指令造成程序堆栈过大,需要去除这些花指令并调整栈平衡
程序中的花指令需要满足如下几个要求才能确保不影响原程序:
花指令自身栈平衡
花指令不影响程序使用的寄存器
根据程序中未加花函数,可以获得如下程序指令特征:
函数内不操作RSP寄存器(不是使用push pop等指令)
使用RAX~RSI寄存器、RBP寄存器
不使用R8~R15寄存器
不使用pushf popf操作FLAG寄存器
提取出程序中所有的指令块,根据指令块重复的次数及上面的规则,判断指令块属于花指令或原程序,对属于花指令的指令块进行patch
1 |
|
提取到的指令块不到500个,可以很轻松得分辨出哪些属于花指令(下面的情况可能会有不同,以脚本运行得到的结果为主)
1 |
|
将花指令块nop掉
1 |
|
保存patch检查程序能否正常运行(只要不段错误基本就没问题)
尝试反编译,提示栈帧过大
把2M的栈帧手动减小到0x1000,按U
删掉函数,按P
重新创建函数
到这一步,成功去花,接下来分析加密算法即可,回到main函数
1 |
|
计算输入字符串长度,16字节补齐(0填充)
1 |
|
sub_4B587
将输入的flag转换为16进制字符串(动调看函数运行后的结果也很容易看出来)
1 |
|
sub_11C9
已经确定过是校验函数,那么sub_7CBD5
自然就是加密函数
第一个参数和第二个参数猜测是密钥及密钥长度
1 |
|
1 |
|
sub_86FA
和sub_37064
都是对密钥进行操作,与输入的字符串无关,因此没有必要逆向这两个函数的算法,直接动调dump值
sub_86FA
:
其实就是md5
sub_37064
:
显然是把十六进制字符串转换为字节数据
然后再算一遍md5,现在就得到了密钥处理后的数据
1 |
|
接下来分组进行加密操作,每次32字节
1 |
|
1 |
|
变种xxtea
解密代码如下
1 |
|
正确flag:L3HSEC{Boku_to_keiyaku_shite_ctfer_ni_natte_yo!}
AWayOut
预期解法
IDA能够正确识别并反编译main函数
1 |
|
很明显的黑盒迷宫,hjkl
控制方向,sub_17DB
检查坐标是否可走,坐标可以走返回0,继续检查下一步,不可走die掉
sub_17DB
被vmp虚拟化,不太可能直接逆向,只能通过该函数的输入和输出判断某坐标是否可以走
因此有如下两种解法:
通过注入、patch的方式手动调用
sub_17DB
函数,通过修改参数遍历迷宫所有坐标patch主函数,记录
sub_17DB
返回不为0时所走的步数,如果die掉时走了n步,则输入的前n步所经过的坐标都是可走的
先在gdb中动调调用sub_17DB
,检查解法一是否可行
随意对一个函数断点,例如write(为什么选write不选puts呢?因为程序中有导入puts,可能存在保护)
程序跑起来之后在write断下,修改rdi、rsi寄存器为坐标,修改rip寄存器为sub_17DB
删掉write断点继续运行程序,正常情况下应该能在write调用的返回位置断下,查看rax获取返回值,但是运行后程序提示FILE CORRUPTED!!!
并直接退出,说明函数内应该存在保护,无法手动调用sub_17DB
(在上面的操作中,并没有对程序进行修改,那么程序是如何检测到上面的操作的呢?)
接下来继续使用gdb动调测试解法二
断点后运行发现也被程序检测到了,猜测是有软件断点检查,换用硬件断点
使用硬件断点后,程序正常运行,并且能够获取函数的返回值
经过上面的测试,发现程序有软件断点检测(软件断点的原理?patch是否会被检测?),只能用硬件断点来监测函数的输入输出
监测上面函数的输出比较复杂,可以在程序退出返回时下硬件断点,读取走的步数
1 |
|
现在已经可以走迷宫了,直接上dfs(由于程序比较慢,可以加上多线程)
1 |
|
正确路径: lljjljjhhjjjjjllkkklllkklllljllljjjjjjjlllljjjlllllklllllljjjhhhjjjljjllljjhhjjljjlj
正确flag: L3HSEC{b078e5b6cc04f827fe807a39631b240f}
非预期解法
迷宫每走一步调用一次sub_17DB
,如果是不可走的坐标就直接die掉,如果是可走的坐标才会检查下一步
由于sub_17DB
函数被虚拟化指令量极大,运行的时间消耗非常大(50ms+),因此可以通过记录程序运行的时间来dfs遍历迷宫
AntiDebuggerAllInOne
题目附件分为linux服务端与windows客户端两个部分,使用网络通信
client.exe
32位exe文件
点击按钮后获取输入框内容,调用sub_401000
进行处理
1 |
|
sub_401000
将输入框的宽字符转换位ASCII字符,将输入字符串格式化拷贝到buf中,sub_401780(buf)
对buf进行操作,随后创建socket使用UDP将buf发送到服务端,从服务端接收数据并使用MessageBoxA显示
1 |
|
代码中有很多的OutputDebugStringA
,该API用于输出调试信息,使用DebugView监听调试信息
调试信息中打印了输入框的内容,并且存在一条提示密码需要为UUID格式
动调观察以下buf的内容,前16个字节为用户名,接下来64字节为密码,buf总长度96字节,剩余16字节目前为空
接着分析sub_181780
对buf的操作
1 |
|
其中调用了5个函数依次对buf进行操作,这5个函数全部都被vmp虚拟化了,不能直接进行分析,尝试通过动调调试观察每个函数执行前后缓冲区的变化
下面的分析中有很多的反调试,使用ScyllaHide和SharpOD插件可以很轻松的绕过,但就如题目描述所说,不建议这样做,建议做题和复现时先手动绕过一遍反调试,绕过成功后再开启工具相应的反调试功能以节约时间
func1
func1断下后单步跳过,程序直接退出,查看DebugVier发现存在提示
IsDebuggerPresent
,根据该提示可猜测是调试器被IsDebuggerPresent API检测到了IsDebuggerPresent用于检测调试器,调试器存在时,返回值为1,调试器不存在是返回0
对IsDebuggerPresent下断点,断下后将返回值eax修改为0
成功绕过检查
观察buf内容,发现func1所做的操作是将uuid格式的密码解码为16进制保存在buf后16字节中
0x82, 0x84, 0xC9, 0xC4, 0xE7, 0x78, 0x43, 0x0F, 0xA7, 0x77, 0xEB, 0x7E, 0xF2, 0x2D, 0xF1, 0x49
func2
func2断下后单步跳过,和func1一样,程序退出,DebugView存在提示
NtGlobalFlag
是PEB中的一个字段,从ctfwiki中可以知道检测方式如下1
2
3
4
5mov eax, fs:[30h] ;Process Environment Block
mov al, [eax+68h] ;NtGlobalFlag
and al, 70h
cmp al, 70h
je being_debugged该检测方法和
IsDebuggerPresent
很相似,都是从PEB中获取调试信息,直接修改PEB的内容就能绕过将上面两处内容修改为0(第一处是IsDebuggerPresent的判定,改了之后就不用再修改返回值)
成功绕过检查
0xF1, 0xA7, 0x84, 0xE7, 0x78, 0x2D, 0x49, 0x82, 0xC9, 0xEB, 0x43, 0x0F, 0x77, 0xC4, 0x7E, 0xF2
分析处理前后两组数据,可以观察到第一组中的所有数据都在第二组中存在,只是位置发生了交换
1
2{0x82, 0x84, 0xC9, 0xC4, 0xE7, 0x78, 0x43, 0x0F, 0xA7, 0x77, 0xEB, 0x7E, 0xF2, 0x2D, 0xF1, 0x49}
{0xF1, 0xA7, 0x84, 0xE7, 0x78, 0x2D, 0x49, 0x82, 0xC9, 0xEB, 0x43, 0x0F, 0x77, 0xC4, 0x7E, 0xF2}为了更好的观察数据变化情况,更换一组更有辨识度的密码
00010203-0405-0607-0809-0a0b0c0d0e0f
1
2{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}
{0x0E, 0x08, 0x01, 0x04, 0x05, 0x0D, 0x0F, 0x00, 0x02, 0x0A, 0x06, 0x07, 0x09, 0x03, 0x0B, 0x0C}分析获得置换盒
{0x0E, 0x08, 0x01, 0x04, 0x05, 0x0D, 0x0F, 0x00, 0x02, 0x0A, 0x06, 0x07, 0x09, 0x03, 0x0B, 0x0C}
func3
DebugView提示func3存在
CheckRemoteDebuggerPresent
保护,同样的,直接断点该函数,改返回值为0(注意该返回值是通过参数返回,在堆栈中,不是eax)func3运行后
两组数据对比
1
2{0x0E, 0x08, 0x01, 0x04, 0x05, 0x0D, 0x0F, 0x00, 0x02, 0x0A, 0x06, 0x07, 0x09, 0x03, 0x0B, 0x0C}
{0x81, 0xC1, 0x00, 0x20, 0x80, 0xA1, 0xA1, 0xE0, 0x00, 0x41, 0x40, 0xC0, 0xE1, 0x20, 0x61, 0x61}1
2{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}
{0xE0, 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xE1, 0x01, 0x21, 0x41, 0x61, 0x81, 0xA1, 0xC1}分析发现func3是将数据整体循环右移3位
func4
DebugView提示func4存在
NtQueryInformationProcess
保护,根据ctfwiki可以知道,该API可以查询3个与调试相关的内容0x07 DebugPort
、0x1e DebugObjectHandle
、0x1f DebugFlags
同样的,断点改返回值(注意该API的返回指令并非反汇编识别出来的ret)
0x07 DebugPort
把返回值-1修改为0
0x1e DebugObjectHandle
把返回值修改为NULL
0x1f DebugFlags
把返回值修改为TRUE
成功绕过检查
1
2{0x81, 0xC1, 0x00, 0x20, 0x80, 0xA1, 0xA1, 0xE0, 0x00, 0x41, 0x40, 0xC0, 0xE1, 0x20, 0x61, 0x61}
{0xB5, 0x7E, 0x54, 0x3D, 0x6D, 0x8A, 0x8A, 0xEF, 0x54, 0x48, 0xAF, 0xCA, 0xF6, 0x3D, 0x45, 0x45}从上面的两组数据可以发现
0x00 -> 0x54
、0x20 -> 0x3d
、0xa1 -> 0x8a
、0x61 -> 0x45
,相同的值变换后的值也相同,猜测可能是代换?多跑几组数据(不用返回重启程序,直接改EIP)
1
2{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}
{0x54, 0x4A, 0x1F, 0x86, 0x5E, 0xA6, 0x00, 0xA3, 0x6F, 0xAA, 0xFE, 0x95, 0xD0, 0x8C, 0xD3, 0xEC}1
2{0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04, 0x0B, 0x0A, 0x09, 0x08, 0x0F, 0x0E, 0x0D, 0x0C}
{0x86, 0x1F, 0x4A, 0x54, 0xA3, 0x00, 0xA6, 0x5E, 0x95, 0xFE, 0xAA, 0x6F, 0xEC, 0xD3, 0x8C, 0xD0}1
2{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}
{0xF0, 0x0D, 0x2F, 0x10, 0x4D, 0x41, 0x51, 0x47, 0xF4, 0x92, 0xD2, 0x15, 0x4C, 0xEB, 0x22, 0xF7}1
2{0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F}
{0x3D, 0x11, 0xCC, 0xD8, 0xBB, 0x35, 0x84, 0x6E, 0x67, 0x77, 0x36, 0x39, 0x07, 0xB7, 0x4F, 0x9F}1
2{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F}
{0x13, 0xD6, 0xF9, 0x3B, 0x24, 0x03, 0xE1, 0xE7, 0x9D, 0x46, 0x02, 0xD7, 0x75, 0x19, 0x83, 0x96}1
2{0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F}
{0xAF, 0x48, 0x05, 0xB8, 0x2A, 0xA5, 0x32, 0xA2, 0xDA, 0x64, 0x16, 0x3A, 0x5A, 0xC8, 0x58, 0x93}1
2{0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F}
{0x01, 0xB3, 0x9B, 0x1D, 0x7F, 0x34, 0x94, 0xF2, 0x65, 0x87, 0xCF, 0x85, 0xF8, 0x7D, 0x27, 0xDF}1
2{0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F}
{0x18, 0x45, 0x99, 0xAB, 0xE2, 0x7A, 0xBF, 0xC5, 0x62, 0x0A, 0x78, 0xED, 0x97, 0x6C, 0x8B, 0x37}1
2{0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F}
{0x29, 0x82, 0x1B, 0x43, 0xBE, 0x63, 0x0E, 0x7B, 0x09, 0x1C, 0xE0, 0xC9, 0x6B, 0x44, 0xD9, 0x55}1
2{0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F}
{0x6D, 0xB5, 0x1A, 0x5B, 0x8E, 0x2E, 0x2D, 0x33, 0xC4, 0x71, 0xFD, 0x21, 0xC7, 0x68, 0xCE, 0xEA}1
2{0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F}
{0x26, 0x0F, 0xA7, 0xFA, 0x1E, 0x04, 0xFC, 0xAD, 0xDC, 0x49, 0x79, 0x38, 0x3E, 0xA0, 0x28, 0xF1}1
2{0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF}
{0xB0, 0x8A, 0xE5, 0x69, 0x40, 0x8D, 0xDB, 0xE4, 0xAC, 0x17, 0xD1, 0x30, 0xC6, 0xE9, 0x57, 0xD4}1
2{0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF}
{0xC3, 0x3F, 0xEE, 0x3C, 0xF3, 0x74, 0xBC, 0xE6, 0xA9, 0x90, 0xB4, 0x8F, 0xB2, 0xCB, 0x89, 0xE3}1
2{0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF}
{0xCA, 0x7E, 0x70, 0xB9, 0x59, 0x61, 0xA1, 0xB6, 0xBA, 0x56, 0x98, 0xC1, 0x14, 0x52, 0x9C, 0xC2}1
2{0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF}
{0x7C, 0xDE, 0xD5, 0x23, 0xAE, 0x0B, 0x66, 0x5F, 0x06, 0x81, 0x50, 0xFB, 0xC0, 0xF5, 0xB1, 0x0C}1
2{0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF}
{0xEF, 0xF6, 0xA4, 0x76, 0x5C, 0x12, 0x6A, 0xBD, 0x31, 0x4E, 0x2B, 0x2C, 0x9A, 0xA8, 0x88, 0x4B}1
2{0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF}
{0x42, 0x08, 0x25, 0x5D, 0x91, 0x73, 0x60, 0xCD, 0x20, 0x9E, 0x53, 0x72, 0x80, 0xE8, 0xDD, 0x00}256个字符跑完,得到完整的代换表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18{
0x54, 0x4A, 0x1F, 0x86, 0x5E, 0xA6, 0x00, 0xA3, 0x6F, 0xAA, 0xFE, 0x95, 0xD0, 0x8C, 0xD3, 0xEC,
0xF0, 0x0D, 0x2F, 0x10, 0x4D, 0x41, 0x51, 0x47, 0xF4, 0x92, 0xD2, 0x15, 0x4C, 0xEB, 0x22, 0xF7,
0x3D, 0x11, 0xCC, 0xD8, 0xBB, 0x35, 0x84, 0x6E, 0x67, 0x77, 0x36, 0x39, 0x07, 0xB7, 0x4F, 0x9F,
0x13, 0xD6, 0xF9, 0x3B, 0x24, 0x03, 0xE1, 0xE7, 0x9D, 0x46, 0x02, 0xD7, 0x75, 0x19, 0x83, 0x96,
0xAF, 0x48, 0x05, 0xB8, 0x2A, 0xA5, 0x32, 0xA2, 0xDA, 0x64, 0x16, 0x3A, 0x5A, 0xC8, 0x58, 0x93,
0x01, 0xB3, 0x9B, 0x1D, 0x7F, 0x34, 0x94, 0xF2, 0x65, 0x87, 0xCF, 0x85, 0xF8, 0x7D, 0x27, 0xDF,
0x18, 0x45, 0x99, 0xAB, 0xE2, 0x7A, 0xBF, 0xC5, 0x62, 0x0A, 0x78, 0xED, 0x97, 0x6C, 0x8B, 0x37,
0x29, 0x82, 0x1B, 0x43, 0xBE, 0x63, 0x0E, 0x7B, 0x09, 0x1C, 0xE0, 0xC9, 0x6B, 0x44, 0xD9, 0x55,
0x6D, 0xB5, 0x1A, 0x5B, 0x8E, 0x2E, 0x2D, 0x33, 0xC4, 0x71, 0xFD, 0x21, 0xC7, 0x68, 0xCE, 0xEA,
0x26, 0x0F, 0xA7, 0xFA, 0x1E, 0x04, 0xFC, 0xAD, 0xDC, 0x49, 0x79, 0x38, 0x3E, 0xA0, 0x28, 0xF1,
0xB0, 0x8A, 0xE5, 0x69, 0x40, 0x8D, 0xDB, 0xE4, 0xAC, 0x17, 0xD1, 0x30, 0xC6, 0xE9, 0x57, 0xD4,
0xC3, 0x3F, 0xEE, 0x3C, 0xF3, 0x74, 0xBC, 0xE6, 0xA9, 0x90, 0xB4, 0x8F, 0xB2, 0xCB, 0x89, 0xE3,
0xCA, 0x7E, 0x70, 0xB9, 0x59, 0x61, 0xA1, 0xB6, 0xBA, 0x56, 0x98, 0xC1, 0x14, 0x52, 0x9C, 0xC2,
0x7C, 0xDE, 0xD5, 0x23, 0xAE, 0x0B, 0x66, 0x5F, 0x06, 0x81, 0x50, 0xFB, 0xC0, 0xF5, 0xB1, 0x0C,
0xEF, 0xF6, 0xA4, 0x76, 0x5C, 0x12, 0x6A, 0xBD, 0x31, 0x4E, 0x2B, 0x2C, 0x9A, 0xA8, 0x88, 0x4B,
0x42, 0x08, 0x25, 0x5D, 0x91, 0x73, 0x60, 0xCD, 0x20, 0x9E, 0x53, 0x72, 0x80, 0xE8, 0xDD, 0x00
}func5
func5运行之后程序退出,无提示,猜测func5内也存在反调试手段
在ctfwiki中查看
ZwSetInformationThread
的相关介绍,发现和当前的情况相似,断点查看是否调用该api发现确实是该api进行的反调试,直接设置EIP到ret处,成功绕过
1
2{0xB5, 0x7E, 0x54, 0x3D, 0x6D, 0x8A, 0x8A, 0xEF, 0x54, 0x48, 0xAF, 0xCA, 0xF6, 0x3D, 0x45, 0x45}
{0xAD, 0xF3, 0xA2, 0xE9, 0x6B, 0x54, 0x54, 0x7F, 0xA2, 0x42, 0x7D, 0x56, 0xB7, 0xE9, 0x2A, 0x2A}1
2{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}
{0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x48, 0x50, 0x58, 0x60, 0x68, 0x70, 0x78}观察发现这一次是整体循环左移3位
根据上面5个函数的分析,可以还原处sub_181780
函数的加密逻辑,逆向求解破解client程序
1 |
|
由于是udp通信,直接抓包收集加密后的数据
server.out
64位elf文件,保留有符号
观察函数表发现有AES加密相关的函数,猜测程序中可能使用了AES进行加解密
main函数就是一个简单的udp通信,接收到buf后由do_login
处理,校验成功返回Congratulations! flag is $(Username){$(Password)}
,校验失败返回wrong password!
,从这里可以知道,用户名应该是L3HSEC
1 |
|
与client相似,do_login中使用4个函数对buf进行处理,完成后将buf后16字节与license校验,校验通过返回0,即登陆成功
1 |
|
同样的,4个处理函数都被vmp保护了
继续动调dump,对aes的几个函数断点
1 |
|
do_login1
处理前,buf中的数据与udp接收到的数据相同
ni跳过do_login1后,发现gdb提示有fork,gdb自动附加到了子进程(do_login1中的反调试被自动绕过)
分析加密前后两组数据,没有明显特征,手动修改buf再跑一遍
1
291 e6 d0 01 53 c3 02 8b 50 f8 f7 c0 e4 f9 97 74
6e 19 2f fe ac 3c fd 74 af 07 08 3f 1b 06 68 8b1
200 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
ff fe fd fc fb fa f9 f8 f7 f6 f5 f4 f3 f2 f1 f0发现刚好是bit反转
do_login2
ni之后程序直接退出了,猜测是调试器被检测到
对exit下断点
程序退出前在exit断下,在堆栈中检查到
TracerPid
字段,因此可以猜测程序是通过检查/proc/self/status
实现的,在ida中确实也发现了该字符串尝试将
TracerPid
patch成Seccomp
(其它为0的字段也可以,为什么不patch文件名或者直接将TracerPid修改成不存在的字段呢?)成功绕过检查
1
26e 19 2f fe ac 3c fd 74 af 07 08 3f 1b 06 68 8b
71 1c 32 01 af 3f 00 77 b2 0a 0b 42 1e 09 6b 8e这一步操作比较简单,对每个字节进行+3
do_login3
ni发现程序在函数内断了下来,前面并没有对该函数内设置断点,查看指令发现代码中有
int3
先不管这个int3,直接c
c之后直接跳到了exit,说明又触发了保护,猜测与这条int3指令有关
nop掉这个int3试试(还是会exit,这里就不写出了)
让gdb忽略该信号
handle SIGTRAP nostop print
(当然也是不行的)程序内部触发int3并且能够正常运行,说明程序自身存在处理SIGTRAP信号的能力,而在上面的调试中,gdb并没有处理该信号,仍然将信号交由程序处理,那么程序是如何检测到调试器的呢?
观察程序的导入函数发现有
gettimeofday
,该api用于获取时间戳对该api断点,查看是否存在调用
ni之后在gettimeofday断下,说明do_login3应该是使用运行时间检测调试器是否存在
直接把gettimeofday的plt给patch成memset,timeval结构体为16字节(除了patch,也可以使用
LD_PRELOAD
链接魔改的gettimeofday)成功绕过
1
271 1c 32 01 af 3f 00 77 b2 0a 0b 42 1e 09 6b 8e
71 d6 90 78 42 d0 50 4d ee 00 fc f5 80 4c 38 8e1
200 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
f0 70 b0 30 d0 50 90 10 e0 60 a0 20 c0 40 80 00可以看出是对16个字节按bit进行了旋转,第一个bit替换到最后一个bit
do_login4
ni之后程序退出,显然还是有反调试,linux反调试当然少不了ptrace
直接patch掉返回0
aes_init断下
aes_key_expansion断下
密钥长度16,密钥的值刚好是buf中的用户名,0填充长度
aes_cipher断下,使用aes将buf后16字节加密
加密后数据
1
271 d6 90 78 42 d0 50 4d ee 00 fc f5 80 4c 38 8e
b9 ea 8d a1 7d 34 11 2f 50 f3 64 64 55 0e df 8fCyberChef跑一下确认是没有魔改的AES
do_login里4个函数分析完,不难写出解密脚本,结合客户端的解密部分
1 |
|
L3HSEC{209611a7-aa51-3314-8119-fbef933a1c8f}