抵御静态分析

对于二进制程序分析,工具都要先进行反汇编,所以要进行抵御,可以对汇编进行特殊处理来干扰工具的分析

花指令

函数头处增加pushfd,popfd和nop指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
;常规的函数头
push ebp
mov ebp,esp
sub esp,0x100

;加上花指令后
push ebp
pushfd
add esp,0xd
nop
sub esp 0xd
popfd
mov ebp,esp
sub esp,0x100

;pushfd和popfd等指令会混淆逆向工具的栈指针解析

插入脏字节并设置跳转

1
2
3
4
5
6
push ebp
jmp addr1 ;跳转到下面以保持正常的运行
db 0xe8 ;脏字节,并且这个0xe8是call指令的起始字节,会让反汇编器认为这里是一条call指令
addr1:
mov ebp,esp
sub esp,0x100

条件跳转来干扰递归下降反汇编器

递归下降反汇编器虽然部分模拟了程序执行的控制流过程,但是并不是真正的运行,不能获取所有的信息,

利用这点插入条件跳转来让其反汇编所有的分支,这样就会导致0xe8被解析为指令,导致错误

1
2
3
4
5
6
7
push ebp
jz addr1
jnz addr1 ;实际上是成为了无条件跳转
db 0xe8
addr1:
mov ebp,esp
sub esp,0x100

例题见逆向做题总结

同时将代码打乱顺序

1
2
3
4
5
6
7
8
9
10
push ebp
jz addr3
jnz addr3 ;实际上是成为了无条件跳转
db 0xe8
addr3:
sub esp,0x100
...
addr2:
mov ebp,esp
jmp addr3

指令替换

将一些指令替换为另一组相同或相似效果的指令来混淆,虽然程序效果没有变化,但是特殊的指令会让反汇编器出现错误

例如call,ret指令会让反汇编器解析出的函数地址范围和调用关系出现错误

1
2
3
4
5
6
7
8
9
10
;call指令
call addr
;可替换为
push addr
ret
;ret 进一步替换为(前提是ecx没有在使用)
push ecx
mov ecx,[esp+4]
add esp,8
jmp ecx

代码自修改(SMC)

SMC技术会对程序特定部分在运行时进行特定的处理,并被使用函数指针来作为代码直接调用,属于动态代码加密技术.

常见于壳类程序中,静态分析时IDA等工具会将处理前的部分解析为数据,导致错误.

SMC中会对.text中某块内存的属性进行修改,然后进行解密等操作,让该部分转换为实际可执行的指令.

修改属性常使用VirtualProtect()函数(windows中)和mprotect()函数(Linux中):

VirtualProtect()

1
2
3
4
5
6
7
8
#include <Memoryapi.h>

BOOL VirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
  • lpAddress:要改变属性的内存起始地址
  • dwSize:要改变属性的内存区域大小
  • flAllocationType:内存新的属性类型
  • lpflOldProtect:内存原始属性类型保存地址

常见的属性类型如下(SMC常用0x40):

image-20240126232046604

mprotect()

1
2
3
#include <sys/mman.h>

int mprotect(void *addr, size_t len, int prot);
  • 该系统调用修改起始位置为addr,长度为length字节的虚拟内存区域中分页上的保护
  • addr地址必须为分页大小的整数倍,length会被向上舍入到系统分页大小的下一个整数倍
    • prot参数是一个位掩码,常见值如下(值分别为1,2,7)
image-20240126232313960

分析方法

动调

下断点调试后,待SMC解密过程结束后,选中包括加密区域的整个函数代码,进行强制分析,然后F5就能得到正确的代码.(例题:[羊城杯 2021]BabySmc)

加密

加密壳程序分为数据加密,代码加密,算法加密

数据加密一般是在合适的时机对程序中已有的数据进行即时的解密

代码加密同理,例如SMC技术

算法加密偏重算法的混淆,模糊与隐藏,例如VM虚拟机保护

反调试

x64dbg和OD使用SharpOD反反调试插件来绕过反调试.