菁英班游戏安全第一次作业

第一次作业

1.分析并调试程序,找到flag

使用技术:IDA动态调试crackme.exe

前置知识点

步骤

1.找到文件夹IDA目录下的dbgsrv文件夹,找到对应版本的win_32或者64remote.exe,点击运行。

2.打开ida,将exe拖入,Debugger → Select a debugger → Remote windows debugger

3.Debugger → process options → 填写三行(第一,二行为 文件的路径,第三行为空,也可设置为文件对应的根目录) 和 Hostname设为127.0.0.1(代表本机) → ok

4.附加程序:先运行我们要调试的软件,然后Debugger → Attach to process… → 选中我们要调试的程序 → attach

快捷键

1.F2下断点
2.F7,f8单步步入
3.N重名
4.g跳到地址和函数名
5.u取消把函数汇编变成机器码
6.c就是把机器码变成汇编
7.F5
8.p分析函数,把机器码那些东西翻译成函数
9.ctrl+s看见系统所有的模块
10.ctrl+f搜索
11.单步调试注意右上角,寄存器变蓝色表示被改了
12.otions->number of opcode bytes可以查看机器码,填入4一行看4个机器码
13.在hex view-1按F2可以修改机器码,再次按F2确定修改
14.alt+g看是thumb还是arm指令
15.在函数名上按X可以看见上层调用
16.在f5伪c/c++代码的情况下,注释是/,汇编情况下注释是;
17.f4移动到光标处
18.在寄存器窗口按E可以修改寄存器的值
19.在内存窗口f2可以修改内存的值

ctrl+F7:运行至函数结束,用来跳出函数

ctrl+F2:重新开始调试

关于本题

  • 进入IDA动态debug,打断点到sub_7FF755DA14D0()发现这个函数进行了printf(‘请输入flag:’)操作
  • 猜测下一个函数就进行flag输入的验证,即sub_7FF755DA1020(&v34);
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
sub_7FF755DA1020(&v34);
CreateThread(0i64, 0i64, StartAddress, 0i64, 0, 0i64);
sub_7FF755DA1070(&unk_7FF755DC5600, v37);
v35[0] = xmmword_7FF755DC5618;
v36 = 85;
v35[1] = xmmword_7FF755DC5628;
if ( v1 < 2 )
goto LABEL_9;
v15 = (char *)v35 + 4;
v16 = _mm_load_si128((const __m128i *)&xmmword_7FF755DC5670);
v17 = _mm_load_si128((const __m128i *)&xmmword_7FF755DC56C0);
v18 = _mm_load_si128((const __m128i *)&xmmword_7FF755DC5690);
v34 = _mm_load_si128((const __m128i *)&xmmword_7FF755DC56B0);
v19 = v34.m128i_i32[0];
v20 = _mm_cvtsi32_si128(5u);
v21 = _mm_cvtsi32_si128(0x1Fu);
do
{
v15 += 8;
v22 = _mm_add_epi32(_mm_shuffle_epi32(_mm_cvtsi32_si128(v0), 0), v16);
v23 = _mm_add_epi32(_mm_shuffle_epi32(_mm_cvtsi32_si128(v0 + 4), 0), v16);
v0 += 8;
v24 = _mm_sra_epi32(
_mm_add_epi32(
(__m128i)_mm_shuffle_ps(
(__m128)_mm_mul_epi32(_mm_unpacklo_epi32(v22, v22), v17),
(__m128)_mm_mul_epi32(_mm_unpackhi_epi32(v22, v22), v17),
221),
v22),
v20);
v25 = _mm_and_si128(
_mm_shuffle_epi32(
_mm_shufflehi_epi16(
_mm_shufflelo_epi16(
_mm_sub_epi32(v22, _mm_mullo_epi32(_mm_add_epi32(_mm_srl_epi32(v24, v21), v24), v18)),
216),
216),
216),
(__m128i)xmmword_7FF755DC56A0);
*((_DWORD *)v15 - 3) = _mm_cvtsi128_si32(
_mm_xor_si128(
_mm_add_epi8(_mm_packus_epi16(v25, v25), _mm_cvtsi32_si128(v19)),
_mm_cvtsi32_si128(*((_DWORD *)v15 - 3))));
v26 = _mm_sra_epi32(
_mm_add_epi32(
(__m128i)_mm_shuffle_ps(
(__m128)_mm_mul_epi32(_mm_unpacklo_epi32(v23, v23), v17),
(__m128)_mm_mul_epi32(_mm_unpackhi_epi32(v23, v23), v17),
221),
v23),
v20);
v27 = _mm_and_si128(
_mm_shuffle_epi32(
_mm_shufflehi_epi16(
_mm_shufflelo_epi16(
_mm_sub_epi32(v23, _mm_mullo_epi32(_mm_add_epi32(_mm_srl_epi32(v26, v21), v26), v18)),
216),
216),
216),
(__m128i)xmmword_7FF755DC56A0);
*((_DWORD *)v15 - 2) = _mm_cvtsi128_si32(
_mm_xor_si128(
_mm_add_epi8(_mm_packus_epi16(v27, v27), _mm_cvtsi32_si128(v19)),
_mm_cvtsi32_si128(*((_DWORD *)v15 - 2))));
}
while ( v0 < 32 );
if ( v0 < 33 )
{
LABEL_9:
v28 = (char *)v35 + v0;
do
{
++v28;
v29 = v0 / 57;
v30 = v0++;
*(v28 - 1) ^= v30 - 57 * v29 + 53;
}
while ( v0 < 33 );
}
v31 = v37;
do
{
v32 = (unsigned __int8)*(v31 - 48);
v33 = (unsigned __int8)*v31 - v32;
if ( v33 )
break;
++v31;
}
while ( v32 );
if ( v33 )
v34.m128i_i32[0] = -872817740;
else
v34.m128i_i32[0] = -1879061547;
v34.m128i_i16[2] = 3338;
*(__int16 *)((char *)v34.m128i_i16 + 1) ^= 0x3736u;
v34.m128i_i8[3] ^= 0x38u;
v34.m128i_i8[6] = 0;
sub_7FF755DA1020(&v34);
Sleep(0x1388u);
}
  • 创建了一个子进程
  • sub_7FF755DA1070(&unk_7FF755DC5600, v37);第一个参数为%s,猜测为scanf函数,v37就是我们传入的flag,存储在[rcx-8]中

打断点在scanf然后进一步调试,观察栈中数据变化,发现[rbp+57h+var_B0]中存储的是flag的信息,

image-20231215213439983

image-20231215214312875

字符串为:HiGWDUuXQS6wVHBTp0ERfJe6VqprMqD1

image-20231215214839633

2.远程注入模块

要求

  • 使用Windows API CreateRemoteThread 远程注入模块到crackme进程中并且不崩溃
  • 然后使用ark工具(例如processhacker等)能够查看注入模块的信息。
  • 通过Hook进程中的函数使得输入任何字符串,控制台都会打印”正确”。

原理

Dll注入原理

Dll注入一般指向一个正在运行的程序注入一个线程,注入的线程运行我们的代码,而我们的代码就以DLL(动态链接库)的形式存在。但是程序不会平白无故的就加载我们的dll,这时候就需要使用我们强大的Windows API了,它为我们提供了大量的函数来附加和操纵其他进程。
API中的所有函数都包含于DLL文件之中。其中,最重要的是“Kernel32.dll”(包含管理内存,进程和线程相关的函数),“User32.dll”(大部分是用户接口函数),和“GDI32.dll”(绘制图形和显示文本相关的函数)。。

其中最经典的注入方式:远程进程注入

这种方法灵活性高,同时要求掌握的知识也很多,其核心思想就是在目标进程中开启一个线程调用LoadLibrary函数来加载我们想要注入的dll.

image-20231215181957773

具体流程

  • 获得要注入进程的进程ID,可以使用进程名获得。
  • 使用VirtualAllocEx api来在该进程内开辟一块内存,大小正好是DLL路径。
  • 将DLL路径信息通过WriteProcessMemory 写入该进程空间。
  • 获得kernal32在本进程的地址。因为同平台下所有exe都在同一位置加载kernal32。
  • 在kernal32中获得LoadLibraryW的地址,此地址也是被注入进程的LoadLibraryW地址。
  • 创建远程线程执行这个LoadLibraryW操作,参数就是要传入的DLL路径。

dll文件结构

DllMain

跟exe有个main或者WinMain入口函数一样,DLL也有一个入口函数,就是DllMain。以“DllMain”为关键字。对于动态链接库,DllMain是一个可选的入口函数。

函数定义:

1
2
3
4
5
BOOL WINAPI DllMain(
_In_ HINSTANCE hinstDLL, // 指向自身的句柄
_In_ DWORD fdwReason, // 调用原因
_In_ LPVOID lpvReserved // 隐式加载和显式加载
);

对于第二个参数fdwReason:它指明了系统调用Dll的原因,可能是

1
2
3
4
DLL_PROCESS_ATTACH
DLL_PROCESS_DETACH
DLL_THREAD_ATTACH
DLL_THREAD_DETACH

下面详细分析:

  • 进程映射

DLL_PROCESS_ATTACH

一个程序要调用Dll里的函数,首先要先把DLL文件映射到进程的地址空间。要把一个DLL文件映射到进程的地址空间,有两种方法:LoadLibrary或者LoadLibraryEx。

当一个DLL文件被映射到进程的地址空间时,系统调用该DLL的DllMain函数,传递的fdwReason参数为DLL_PROCESS_ATTACH,这种调用只会发生在第一次映射时。如果同一个进程后来为已经映射进来的DLL再次调用LoadLibrary或者LoadLibraryEx,操作系统只会增加DLL的使用次数,它不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。不同进程用LoadLibrary同一个DLL时,每个进程的第一次映射都会用DLL_PROCESS_ATTACH调用DLL的DllMain函数。

  • 进程卸载

DLL_PROCESS_DETACH

当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的fdwReason值是DLL_PROCESS_DETACH。当DLL处理该值时,它应该执行进程相关的清理工作。

那么什么时候DLL被从进程的地址空间解除映射呢?两种情况:

​ 1.FreeLibrary解除DLL映射(有几个LoadLibrary,就要有几个FreeLibrary)

​ 2.进程结束而解除DLL映射,在进程结束前还没有解除DLL的映射,进程结束后会解除DLL映射。(如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。)

注意:当用DLL_PROCESS_ATTACH调用DLL的DllMain函数时,如果返回FALSE,说明没有初始化成功,系统仍会用DLL_PROCESS_DETACH调用DLL的DllMain函数。因此,必须确保清理那些没有成功初始化的东西。

  • 线程映射

DLL_THREAD_ATTACH

当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,并用值DLL_THREAD_ATTACH调用DLL的DllMain函数。

新创建的线程负责执行这次的DLL的DllMain函数,只有当所有的DLL都处理完这一通知后,系统才允许进程开始执行它的线程函数。

注意跟DLL_PROCESS_ATTACH的区别,第n(n>=2)次以后地把DLL映像文件映射到进程的地址空间时,是不再用DLL_PROCESS_ATTACH调用DllMain的。而DLL_THREAD_ATTACH不同,进程中的每次建立线程,都会用值DLL_THREAD_ATTACH调用DllMain函数,哪怕是线程中建立线程也一样。

  • 线程卸载

DLL_THREAD_DETACH

如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),系统查看当前映射到进程空间中的所有DLL文件映像,并用DLL_THREAD_DETACH来调用DllMain函数,通知所有的DLL去执行线程级的清理工作。

注意:如果线程的结束是因为系统中的一个线程调用了TerminateThread,系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数。

关于本题

