首页
社区
课程
招聘
[分享][分享][建议]ctf 中常见混淆与反混淆学习 0x01
发表于: 2024-7-19 23:05 2089

[分享][分享][建议]ctf 中常见混淆与反混淆学习 0x01

2024-7-19 23:05
2089

ctf 中常见混淆与反混淆学习 1(

概念:

  • 阻止逆向分析,在源代码和工具分析处理出的中间代码产生混淆

混淆种类:

  • 花指令
  • smc自解密
  • ollvm混淆
  • 变量名混淆

也可按源码和机器码层面来进行分类:

源码级:

  • 变量名混淆
  • 花指令
  • 自解密

机器码:

  • ollvm混淆
  • mov混淆

在ctf中常见的是花指令,ollvm,自解密,mov混淆

花指令

花指令一般干扰静态分析层面,扰乱汇编代码,让ida反汇编失败

这里说一下在反汇编的过程中,数据与代码的区分问题

在程序中,字节码可能表示指令,也可能表示数据,不同字节码包含的字节数不同,有单字节指令,也有多字节指令。如果首字节是多字节指令,反汇编在确定第一个指令,也就是操作码之后,就会确定该指令是包含多少字节码的指令,然后将这些字节码转化为汇编指令

反汇编算法

  • 线性扫描法(如OD):从入口点或是代码开头,逐条语句进行反汇编,以一条指令的结束作为下一条指令的开始。这样的实现很容易被干扰

列如:

1
2
3
4
jmp label1
db 0xe8     ; 这个字节在跳转后不执行,但线性反汇编器会错误地将其当作指令的一部分
label1:
nop         ; 实际CPU从这里开始运行

在上面的代码中,当 jmp label1 被执行后,CPU 实际上会跳转到 label1 执行 nop 指令。

然而,线性反汇编器会继续从 0xe8 处分析,误将数据字节当作指令进行反汇编,导致大量指令反汇编错误。

  • 递归下降(如ida):对代码的执行路径进行分析,当解码出分支指令后,把这个分支指令的地址记录下来,并反汇编各个分支中的指令,可以有效避免将数据作为指令解析,但也会被干扰

列如:

1
2
3
4
5
jz  label1
jnz label1
db 0xe8     ; 干扰字节
label1:
nop         ; 正常指令

由于jz和jnz都存在理论上的连续向下执行分支,所以ida仍然会优先反汇编干扰字节,导致反汇编出错,也就是jz和jnz一起使用,产生了jmp一样的效果。

花指令原理

向正常的程序里面嵌入数据块或者指令,达到反汇编器分析失败的目的。

常见花指令分类:

  • jz,jnz:用两条相反的条件跳转语句,使条件跳转变为跳转
  • call:用pop的方式来清除cal的压栈,使栈平衡。从而用call实现jmp,让ida认为cal的目标地址为函数起始地址,导致函数创建错误

下面是几种的花指令的实战:(采用的AT&T语法)

1
2
3
4
5
6
        __asm__ (
    "xor %eax, %eax\n"   // 将EAX寄存器清零
    "jz 1f\n"            // 如果EAX为零,跳转到1标签
    ".byte 0xe8\n"       // 在代码段中插入字节0xE8(即call的字节码)
    "1:\n"               // 标签1
);
1
2
3
4
5
6
__asm__ (
    "xor %eax, %eax\n"    // 将EAX寄存器清零
    "jz 1f\n"             // 如果EAX为零,跳转到1标签
    ".byte 0xeb\n"        // 在代码段中插入字节0xEB(jmp 的字节码)
    "1:\n"                // 标签1
); #这一种花指令,对od来说是无效的,因为od是递归扫描

但可以创建一个指向无效的数据地址,来迷惑od

1
2
3
4
5
6
7
8
9
__asm__ (
    "xor %eax, %eax\n"    // 将EAX寄存器清零
    "jz 1f\n"             // 如果EAX为零,跳转到标签1
    "jnz 2f\n"            // 动态实际不执行此语句,只是在给静态分析增加迷惑性
 
    "2:\n"                // 标签2
    ".byte 0xEB\n"        // 在代码段中插入字节0xEB(jmp的字节码)
    "1:\n"                // 标签1
);

还有一类push的,大量使用可以让程序四分五裂,但这类花指令的使用时要涉及retn,需要保证esp栈顶指针值不紊乱,故在ctf中不是很常见,这里不多加讨论

花指令解决

对于不影响程序运行的花指令,nop即可。

而有一类花指令,对于程序起连结作用的,可以改为其他工具可识别指令

SMC 代码自修改

原理

  • 在执行某一段代码时,程序会对自身的该段代码进行自修改,只有在修改后的代码才是可汇编、可执行的。
  • 在程序未对该段代码进行修改之前, 在静态分析状态下, 均是不可读的字节码,IDA之类的反汇编器无法识别程序的正常逻辑。

SMC的实现是需要对目标内存进行修改的,.text一般是没有写权限的。那么就需要拥有修改目标内存的权限:

在linux系统中,可以通过mprotect函数修改目标内存的权限
在Windows系统中,VirtualProtect函数实现内存权限的修改
因此也可以观察是否有这俩个函数来判断是否进行了SMC。

smc实操

  1. 先写出可运行的程序,包括加密逻辑。
  2. 得到可执行程序后,借助ida、OD等逆向工具,导出待加密代码段或待加密函数的字节码。
  3. 通过设定好的SMC加密算法,加密上一步骤导出字节码,得到字节码密文。
  4. 在可运行程序的源代码中添加字节码密文,并在调用SMC加密函数前,使用SMC加密字节码的逆算法解密代码段,然后通过函数指针的方式进行调用。

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
 
// 示例加密字节码,这里用实际函数的机器码进行加密
char enc_bytecode[] = {0xEB, 0x0E, 0x5E, 0xC6, 0x46, 0x01, 0x65, 0xC6, 0x46, 0x02, 0x66, 0xB0, 0x01, 0xB4, 0x4C, 0xCD, 0x21, 0xB4, 0x4C, 0xCD, 0x21};
 
int main() {
    int (*Check)(char*, char*); // 函数指针
    char reg1[] = "test1";
    char reg2[] = "test2";
 
    // SMC 解密块,这里以异或加密的 SMC 为例
    for (int i = 0; i < sizeof(enc_bytecode); i++) {
        enc_bytecode[i] ^= 0x12;
    }
 
    // 通过函数指针的方式调用
    Check = (int(*)(char*, char*))&enc_bytecode;
    Check(reg1, reg2);   // 调用该函数
 
    return 0;
}

smc处理

静态分析时可以ida python直接patch,也可以动态调试让程序自解密再dump

收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 1306
活跃值: (1485)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
天堂之门呢
2024-7-21 13:00
0
游客
登录 | 注册 方可回帖
返回
//