逆向学习-SMC和花指令

花指令

动态保护:

反调试、反虚拟机

静态保护:

花指令、加密、加壳、混淆

反汇编原理:

线性扫描

递归行进算法

增加__declspec(naked),汇编指令只有这些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__declspec(naked)
void test() {

__asm {


push eax
xor eax, eax
test eax, eax
jmp label1
__emit 0xe9
label1:
pop eax
mov ebx, 1
add eax, ebx
ret

}
}

0xe9是跳转指令,__emit 0xe9强行插入,在线性扫描时会错误识别,起到静态保护作用,递归行进算法能正常识别

成果骗过VS

image-20240911191900217

IDA递归行进算法未骗过

image-20240911192213690

IDA为递归行进算法,会到不同的分支,使用jz、jnz成功骗过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__declspec(naked)
void test() {

__asm {


push eax
xor eax, eax
test eax, eax
jz label1
jnz label1
__emit 0xe9
label1:
pop eax
mov ebx, 1
add eax, ebx
ret

}
}

image-20240911192006290

实际运行,CPU永远不会来到这些错乱的地方

jz花指令,必定为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__declspec(naked)
void test() {

__asm {


push eax
xor eax, eax
test eax, eax
jz label1
__emit 0xe9
label1:
pop eax
mov ebx, 1
add eax, ebx
ret

}
}

call

执行call指令需要将EIP压栈(EIP 寄存器保存着下一个要执行的指令的内存地址。换句话说,它指向 CPU 当前正在执行的指令序列中的下一个指令的位置。)

(ESP指向栈顶)

add dword PTR [esp], 7 ,栈顶+7,直接指向add eax,5

2+5

ret和call对应

call:压栈+跳转

ret:出栈+跳转(栈顶数据弹出,跳转执行)

在第一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__declspec(naked)
void test2() {

__asm {

call label2
__emit 0xE9
label2:
add dword PTR [esp], 7
#第一个字节:操作码 C2 第二个字节:位移量,即 1 的编码,通常表示为 0x01
ret
#__emit 0xE9 操作码,__emit 占 1 个字节。紧随其后的位移量,占 4 个字节。总共,__emit 0xE9 插入的机器码占 5 个字节。
__emit 0xE9

add eax, 5
ret

}
}

地址计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
section .text
global _start

_start:
; 假设程序入口点的地址为 0x08048000
push ebp ; 指令长度 1 字节
mov ebp, esp ; 指令长度 3 字节
sub esp, 4 ; 指令长度 3 字节
call some_func ; 指令长度 5 字节
add esp, 4 ; 指令长度 3 字节
pop ebp ; 指令长度 1 字节
ret ; 指令长度 1 字节

some_func:
; 函数体
ret ; 指令长度 1 字节


在这个例子中,程序的入口点 _start 的地址假设为 0x08048000。那么每个指令的地址如下:

push ebp 的地址是 0x08048000
mov ebp, esp 的地址是 0x08048001(因为 push ebp 占用了 1 字节)
sub esp, 4 的地址是 0x08048004(因为 mov ebp, esp 占用了 3 字节)
call some_func 的地址是 0x08048007(因为 sub esp, 4 占用了 3 字节)
add esp, 4 的地址是 0x0804800C(因为 call some_func 占用了 5 字节)
pop ebp 的地址是 0x0804800F(因为 add esp, 4 占用了 3 字节)
ret 的地址是 0x08048010(因为 pop ebp 占用了 1 字节)
这些地址是由 EIP 或 RIP 在每次执行指令后自动递增的结果。每条指令执行后,EIP 或 RIP 会指向紧接着的一条指令的起始地址

0XE8也可以代替0xE9

去除花指令手动:

IDA反汇编右键 undefined 快捷键U

正确入口右键code 快捷键C

自动去除:

把垃圾字节替换NOP 一个字节的0XE9

SMC

SMC:

Self modifying Code

代码自修改

在一段代码执行前,对其修改,一般用来加密核心功能逻辑,加壳技术基础 隐藏核心功能代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// SMCTest.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <windows.h>

__declspec(naked)
void important_fun() {
__asm {

mov eax, 0x7657FD1E
push 0
push 0x6E617579
push 0x6E617578
mov ebx, esp

push 0
push ebx
push ebx
push 0
call eax
add esp, 0x0C

ret
}
}

void important_fun2() {
printf("hello, world!\n");
}