注入文件:evil.cpp(生成evil.exe)
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include<stdio.h>
#include<Windows.h>
#include<iostream>
#include<string>
#include <TlHelp32.h>
#include <tchar.h>
using namespace std;
BOOL CreateRemoteDllInjectDll(DWORD ProcessID);
DWORD FindProcessID(LPCTSTR szProcessName);
//DWORD FindProcessID(const wchar_t* szProcessName);
int main() {
//Get the pid by name
DWORD pid = FindProcessID("crackme.exe");
printf("pid:%d\n", pid);
//DLL inject
if (!CreateRemoteDllInjectDll(pid)) {
printf("faild to create remotedllinject evil.dll");
puts("die");
}
system("pause");
return 0;
}


//Create remotedllinject evil.dll
BOOL CreateRemoteDllInjectDll(DWORD ProcessID) {
char DllName[_MAX_PATH] ="E:\\school\\TencentClass\\T1\\TencentClass_T1.dll";
HANDLE hProcess = NULL;

//注入进程
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessID);//打开进程获取句柄
if (NULL == hProcess) {
puts("OpenProcess error");
return false;
}
size_t dwsize = sizeof(DllName);
void* pDLLAddr = VirtualAllocEx(hProcess, NULL, dwsize, MEM_COMMIT,PAGE_EXECUTE_READWRITE);
//分配虚拟内存,并且地址保存在pDLLAddr目录中
if (pDLLAddr == NULL) {
puts("VirtualAllocEx is error");
return false;
}
if (!WriteProcessMemory(hProcess, pDLLAddr, (void*)DllName, dwsize, NULL))
{//把所需要的注入的dll文件目录字符注入给目标进程
puts("WriteProcessMemory is error");
return false;
}

//获取地址
HMODULE hModule = GetModuleHandle("Kernel32");//获取kernel32.dll模块
if (hModule == NULL) {
puts("kernel32 error");
return false;
}
LPTHREAD_START_ROUTINE LoadLibraryAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryA");//进一步获取LoadLibraryA的函数地址
if (LoadLibraryAddr == NULL) {
puts("LoadLibraryAddr error");
return false;
}
//执行远程线程注入
//HANDLE hRemotehandle = CreateRemoteThread(hProcess, NULL, 0,LoadLibraryAddr, pDLLAddr, 0, NULL); //创造远程线程注入到目标进程中
//if (!hRemotehandle) {
// printf("%p\t%p\t%p\t", hProcess, LoadLibraryAddr, pDLLAddr);
// DWORD dwError = GetLastError();
// printf("CreateRemoteThread is error. Error Code: %d\n", dwError);
// return false;
//}
HANDLE hRemotehandle = CreateRemoteThread(hProcess, NULL, 0, LoadLibraryAddr, pDLLAddr, 0, NULL); //创造远程线程注入到目标进程中
if (!hRemotehandle) {
DWORD dwError = GetLastError();

LPVOID lpMsgBuf;
FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwError,
0, // Default language
(LPSTR)&lpMsgBuf,
0,
NULL
);

//printf("CreateRemoteThread is error. Error Code: %d - %s\n", dwError, lpMsgBuf);

LocalFree(lpMsgBuf);

return false;
}
//printf("%p\t%p\t%p\t", hProcess, LoadLibraryAddr, pDLLAddr);


WaitForSingleObject(hRemotehandle, INFINITE);//等待返回
CloseHandle(hRemotehandle);//关闭目标进程句柄
puts("inject successfully!");
return true;
}

