羊城杯 2023 re 复现
羊城杯 2023 re
闲着无事看看老题做做
Ez加密器
必须输入6位验证码,DASCTF{}开头结尾,此外输入的验证码会经过base64编码,似乎大小写也会变
那么其实就是用验证码作为key去加密,这里是DES加密
base64码表:abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+/
发现了这串,是des的特征
那么我们只用爆破验证码去解密即可
中间还对str进行异或了
1 | print('DASCTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}') |
DASCTF{f771b96b71514bb6bc20f3275fa9404e}
CSGO
有反调试,ScyllaHide开启反反调试即可
发现不大对
换表了
主要打那个表操作有一个+11,感觉是从11位开始取,越界再从0开始取试一下直接出了
DASCTF{73913519-A0A6-5575-0F10-DDCBF50FA8CA}
Blast
发现一大串
似乎是md5
程序的逻辑似乎是输入的flag都在这个列表中,那肯定都是单字节的加密成md5,爆破应该是最快的方法
1 |
|
flag{Hello_Ctfer_Velcom_To_my_Mov_and_md5(md5)_world}
vm_wo
看着是个虚拟机,想调试发现是ios的…
0x20D01011903001ALL应该是opcode
1 | [0x1A, 0x00, 0x03, 0x19, 0x01, 0x01, 0x0D, 0x02, 0x07, 0x18, 0x01, 0x02, 0x01, 0x00, 0x03] |
1 | vm_body[0] = 3 |
整理下
1 | vm_body[0] = ((vm_body[0] << 7) | (vm_body[0] >> 1)) ^ 0xEF |
1 | enc = [0xDF,0xD5,0xF1,0xD1,0xFF,0xDB,0xA1,0xA5,0x89,0xBD,0xE9,0x95,0xB3,0x9D,0xE9,0xB3,0x85,0x99,0x87,0xBF,0xE9,0xB1,0x89,0xE9,0x91,0x89,0x89,0x8F,0xAD] |
DASCTF{you_are_right_so_cool}
Babyobfu
main中变成了这样
要么是花指令要么是smc加密了
看着像后者,先分析下汇编
参考了详细的wp
切片式SMC的例题解析及其自动化反混淆 | CN-SEC 中文网
初始化阶段
该阶段主要由两个memset函数(memset(str, c, n)
)构成,主要用0填充了首地址分别为[rbp-47h]
和[rbp-0C4h]
的两个局部变量数组,大小分别为0x1F
和0x7C
,从后续阶段可知两个数组可命名为state
和data
。即:
把一个较大的局部缓冲(124 字节,位于 [rbp-0xC4])清零,作为后续密钥准备/SMC 解密的工作区;同时把 [rbp-0xC8] 这个局部变量置 0 以复用为 memset 的填充值或后续标志。
准备key
该函数通过a1
判断是否进行密钥更改、a2
限制大小,将a4[a3[i]]
与a5
异或。由函数调用前的传参可知,a1
为上一阶段中初始化的state
数组的首字节(根据栈偏移确认索引),a4
为上一阶段中初始化的data
数组(即密钥),a3
为.data段中的一段已知数据,a2
和a5
都是常数。即:
prepare_key 的作用是“按一张索引表对一块缓冲区做定点异或搅动”,把原本清零的栈上缓冲区改造成一个“派生密钥缓冲”。随后,调用者会从这块缓冲区的固定偏移处取出一个32位值,作为第二段 SMC 解密的密钥使用。
a1:模式/开关标志:控制是否进入异或搅动流程,或选择分支。实测场景下为开启搅动的常量值(例如非零)。
a2:计数/长度:指明需要处理的索引数量(循环次数),也就是会从索引表里取前 a2 个索引来改写缓冲区相应位置。
a3:索引表指针:指向一组字节/DWORD 索引的数组;本题中就是全局的 key_index_table(原 unk_406330)。函数用这些索引决定“改写缓冲区的哪些下标”。
a4:目标缓冲区指针:指向调用者栈上的那块工作区(main 里先 memset 清零的 [rbp-0xC4] 开始的 0x7C 字节)。prepare_key 会按索引逐个位置做异或写回。
a5:32 位种子/基键:一个固定常量(本题看到的是 0x7FDCC233)。函数把它拆成 4 个字节,配合索引位置做组合异或,作为“搅动掩码”。
取 buf 内某固定偏移处的 4 字节(在本题里是 [rbp-0xBC])作为 key2;
以 key2 调用第二次 SMC 解密函数,解出函数区(地址来自 [QWORD 0x406980],长度来自 [QWORD 0x406988])。
代码解密阶段
作用:这是题目中的 SMC(自修改代码)解码器。它会把一段内存页临时改成可写/可执行,然后按固定算法逐字节异或,把“加密的代码段”还原成可执行的明文,再在同一地址原地覆盖。还原后,程序流会跳到这段被解出来的代码继续执行。
函数参数(SysV x64 调用约定)
- 第1参 rdi:addr,待解密/修改的代码区首地址
- 第2参 rsi:size,字节长度
- 第3参 rdx:key,32 位解密键(通常作为 uint32 使用,循环取其 4 个字节)
在本题里它被 main 调用两次:第一次解开“中间逻辑”,第二次解开“真正的校验函数”。
要达到全自动的目的,IDAPython脚本需要关注这几个关键点:函数调用的参数获取、SMC的密钥设置与解密、SMC代码的nop与函数重建。
整个反混淆的思路是在指定的函数开头与结尾地址中进行遍历,函数开头搜索到调用_memset
时初始化两个数组,并记录他们的栈偏移;搜索到调用preparekey
时,往上寻找各参数并调用自己复刻的preparekey
进行密钥设置;搜索到调用decrypt_and_modify_code
时,同样往上寻找各参数并调用自己复刻的decrypt_and_modify_code
进行代码解密;搜索到调用smc_encrypt
时,往上寻找各参数并记录参数设置的地址。所有的相关指令都被记录在一个nop数组中,遍历结束以后nop掉这些指令,防止干扰对程序的分析,并重建函数方便反编译。
当然还想到一种方案,就是在程序运行时,动态的获取寄存器里的值,这样就不用去模拟获取key这种操作了
1 | def prepare_key(state, size, idx_arr, data_arr, key): |
patch_byte后的del_items是为了保证这些地址中的字节都是undefined对象,在后续create_insn时能正常创建指令。
create_insn_with_check是我自定义的函数,为的是能保证ea处的字节能被创建指令。
IDAPython的create_insn函数有点小坑,对于undefined的字节,只要这个字节开始反汇编出的指令覆盖到后面已经被定义(explored)的字节,那么就会create_insn失败。而在IDA界面中按快捷键C
(MakeCode)对该字节创建指令,就会直接覆盖后面的explored字节。
所以就会出现按C
能转指令但是create_insn转不了指令的情况。于是这个函数就是为了解决create_insn失败的问题:把后面的字节逐个无条件转成undefined,直到能成功创建ea处的指令。(其实为了鲁棒性还应该检查最后return的时候off==create_insn(ea)的,但没出bug就不管了)
1 | def create_insn_with_check(ea): |
遍历部分就是一个纯粹的顺序遍历反汇编,主要是对汇编指令的特征进行匹配,匹配到对应的关键词进行对应操作。这需要分别对三个smc函数更名以后才会正常匹配,不然脚本啥都匹配不到。
对smc_keyset
和smc_decrypt
,向前遍历并使用正则匹配指令的操作数,获取到参数后调用对应的函数;对初始化阶段的两个memset
,由于一定会在函数的最开头(不然后面解密就没有局部变量可用了),那么匹配到以后就不会再匹配了,并且记录他们创建的局部变量数组的偏移,在smc_keyset
和smc_decrypt
两个函数调用时需要用到这个偏移计算参数;对smc_encrypt
,同样需要向上寻找参数,但只是用于寻找这些添加参数的指令。这些涉及到的指令都会添加进nop的列表g_nop_addrs
里,防止赋值干扰后续分析(需要保证后续使用到这些寄存器时会重新赋值)。
遍历时使用ea += get_item_size(ea)
而不是next_head()
是因为对于后续不是指令的地址(因为后面的是没反混淆的所以必然会出现这种情况),next_head()
会直接跳过,从而影响程序的顺序扫描。
太难了……这个静态分析,逻辑上简单,但是代码实现上非常困难,实战很难打出来
1 | import re |
重建后效果图
1 | g_secret = [0xDEADBEEF, 0xAAC32EF5, 0x3548AC2D, 0xCACDEF2D] |
这个静态方法太搞了,有空回来看看吧切片式SMC的例题解析及其自动化反混淆 | CN-SEC 中文网
动态方法如下:
1 | import idaapi |
留下了很多nop,还是不能F5……
也可以手搓,每次运行可以记录下这里的几个地址然后脚本patch
1 | def set_smc_key(key): |