CM分析-腾讯面试

CM分析-腾讯面试

一、反调试

1、分析

程序启动时会对Zw函数进行拷贝到自身内存中,并将内存加密。实现过掉常规API检测。

document_image_rId4

document_image_rId5

然后对BeginDebugForceFlags标志位进行检测,如果根据是否进行调试来设置自定义值。

document_image_rId6

执行到MFC入口后会首先判断Wow64Transition是否被hook。

document_image_rId7

然后对PEB->ProcessHeap->FlagsPEB->ProcessHeap->ForceFlagPEB->BeginDebugPEB->NtGlobalFlag多个标志位进行检查是否存在调试。

document_image_rId8

document_image_rId9

document_image_rId10

document_image_rId11

除此之外,程序会通过解密自拷贝的Zw函数来进行反调试。

  • 通过设置ThreadInformation为ThreadHideFromDebugger,将调试器分离实现反调试。

document_image_rId12

document_image_rId13

  • 通过查询调试对象来判断是否进行调试。

document_image_rId14

document_image_rId15

document_image_rId16

document_image_rId17

  • 调试时默认会有一个调试对象,程序尝试创建一个调试对象后获取,如果获取不到则表明处于调试状态。

document_image_rId18

  • 通过传递非法指针地址引发0xC0000005 异常,如果执行后返回的异常码不正确则表明调试器接管了异常处理,即存在调试。

document_image_rId19

document_image_rId20

  • 常规API检测反调试。

document_image_rId21

document_image_rId22

  • 查询系统信息

document_image_rId23

  • 引发异常检测调试

document_image_rId24

其中,反调试检测大多在窗口消息中。

document_image_rId25

document_image_rId26

包括文字的渲染。

document_image_rId27

2、反反调试

2.1、Patch

通过nop 反调试函数方式或者修改返回值的方式的patch实现过掉反调试。

document_image_rId28

document_image_rId29

document_image_rId30

2.2、Hook Zw

2.2.1 EAT

在程序启动初期,快速将ntdll.dll的导出函数进行替换后自写分发过滤。

document_image_rId31

document_image_rId32

2.2.2 替换wow64transition

通过ce查看32位下的syscall。

document_image_rId33

可以将mov edx,xxxxxx的硬编码20 8f 87 77 修改为自己的函数地址,然后通过eax的值判断函数后进行自定义处理。

document_image_rId34

由于CM会检测wow64transition头部是否为0xE9(默认 FF 25 xxxxxxx)

document_image_rId35

因此自定义函数的头字节不能为0xe9。但在实现后测试的环节中发现部分Zw函数调用时崩溃,原因也暂未查找,因此该方法只作为一个额外的思路。

二、注册机

1、算法分析

document_image_rId36

Win32获取EditBox函数大多是GetWindowText,但是在ida中交叉引用发现并没有什么有价值的东西。但是在导入表看到OutputDebugStringA,然后尝试打开了DbgView看看能不能捕获到内容。

document_image_rId37

发现当用户名和注册码输入长度较长时,点击注册有输出,但该输出并非正确的注册码。猜测上层有对注册逻辑处理,交叉引用后发现上层代码无法进行伪代码生成。

document_image_rId38

简单分析后发现该段代码存在异常处理和花指令。

document_image_rId39

在异常地址0x401B86和异常函数0x0415890下断点后Shift+F9跳转到自定义异常跟踪分析后,发现该处最终的跳转结果为0x401BA1。

document_image_rId40

对代码进行修复后且nop无效代码后,ida已经可以进行伪代码生成。

document_image_rId41

document_image_rId42

注册算法为从地址为0x401BA9处循环执行0x960次,每次以四字节大小读取地址数据后与初始值的Key(0x19820714),进行异或运算得到最后一个校验值。然后生成三个固定常量表。

document_image_rId43

紧接着获取用户名和注册码对应EditBox的内容。

document_image_rId44

其中会判断用户名长度是否为8,注册码长度是否为24。

document_image_rId45

最后将用户名和上边生成的三个表、Key进行计算注册码。

document_image_rId46

计算完毕后会执行函数OutputDebugStringA将计算出来的注册码输出,但输出的方式为将计算出的注册码的头和尾字符各上升一个字符,及”0”->”1”。

document_image_rId47

2、注册机实现

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
#include <stdio.h>
#include <Windows.h>

namespace MemTools {

HANDLE m_hProcess;
DWORD m_pid = NULL;

void Attach(DWORD pid)
{
m_pid = pid;
if (m_hProcess)
CloseHandle(m_hProcess);
m_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
}

template<typename T>
T ReadMem(DWORD addr)
{
if (!m_hProcess)
return T();
T value{};
ReadProcessMemory(m_hProcess, (LPVOID)addr, &value, sizeof(T), NULL);
return value;
}
}

int main() {

int key = 0x19820714;
HWND hWnd = FindWindowA(NULL,"CrackMeDemo");
DWORD pid{};
GetWindowThreadProcessId(hWnd, &pid);
MemTools::Attach(pid);

for (int i = 0; i < 0x960; i++)
key ^= MemTools::ReadMem<DWORD>(0x401ba9 + i*4);

char v20[27] ="0";
char v26[27] = "A";
char v18[27] = "a";

for (int j = 1; j < 26; ++j) // 生成表
{
v20[j] = v20[j - 1] + 1;
v26[j] = v26[j - 1] + 1;
v18[j] = v18[j - 1] + 1;
}

char user[] = "test1234";
char pwd[1024]{};
if (strlen(user) != 8)
return 0;

for (int k = 0; k < 8; k++)
{
user[k] ^= k;
user[k] ^= MemTools::ReadMem<BYTE>(0x401BB9 +k);
user[k] ^= MemTools::ReadMem<BYTE>(0x401BC9 +k);
}
*(DWORD*)(user) ^= key;
*(DWORD*)&user[4] ^= key;

for (int m = 0; m < 8; m++)
{

unsigned __int8 v8 = (unsigned __int8)(user[m] & 0xE0) / 32;
unsigned __int8 v6 = (user[m] & 0x1c) / 4;
unsigned __int8 v7 = user[m] & 3;
if (m % 3 == 2)
{
pwd[m * 3] = v20[v7];
pwd[m * 3 + 1] = v26[v8 + 8];
pwd[m * 3 + 2] = v18[v6 + 16];
}
if (m % 3 == 1)
{
pwd[m * 3] = v26[v8 +16];
pwd[m * 3 + 1] = v18[v6 + 8];
pwd[m * 3 + 2] = v20[v7];
}
if (!(m % 3))
{
pwd[m * 3] = v26[v6 +16];
pwd[m * 3 + 1] = v18[v7 + 8];
pwd[m * 3 + 2] = v20[v8];
}
}
printf("%s\n",pwd);
return(0);
}

document_image_rId48

DbgView捕获到的内容

document_image_rId49

将头尾各降一个字符得到的内容与注册机一致。

Uk4Xm30PtWj6Vl13LtRk2Xp1 -> Tk4Xm30PtWj6Vl13LtRk2Xp0