void encrypt_fun() {
unsigned char encryped_code[32] = {0};
unsigned char* ptr = (unsigned char*)important_fun;
for (int i = 0; i < 31; i++) {
encryped_code[i] = ptr[i] ^ 0x11;
printf("0x%02X, ", encryped_code[i]);
if ((i + 1) % 8 == 0) {
printf("\n");
}
}

printf("\n======================================\n");

for (int i = 0; i < 31; i++) {
printf("0x%02X, ", ptr[i]);
if ((i + 1) % 8 == 0) {
printf("\n");
}
}
}


void decrypt_fun(PVOID address) {
unsigned char* ptr = (unsigned char*)address;
for (int i = 0; i < 31; i++) {
ptr[i] = ptr[i] ^ 0x11;
printf("0x%02X, ", ptr[i]);
if ((i + 1) % 8 == 0) {
printf("\n");
}
}
}


unsigned char g_code[] = {
0xA9, 0x0F, 0xEC, 0x46, 0x67, 0x7B, 0x11, 0x79,
0x68, 0x64, 0x70, 0x7F, 0x79, 0x69, 0x64, 0x70,
0x7F, 0x9A, 0xCD, 0x7B, 0x11, 0x42, 0x42, 0x7B,
0x11, 0xEE, 0xC1, 0x92, 0xD5, 0x1D, 0xD2
};


