天龙八部之KCTF
乔峰,你好大的胆子!胆敢谋害少林寺方丈?!为救阿朱,在你面前的有俩条路,一是打服所有人,二是自尽。
阅读E-Debug源码,发现如表 1所示代码,功能为检测是否为易语言静态编译程序,将程序当中的相应特征抹掉即可完成Anti E-Debug。
duint eMagicHead = Script::Pattern::FindMem(codeBase, codeSize, "50 64 89 25 00 00 00 00 81 EC AC 01 00 00 53 56 57");
if (ReadUInt(lpFirstLibAddr) == 0x1312D65) {
// ->检测到易语言静态编译程序
}
表 1
其中 "50 64 89 25 00 00 00 00 81 EC AC 01 00 00 53 56 57"特征码取自函数“krnln_?LibInitUserProgram@CKrnlApp”,溯源流程如图 1所示。关于符号哪里来,写在附录OBJ(编译中间文件)。
图 1
另一特征码“0x1312D65”(652D3101)取自libinfo0,该字段归属于libinfos,如图 2所示。此外,还有lib0_guid、lib0_name字段可用于检测其是否为易语言程序。
图 2
在抹掉相应的特征后,可能会出现强制解析绕过反调试的情况。对此,通过重载易语言的内核(核心支持库部分),达到E-Debug失效的目的。同时,也可借此机会,进一步扩大防守空间。
重载流程如图 3所示,先尝试还原krnln_static.lib相关函数,复现一份后使用llvm-cl生成e_plug_overload_lib.lib文件。随即篡改obj文件,抹掉原函数特征并跳转到e_plug_overload_lib所对应的重载函数。最后通过修改link的参数链接e_plug_overload_lib达到重载易语言内核的目的。
图 3
编写一段测试代码,基于COFFI库完成,代码内容如图 4所示。功能是HOOK掉OBJ文件的ecode函数。程序编译结果如图 5所示,可以看到ecode直接跳转到了“_krnle_test_test_test”函数。程序运行结果如图 6所示,可以看到是一段具有llvm特征的代码。
图 4
图 5
图 6
接下来列出本次赛题所重载的函数及对应关系。
符号
重载后的符号
_krnln_fnXOR
_bit_xor_operate
_krnln_fnSHL
_bit_shl
_krnln_fnRnd
_m_krnln_fnRnd
_krnln_fnSHR
_m_krnln_fnSHR
_krnln_fnLen
_m_krnln_fnLen
_krnln_fnBinLen
_m_krnln_fnBinLen
_krnln_fnMod
_m_krnln_fnMod
_krnln_fnBAnd
_m_krnln_fnBAnd
表 2
编写编译时混淆首先需要先解析语法树,解析过程参考易语言编译器逆向一文中的前缀表达式。1000行左右的代码即可解析易语言的大多数控制流。解析效果如图 7所示,由于笔者时间不够了,哈哈,所以没实现。
图 7
Crypto:
.版本 2
' 计算flag字节和
sum = 0
.计次循环首 (32, i)
sum = sum + flag [i]
.计次循环尾 ()
' 计算随机偏移(简化FS寄存器读取为0)
rand_offset = box_size + sum % (1024 - 2 × box_size)
g_rand_offset = rand_offset
' 用随机数填充buf
.计次循环首 (1024, i)
buf [i] = 取随机数 (0, 255)
.计次循环尾 ()
' 初始化vec_ord [0-255]
.计次循环首 (256, i)
vec_ord [i] = i - 1
.计次循环尾 ()
' Fisher-Yates洗牌算法
.计次循环首 (256, i)
j_idx = 取随机数 (1, i)
temp = vec_ord [i]
vec_ord [i] = vec_ord [j_idx]
vec_ord [j_idx] = temp
.计次循环尾 ()
' 初始化pbox_arr
.计次循环首 (256, i)
pbox_arr [vec_ord [i] + 1] = 255 - vec_ord [i]
.计次循环尾 ()
' 2025轮置换
p = 0
.计次循环首 (2025, round)
.计次循环首 (256, i)
p = (p + pbox_arr [i] + round - 1 + key [(i - 1) % key_size + 1]) % 256
.如果真 (p + 1 = i) ' 跳过交换
到循环尾 ()
.如果真结束
' 交换 pbox_arr[i] 和 pbox_arr[p+1]
temp = pbox_arr [i]
pbox_arr [i] = pbox_arr [p + 1]
pbox_arr [p + 1] = temp
.计次循环尾 ()
.计次循环尾 ()
' 将pbox_arr保存到buf的随机偏移位置
.计次循环首 (256, i)
buf [g_rand_offset + i] = pbox_arr [i]
.计次循环尾 ()
' 复制输入到输出
.计次循环首 (32, i)
output_arr [i] = input [i]
.计次循环尾 ()
' DWORD异或操作(4字节一组)
.计次循环首 (8, i) ' 32/4=8组
value = 0
.计次循环首 (4, j)
k = (i - 1) × 4 + j
value = value + 左移 (output_arr [k], (j - 1) × 8)
.计次循环尾 ()
key_byte = key [(i - 1) % 8 + 1]
value = 位异或 (value, key_byte)
' 分解DWORD回字节
.计次循环首 (4, j)
k = (i - 1) × 4 + j
output_arr [k] = 位与 (右移 (value, (j - 1) × 8), 255)
.计次循环尾 ()
.计次循环尾 ()
' 114514次混淆操作
.计次循环首 (114514, i)
.计次循环首 (32, j)
.如果 (j % 2 = 1) ' 奇数索引(易语言索引从1开始)
.如果 (j > 1)
output_arr [j] = 位异或 (output_arr [j], output_arr [j - 1])
.否则
' 索引1无前一个字节,跳过
.如果结束
.否则
output_arr [j] = buf [g_rand_offset + output_arr [j] + 1]
.如果结束
.计次循环尾 ()
.计次循环尾 ()
' 转换回字节集
.计次循环首 (32, i)
output [i] = output_arr [i]
.计次循环尾 ()
返回 (到字节集 (output))
表 3
DeCrypto:
.版本 2
' 从buf加载pbox
.计次循环首 (256, i)
pbox_arr [i] = buf [g_rand_offset + i]
.计次循环尾 ()
' 构建逆pbox映射
.计次循环首 (256, i)
inv_pbox [pbox_arr [i] + 1] = i - 1
.计次循环尾 ()
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2025-8-27 12:08
被kanxue编辑
,原因: