逆向工程核心原理

《逆向工程核心原理》

0x00-调试Helloworld程序

选择Release模式生成的可执行文件能使程序代码更简洁,方便调试-之前我都是debug模式的,难怪有一堆初始化操作

OD指令-Ctrl+F2,重新开始调试

​ Ctrl+F9,一直在函数代码内部运行,知道遇到retn,跳出函数

​ :可以给地址添加标签

​ Ctrl+E,编辑数据

​ 空格,编写汇编代码

0x01-小端序标记法

采用小端序时,地址高位存储数据的高位,地址低位存储数据的低位,我们知道一个地址最多存储一个字节的单元,也就是两个十六进制,那么0x12345678在内存存储中时则为78 56 34 12

0x02-IA-32寄存器基本讲解

通用寄存器

寄存器作用

段寄存器

其中FS寄存器比较重要,用于计算SEH、TEB、PEB等地址

重要标志位

0x03-栈

栈的特征

栈的特征

函数调用入栈顺序

0x04-栈帧

通过EBP-栈帧指针寄存器访问局部变量、参数、函数返回地址

函数开始时要先把已有值保存到栈中

指出相关内存属于哪个段

返回值

外平栈和内平栈

xor eax,eax

关闭段显示

函数的间接调用

Test相当于&命令,改变ZF的值

TEST指令

0x05-Process Explorer

安装Peocess Explorer、sysinternals

0x06-函数调用约定

cdecl

stdcall

stdcall

fastcall

0x07-鸡汤

0x08-PE文件格式

相当于复习了

RVA与VA

映像

显式链接与隐式链接

为什么要有导入表

1、是因为不同版本的dll函数存放的地址也不同,为了能正常调用函数,编译器准备了存放函数实际地址的位置

2、重定位,多个dll无法同时装载到1000000h

导入表

导入表结构

IAT输入顺序

因为存在按序号导入也有按名称导入,所以获取函数起始地址的时候有两种方式

程序运行中的IAT表

导出表

获取函数地址

即通过函数名称导出:先去函数名称表依次比较,找到相同时,得到索引index,去导出序号表根据index取出里面的值作为新的索引index_new,再去函数地址表找到函数地址

按序号导出

patched PE

0x09-运行时压缩

压缩器

保护器

保护器种类

upx加壳后的notepad.exe

0x0A-调试UPX压缩的notepad程序

OD跟踪命令

解码循环

恢复地址

IAT

0x0B-基址重定位表

ASLR

重定位表中的地址计算

重定位表分块大小原因

重定位

0x0C-从可执行文件中删除重定位表

步骤

0x0D-Upack PE文件头详细分析

重叠文件头

因为DOS头尾部有一堆垃圾数据,所以将其修改并去除垃圾数据并修改e_lfanew,可以实现文件头重叠

IMAGE_FILE_HEADER.SizeofOptionalHeader

通过修改可选PE头的大小,可以向文件头插入解码代码。

可选PE头存在的原因

IMAGE_OPTINAL_HEADER.NumberOfRvaAndSizes

IMAGE_SECTION_HEADER

UPcak的重叠特征

也就是说Upack先将notepad.exe压缩至第二个节区,运行时将第二个节区的代码解压至第一个节区

解压后的第一个节区

RVA to RAW

导入表

导入地址表

0x0E-Upack调试-查找OEP

0x0F-内嵌补丁

内嵌补丁与代码补丁的区别

程序分析

先对附件进行分析

弹出的对话框要求解压其本身

对地址4010F5的第一次异或

401007地址的值异或7

再次对4010F5进行异或操作,解密

可以看到这里对地址的值进行校验,每次取出四个字节相加,所以当我们修改了内容时需要对校验部分修改

校验

所以解码完的部分存在我们要找的字符串

对401090地址的值进行异或

异或

大致流程

内嵌补丁练习

因为我们要打补丁的字符串位于B区,而B区进行了双重加密,而且对其内容进行了校验,所以采用内嵌补丁

内嵌补丁

因为节区要对齐,所以可能存在空白节区,那我们就可以在节区末尾添加我们的洞穴代码

然后让程序先跳转到我们这个地址,再在这段代码最后一句,加上跳回原本程序的代码

程序原本的跳转

但是要注意一点,就是我们插入的是已经解完密的jmp,而实际在文件中,这里的jmp是被加密的,所以要先对我们的修改进行加密,也就是xor 7

0x10-Windows消息钩取

windows消息机制

https://blog.csdn.net/alzzw/article/details/108217879

windows消息流

消息钩取工作原理

我们设置的钩子能在应用程序之前获取到OS相应信息

SetwindowsHookEx()

HHOOK SetWindowsHookExA(
[in] int idHook,// 要安装的挂钩过程的类型
[in] HOOKPROC lpfn,// 指向挂钩过程的指针
[in] HINSTANCE hmod,// 所指向的挂钩过程的 DLL 的句柄
[in] DWORD dwThreadId
);

main.cpp

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
#include "stdio.h"
#include "conio.h"
#include "windows.h"

#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"

typedef void (*PFN_HOOKSTART)();
typedef void (*PFN_HOOKSTOP)();

void main()
{
HMODULE hDll = NULL;
PFN_HOOKSTART HookStart = NULL;
PFN_HOOKSTOP HookStop = NULL;
char ch = 0;

// 加载KeyHook.dll
hDll = LoadLibraryA(DEF_DLL_NAME);
if (hDll == NULL)
{
printf("LoadLibrary(%s) failed!!! [%d]", DEF_DLL_NAME, GetLastError());
return;
}

// 获取导出函数地址
HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);

// 开始钩取
HookStart();