void smc_test() {

PVOID codePage = VirtualAlloc(NULL, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (codePage) {
ZeroMemory(codePage, 4096);
memcpy(codePage, g_code, sizeof(g_code));
decrypt_fun(codePage);

__asm {
call codePage

}
}
}

int _tmain(int argc, _TCHAR* argv[])
{
MessageBoxA(NULL, "start", "start", MB_OK);
//important_fun();
encrypt_fun();

//smc_test();

//important_fun2();
return 0;
}


思考

1、如何自动去除花指令

2、如何用汇编实现调用messageboxA

https://xz.aliyun.com/t/14580?time__1311=GqAhDKYK7Iox%2FtXeBKGQDktG8DgiT9tSYIeD

逆向题

image-20240912093831338

IDA静态分析:

跟进数组,转换为array

image-20240912101452068

image-20240912101949184

image-20240912102012608

image-20240912102041495

image-20240912102119320

image-20240912102129454

将字节码复制出来,使用插件

https://github.com/P4nda0s/LazyIDA

插件报错修改

image-20240912110346721

选中转换convert

image-20240912110929475

1
A1CE0256215723CA11EB71904C749A23222222AA760629AA76062DAA760634A976060E75914311DDAA66063EAA6E063FAA6E063CAA6E063DAA6E0602AA660603AA6E0600AA6E0601AA6E0606AA6E0607AA660604AA6E0605AA6E060AAA6E060BAA6E0608AA660609E466062E5AE466062F57AA7E062CE46606325BE466063357AA7E0630E46606360FE466063758E46606344AE46606354BE466063A44E466063B47E466063945A9D01A28562F21D221DA1A2C57DAA1DD32562B7D7C10E279A1E602E17211E2A7E25623CB7AAF56062EAF5E063E09D009D877C921AF6B222D9C3E342D9C0809C92D943E3519C9573321EA21F2A1DB325EC47F7D7C79A1E602E1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

convert hex string

Winhex => 右键编辑=> 剪切板数据 => 写入

image-20240912111755390

这种直接在IDA读取的字节码是经过异或加密的需要先进行解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// shellcode解密.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <stdio.h>

unsigned const char byte_403018[] = {
0xA1, 0xCE, 0x02, 0x56, 0x21, 0x57, 0x23, 0xCA, 0x11, 0xEB, 0x71, 0x90, 0x4C, 0x74, 0x9A, 0x23,
0x22, 0x22, 0x22, 0xAA, 0x76, 0x06, 0x29, 0xAA, 0x76, 0x06, 0x2D, 0xAA, 0x76, 0x06, 0x34, 0xA9,
0x76, 0x06, 0x0E, 0x75, 0x91, 0x43, 0x11, 0xDD, 0xAA, 0x66, 0x06, 0x3E, 0xAA, 0x6E, 0x06, 0x3F,
0xAA, 0x6E, 0x06, 0x3C, 0xAA, 0x6E, 0x06, 0x3D, 0xAA, 0x6E, 0x06, 0x02, 0xAA, 0x66, 0x06, 0x03,
0xAA, 0x6E, 0x06, 0x00, 0xAA, 0x6E, 0x06, 0x01, 0xAA, 0x6E, 0x06, 0x06, 0xAA, 0x6E, 0x06, 0x07,
0xAA, 0x66, 0x06, 0x04, 0xAA, 0x6E, 0x06, 0x05, 0xAA, 0x6E, 0x06, 0x0A, 0xAA, 0x6E, 0x06, 0x0B,
0xAA, 0x6E, 0x06, 0x08, 0xAA, 0x66, 0x06, 0x09, 0xE4, 0x66, 0x06, 0x2E, 0x5A, 0xE4, 0x66, 0x06,
0x2F, 0x57, 0xAA, 0x7E, 0x06, 0x2C, 0xE4, 0x66, 0x06, 0x32, 0x5B, 0xE4, 0x66, 0x06, 0x33, 0x57,
0xAA, 0x7E, 0x06, 0x30, 0xE4, 0x66, 0x06, 0x36, 0x0F, 0xE4, 0x66, 0x06, 0x37, 0x58, 0xE4, 0x66,
0x06, 0x34, 0x4A, 0xE4, 0x66, 0x06, 0x35, 0x4B, 0xE4, 0x66, 0x06, 0x3A, 0x44, 0xE4, 0x66, 0x06,
0x3B, 0x47, 0xE4, 0x66, 0x06, 0x39, 0x45, 0xA9, 0xD0, 0x1A, 0x28, 0x56, 0x2F, 0x21, 0xD2, 0x21,
0xDA, 0x1A, 0x2C, 0x57, 0xDA, 0xA1, 0xDD, 0x32, 0x56, 0x2B, 0x7D, 0x7C, 0x10, 0xE2, 0x79, 0xA1,
0xE6, 0x02, 0xE1, 0x72, 0x11, 0xE2, 0xA7, 0xE2, 0x56, 0x23, 0xCB, 0x7A, 0xAF, 0x56, 0x06, 0x2E,
0xAF, 0x5E, 0x06, 0x3E, 0x09, 0xD0, 0x09, 0xD8, 0x77, 0xC9, 0x21, 0xAF, 0x6B, 0x22, 0x2D, 0x9C,
0x3E, 0x34, 0x2D, 0x9C, 0x08, 0x09, 0xC9, 0x2D, 0x94, 0x3E, 0x35, 0x19, 0xC9, 0x57, 0x33, 0x21,
0xEA, 0x21, 0xF2, 0xA1, 0xDB, 0x32, 0x5E, 0xC4, 0x7F, 0x7D, 0x7C, 0x79, 0xA1, 0xE6, 0x02, 0xE1
};

unsigned char decrypt_code[300] = {0};


int main()
{
FILE* shellcode = fopen("jiemi.exe","wb+");

for (int i = 0; i < sizeof(byte_403018); ++i)
{
decrypt_code[i] = byte_403018[i] ^ 0x22;

fputc(decrypt_code[i], shellcode);

printf("%2X ", (unsigned int)decrypt_code[i]);

if (i % 16 ==15)
{
printf("\n");
}



}
fclose(shellcode);
return 0;
}


使用IDA打开二进制exe,发现E8疑似花指令

image-20240912113744806

对E8字节码使用undefined,33字节码使用code

image-20240912132312014

无法F5翻译成伪代码,对汇编进行人工翻译

image-20240912142206786

栈为先进先出,所以存在字符串

xuanyuan-zhifeng

后面IDA有点分析不下去了,使用x32dbg进行动态分析

使用字符串查找定位到flag

image-20240913104029726

image-20240913104942890

第一个关键点比较输入字符长度是否等于16

image-20240913105514741

关键点2:

我输入的为16个1

ebx=x ebp=1

1-x

ebx=1

image-20240913133836887

又将1-x和1进行比较,也就是我们输入-真正的字符=1进行比较,如果不相等程序直接会崩溃

image-20240913134026053

接下来第一个字符输入y满足条件,继续看程序运行,debug到这个位置,有个cmp ecx,10是个循环,换算10进制为16次

image-20240913135150861

第二次是我输入的1和u相减,最好和0比较,若不等则程序崩溃

image-20240913135325542

再次输入flag=yu11111111111111,找到了比较规律,比较的就是上面这段数组

1
ds:[edi + edx] = 0 + 0095FB1A = 0095FB1A

image-20240913140546567

image-20240913140640198

规律为输入字符按顺序减去xuanyuan-zhifeng的字母,结果和数组中的数组进行对比

1
2
3
4
5
6
x u a n y u a n - z h i f e n g
1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
y u a n y v a n - z i i f e n h

答案:
yuanyvan-ziifenh

image-20240913140930456

__END__