//Find PID by name
DWORD FindProcessID(LPCTSTR szProcessName)
{
DWORD dwPID = 0xFFFFFFFF;
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
//hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
Process32First(hSnapShot, &pe);
do
{
if (!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
{
dwPID = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapShot, &pe));
CloseHandle(hSnapShot);
return dwPID;
}
dll文件:dllmain.cpp(生成dllmain.exe)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <Windows.h>
BOOL APIENTRY DllMain(HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
OutputDebugString("DLL Inject Sucess");
MessageBox(NULL, "dll sucess", "dll sucess", MB_OK);
break;//加载DLL时运行d
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

作业结果

  • 将ddlmain.cpp编译成64位的dll文件(注意一定要是64位)
  • 将dll文件绝对路径填入evil.cpp中
  • 将evil.cpp编译成64位的exe文件
  • 在crackme执行的过程中,以管理员权限执行evil.exe
  • 利用process Hacker查看改进程调用Modules
  • 可以看到dll成功注入

注入成功并弹窗

image-20231217014350255

Process Hackers截图,evil1.dll为恶意dll

image-20231217014435627

3.Hook程序

原理

dll注入之Hook

DLL注入
在Windows中,每个进程有自己私有的地址空间。当我们用指针来引用内存的时候,指针的值表示的是进程自己的地址空间中的一个内存地址。进程不能创建一个指针来引用属于其他进程的内存。因此,如果进程有一个缺陷会覆盖随机地址处的内存,那么这个缺陷不会影响到其他进程所使用的内存。
独立的地址空间对开发人员和用户是非常有利的。对开发人员来说,系统更有可能捕获错误的内存读/写。对于用户来说,操作系统变得更加健壮了,因为一个应用程序的错误不会导致其他应用程序或操作系统崩溃。
所谓DLL注入就是将一个DLL放进某个进程的地址空间里,让它成为那个进程的一部分。可以通过很多种方式来实现DLL注入。
    + 使用注册表来注入DLL
    + 使用Windows Hook来注入DLL 
    + 使用远程线程来注入DLL   --本题的考点
    + 使用木马DLL来注入
    + 把DLL作为调试器来注入
    + 使用CreateProcess来注入代码

我们已经通过远程注入把恶意的dll注入进去了,下一步是编写dll,修改源程序逻辑,使得我们无论输入什么都返回正确。

Hook技术

hook技术就是让目标函数在执行之前先执行一段我们自己写的代码

本题

IDA分析代码后发现最终判断flag的部分为

1
2
3
4
5
6
7
8
9
10
while ( v33 );
if ( (_DWORD)v34 )
v35.m128i_i32[0] = -872817740; // 错误
else
v35.m128i_i32[0] = -1879061547; //正确
v35.m128i_i16[2] = 3338;
*(__int16 *)((char *)v35.m128i_i16 + 1) ^= 0x3736u;
v35.m128i_i8[3] ^= 0x38u;
v35.m128i_i8[6] = 0;
sub_7FF6422E1020((__int64)&v35, v34, -48i64); //打印函数

ce中找到相关的汇编代码

image-20231219045931532

于是我们有两个思路

  • 修改if判断逻辑,让判断一直为正确
  • 修改最终的输出函数,一直输出正确

第二个方法可以使用minHook实现,将原来的函数Hook为newPrintf函数就行

现主要实现第一种方法

把if判断的逻辑删掉和跳转错误的jne改成nop

image-20231219051036666

最终效果

image-20231219050607270

使用代码实现

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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <windows.h>

using namespace std;
void SetHook(void* pJNE) {
DWORD oldProtect;
VirtualProtect(pJNE, 4, PAGE_EXECUTE_READWRITE, &oldProtect);
*(DWORD*)pJNE = 0x90909090; // 换入nop nop nop nop
VirtualProtect(pJNE, 4, oldProtect, &oldProtect);
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
HMODULE hCrackMe = GetModuleHandle(TEXT("crackme.exe")); //获得基地址
if (hCrackMe != NULL) {
HMODULE jneAddress = (HMODULE)((size_t)hCrackMe + 0x18E7);
SetHook((void*)jneAddress);
OutputDebugString("Hook Sucess");
MessageBox(NULL, "Hook sucess", "Hook sucess", MB_OK);
}
break;
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

编译:

1
2
PS E:\school\TencentClass\T1> E:\app\MinGW-w64\bin\x86_64-w64-mingw32-g++ -shared -o Hook.dll  hook.cpp
PS E:\school\TencentClass\T1> E:\app\MinGW-w64\bin\x86_64-w64-mingw32-g++ -o evil.exe evil.cpp
最终效果
image-20231219053305127