// 等待,直到用户输入“q”
printf("press 'q' to quit!\n");
while (_getch() != 'q');

// 终止钩子
HookStop();

// 卸载KeyHook.dll
FreeLibrary(hDll);
}

KeyHook.dll

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
//KeyHook.cpp

#include "stdio.h"
#include "windows.h"

//定义目标进程名为notepad.exe
#define DEF_PROCESS_NAME "notepad.exe"

//定义全局变量
HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;

//DllMain()函数在DLL被加载到进程后会自动执行
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved){
switch( dwReason ){
case DLL_PROCESS_ATTACH:
g_hInstance = hinstDLL;
break;

case DLL_PROCESS_DETACH:
break;
}

return TRUE;
}

//
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam){
char szPath[MAX_PATH] = {0,};
char *p = NULL;

if( nCode >= 0 ){

//释放键盘按键时,bit 31 : 0 => press, 1 => release
if( !(lParam & 0x80000000) ){
GetModuleFileNameA(NULL, szPath, MAX_PATH);
p = strrchr(szPath, '\\');

//比较当前进程名称,若为notepad.exe,则消息不会传递给应用程序或下一个钩子函数
//_stricmp()函数用于比较字符串,i表示不区分大小写,若两个值相等则返回0
if( !_stricmp(p + 1, DEF_PROCESS_NAME) ){
return 1;
}
}
}

//比较当前进程名称,若非notepad.exe,则消息传递给应用程序或下一个钩子函数
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

//在C++中调用C的库文件,用extern "C"告知编译器,因为C++支持函数重载而C不支持,两者的编译规则不同
#ifdef __cplusplus
extern "C"{
#endif
//__declspec,针对编译器的关键字,用于指出导出函数
//当调用导出函数HookStart()时,SetWindowsHookEx()函数就会将KeyboardProc()添加到键盘钩链
__declspec(dllexport) void HookStart(){
g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}

__declspec(dllexport) void HookStop(){
if(g_hHook){
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
#ifdef __cplusplus
}
#endif

调用导出函数HookStart()时,SetWindowsHookEx()函数就会把KeyboardProc()添加到键盘钩链

调试过程

0x11-恶意键盘记录器

DLL注入

DLL_Main函数

https://blog.csdn.net/tiandao2009/article/details/79839182 dllmain函数的不同情形

DLL注入方法

0x12-DLL卸载

获取目标进程的句柄

1
hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID);

该语句使用进程ID来获取目标进程的进程句柄,下面用获得的进程句柄调用CreateRemoteThread()

获取FreeLibrary()地址

1
2
hModule=GetModule(L"kernel32.dll);
pThreadProc=(LPTHREAD_START_ROUTINE)GetProcAddress(hModule,"FreeLibrary");

若要使目标进程自己调用FreeLibrary(),需要先获得FreeLibrary()的地址。然而上述代码获得的不是加载到目标进程中的FreeLibrary()的地址,而是EjectDll.exe进程中FreeLibrary()的地址,但是FreeLibrary在所有进程中地址是相同的

在目标进程中运行线程

1
hThread=CreateRemoteThread(hProcess,NULL,0,pThreadProc,me.modBaseAddr,0,NULL);

pThreadProc参数是FreeLibrary()的地址,me.modBaseAddr参数是要卸载的DLL的加载地址。将线程函数指定为FreeLibrary()函数,并且把DLL加载地址传递给线程参数,这样就在目标进程中成功调用了FreeLibrary函数

CreateRemoteThread()原意是在外部线程调用执行线程函数,不过这里的线程函数换成了FreeLibrary()

注意事项

0x13-通过修改PE加载DLL

也就是修改导入表来使得PE文件运行时直接加载dll

晚点看

0x14-代码注入

代码注入是一种向目标进程插入独立运行代码并使之运行的技术,他一般调用CreateRemoteThread()以远程线程形式运行插入的代码,所以也被称为线程注入

也就是说DLL是将整个DLL注入进程中,而代码注入只是注入必要的代码,所以同时也要报要操作的数据进行注入

使用代码注入的原因

0x15-汇编代码注入

关闭自动填充nop

接下来将插入的asm的机器码进行复制,并修改格式

得到

1
2
3
4
5
6
7
8
9
char shellcode[] = {
0x55,0x8B,0xEC,0x8B,0x75,0x08,0x68,0x6C,0x6C,0x00,0x00,0x68,0x33,0x32,0x2E,0x64
,0x68,0x75,0x73,0x65,0x72,0x54,0xFF,0x16,0x68,0x6F,0x78,0x41,0x00,0x68,0x61,0x67
,0x65,0x42,0x68,0x4D,0x65,0x73,0x73,0x54,0x50,0xFF,0x56,0x04,0x6A,0x00,0xE8,0x0C
,0x00,0x00,0x00,0x52,0x65,0x76,0x65,0x72,0x73,0x65,0x43,0x6F,0x72,0x65,0x00,0xE8
,0x14,0x00,0x00,0x00,0x77,0x77,0x77,0x2E,0x72,0x65,0x76,0x65,0x72,0x73,0x65,0x63
,0x6F,0x72,0x65,0x2E,0x63,0x6F,0x6D,0x00,0x6A,0x00,0xFF,0xD0,0x33,0xC0,0x8B,0xE5
,0x5D,0xC3
};

0x16-API钩取

钩取流程

因为在用户模式下要访问系统资源时,我们没有办法直接访问到,所以为了运行实际的应用程序代码,需要加载许多系统库,也就是DLL

用户代码访问系统资源

实际就是对API调用时进行钩取,获得控制权

实现API钩取的方法

0x17-记事本WriteFIle()API钩取