第一次作业
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的信息,
字符串为:HiGWDUuXQS6wVHBTp0ERfJe6VqprMqD1
2.远程注入模块
要求
- 使用Windows API CreateRemoteThread 远程注入模块到crackme进程中并且不崩溃
- 然后使用ark工具(例如processhacker等)能够查看注入模块的信息。
- 通过Hook进程中的函数使得输入任何字符串,控制台都会打印”正确”。
原理
Dll注入原理
Dll注入一般指向一个正在运行的程序注入一个线程,注入的线程运行我们的代码,而我们的代码就以DLL(动态链接库)的形式存在。但是程序不会平白无故的就加载我们的dll,这时候就需要使用我们强大的Windows API了,它为我们提供了大量的函数来附加和操纵其他进程。
API中的所有函数都包含于DLL文件之中。其中,最重要的是“Kernel32.dll”(包含管理内存,进程和线程相关的函数),“User32.dll”(大部分是用户接口函数),和“GDI32.dll”(绘制图形和显示文本相关的函数)。。
其中最经典的注入方式:远程进程注入
这种方法灵活性高,同时要求掌握的知识也很多,其核心思想就是在目标进程中开启一个线程调用LoadLibrary函数来加载我们想要注入的dll.
具体流程
- 获得要注入进程的进程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);
int main() { DWORD pid = FindProcessID("crackme.exe"); printf("pid:%d\n", pid); if (!CreateRemoteDllInjectDll(pid)) { printf("faild to create remotedllinject evil.dll"); puts("die"); } system("pause"); return 0; }
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); if (pDLLAddr == NULL) { puts("VirtualAllocEx is error"); return false; } if (!WriteProcessMemory(hProcess, pDLLAddr, (void*)DllName, dwsize, NULL)) { puts("WriteProcessMemory is error"); return false; }
HMODULE hModule = GetModuleHandle("Kernel32"); if (hModule == NULL) { puts("kernel32 error"); return false; } LPTHREAD_START_ROUTINE LoadLibraryAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryA"); if (LoadLibraryAddr == NULL) { puts("LoadLibraryAddr error"); 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, (LPSTR)&lpMsgBuf, 0, NULL );
LocalFree(lpMsgBuf);
return false; }
WaitForSingleObject(hRemotehandle, INFINITE); CloseHandle(hRemotehandle); puts("inject successfully!"); return true; }
DWORD FindProcessID(LPCTSTR szProcessName) { DWORD dwPID = 0xFFFFFFFF; HANDLE hSnapShot = INVALID_HANDLE_VALUE; PROCESSENTRY32 pe; pe.dwSize = sizeof(PROCESSENTRY32); 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; 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成功注入
注入成功并弹窗
Process Hackers截图,evil1.dll为恶意dll
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中找到相关的汇编代码
于是我们有两个思路
- 修改if判断逻辑,让判断一直为正确
- 修改最终的输出函数,一直输出正确
第二个方法可以使用minHook实现,将原来的函数Hook为newPrintf函数就行
现主要实现第一种方法
把if判断的逻辑删掉和跳转错误的jne改成nop
最终效果
使用代码实现
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
| #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; 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
|
最终效果