CM分析-腾讯面试
一、反调试
1、分析
程序启动时会对Zw函数进行拷贝到自身内存中,并将内存加密。实现过掉常规API检测。


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

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

然后对PEB->ProcessHeap->Flags
、PEB->ProcessHeap->ForceFlag
、PEB->BeginDebug
、PEB->NtGlobalFlag
多个标志位进行检查是否存在调试。




除此之外,程序会通过解密自拷贝的Zw函数来进行反调试。
- 通过设置ThreadInformation为
ThreadHideFromDebugger
,将调试器分离实现反调试。






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

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






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


包括文字的渲染。

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



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


2.2.2 替换wow64transition
通过ce查看32位下的syscall。

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

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

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

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

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

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

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

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


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

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

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

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

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

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); }
|

DbgView捕获到的内容

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