1 PE注入
PE注入就是对静态的PE文件进行修改,使其在运行时能够加载我们需要的构造的代码。因为都是对文件进行静态的修改,只需要处理好RVA和Offset之间的转换,保证PE完整的结构就好,相对来说比较简单。
此部分代码都使用Python实现,使用pefile模块对PE文件进行操作,十分方便。当然因为PE的结构就是通过头文件定义的,使用C语言也很方便。
1.1 寻找空洞
寻找代码中的空闲区域的大小,作为PE注入前的参考。
参考了cave miner项目,但是我没有研究其源码,只是按照其原理通过比较节表中的 SizeOfRawData 和 Misc_VirtualSize 属性,实现了最简单的功能。
from pefile import PE
import sys
def search_cave(filename, minsize=0):
"""
搜索代码空洞
""" pe = PE(filename)
caves = []
print(f"[*] FileAlign:\t{pe.OPTIONAL_HEADER.FileAlignment}Bytes")
print(f"[*] SectAlign:\t{pe.OPTIONAL_HEADER.SectionAlignment}Bytes\n")
# 1. Cave Before Sections
sectionDataBegin = pe.sections[0].PointerToRawData
sectionTableEnd = pe.sections[-1].__file_offset__ + pe.sections[-1].sizeof()
caveSize = sectionDataBegin - sectionTableEnd - 40
RVA = sectionTableEnd
flags = 0
if caveSize > minsize:
caves.append(
['BEFORE ' + pe.sections[0].Name.decode().rstrip('\x00'), sectionTableEnd + 40, sectionDataBegin, caveSize, RVA,
flags])
# 2. Cave In Each Sections
for section in pe.sections:
caveSize = section.SizeOfRawData - section.Misc_VirtualSize
if caveSize > minsize:
begin = section.PointerToRawData + section.Misc_VirtualSize
RVA = section.VirtualAddress + section.Misc_VirtualSize
flags = (section.Characteristics & 0xf0000000) >> 29
# print(flags)
caves.append(['IN ' + section.Name.decode().rstrip('\x00'), begin, begin + caveSize, caveSize, RVA, flags])
for cave in caves:
print("[+] " + cave[0], end=":\n")
print("\t\tBegin:\t" + hex(cave[1])[2:], end="h\n")
print("\t\tEnd:\t" + hex(cave[2])[2:], end="h\n")
print("\t\tSize:\t" + hex(cave[3])[2:] + 'h (' + str(cave[3]) + 'Bytes)', end="\n")
print("\t\tRVA:\t" + hex(cave[4])[2:], end="h\n")
flags = list('---')
if cave[5] & 4:
flags[0] = 'w'
if cave[5] & 2:
flags[1] = 'r'
if cave[5] & 1:
flags[2] = 'x'
print("\t\tFlags:\t" + ''.join(flags), end="\n")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("[Usage] python search.py <filename> [minsize]")
exit(0)
if len(sys.argv) == 2:
search_cave(sys.argv[1])
else:
search_cave(sys.argv[1], int(sys.argv[2]))
1.2 注入shellcode
向空洞中注入shellcode,然后修改OEP指向shellcode的入口。需要注意的是要确保注入点的节具有执行权限。
def inject_shellcode(filename, offset, payload):
"""
代码空洞注入shellcode
""" pe = PE(filename)
rva = pe.get_rva_from_offset(offset)
data_size = len(payload) + 5
for i in range(data_size):
if pe.__data__[offset + i] != 0:
print("[!] The cave is too small!")
return
# 添加call指令
opcode = b'\xe8' + (-data_size).to_bytes(4, byteorder='little', signed=True)
payload += opcode
# 修改OEP
new_oep = rva + data_size - 5
pe.OPTIONAL_HEADER.AddressOfEntryPoint = new_oep
# 关闭ASLR
pe.OPTIONAL_HEADER.DllCharacteristics &= 0xff
# 定位节
inject_section = pe.sections[0]
for section in pe.sections:
if offset <= section.PointerToRawData + section.SizeOfRawData:
inject_section = section
break
inject_section.Characteristics |= 0xe0000000
# 注入shellcode
pe.__data__ = pe.__data__[:offset] + payload + pe.__data__[offset + len(payload):]
# pe.set_data_bytes(offset, shellcode)
# 写入文件
modified_filename = filename.replace('.exe', '_injected.exe')
pe.write(modified_filename)
pe.close()
print(f"[+] Injected PE file saved as: {modified_filename}.")
1.3 注入导入表
通过注入导入表使PE文件可以加载任意的DLL,但是导入表的后面一般没有足够的空间来添加表项,此时需要将原来的整个导入表复制到足够大的空洞处再新增表项,同时修改可选头中标识的导入表的RVA。
def inject_import(filename, offset):
"""
注入导入表
:return:
""" pe = PE(filename)
# 复制旧IDT
IDT_offset = pe.get_offset_from_rva(pe.OPTIONAL_HEADER.DATA_DIRECTORY[1].VirtualAddress)
IDT_size = pe.OPTIONAL_HEADER.DATA_DIRECTORY[1].Size - 20
IDT_data = pe.__data__[IDT_offset:IDT_offset+IDT_size]
pe.set_data_bytes(offset, IDT_data)
# 新建一个IDT项
begin = offset + IDT_size
new_IDT = SectionStructure(pe.__IMAGE_IMPORT_DESCRIPTOR_format__)
new_IDT.__unpack__(bytearray(new_IDT.sizeof()))
new_IDT.set_file_offset(begin)
begin += 40
# INT
new_IDT.OriginalFirstThunk = pe.get_rva_from_offset(begin)
new_INT = SectionStructure(pe.__IMAGE_THUNK_DATA_format__)
new_INT.__unpack__(bytearray(new_INT.sizeof()))
new_INT.set_file_offset(begin)
begin += 8
# IAT
new_IDT.FirstThunk = pe.get_rva_from_offset(begin)
new_IAT = SectionStructure(pe.__IMAGE_THUNK_DATA_format__)
new_IAT.__unpack__(bytearray(new_IAT.sizeof()))
new_IAT.set_file_offset(begin)
begin += 8
# funcName
funcName = b'\x00\x00show\x00'
pe.set_data_bytes(begin, funcName)
new_INT.AddressOfData = pe.get_rva_from_offset(begin)
new_IAT.AddressOfData = pe.get_rva_from_offset(begin)
begin += len(funcName) + 3
# dllName
dllName = b'Hello.dll\x00'
pe.set_data_bytes(begin, dllName)
new_IDT.Name = pe.get_rva_from_offset(begin)
pe.__structures__.append(new_IDT)
pe.__structures__.append(new_INT)
pe.__structures__.append(new_IAT)
# 更改扩展头中的IDT的地址
pe.OPTIONAL_HEADER.DATA_DIRECTORY[1].VirtualAddress = pe.get_rva_from_offset(offset)
pe.OPTIONAL_HEADER.DATA_DIRECTORY[1].Size += 20
# 修改节权限
inject_section = None
for section in pe.sections:
if offset <= section.PointerToRawData + section.SizeOfRawData:
inject_section = section
break
inject_section.Characteristics = 0xc0000000
# pe.merge_modified_section_data()
# 写入文件
modified_filename = filename.replace('.exe', '_dll.exe')
pe.write(modified_filename)
pe.close()
print(f"[+] Modified PE file saved as: {modified_filename}.")
1.4 新增节
如果现有的代码空洞不足以进行注入,那么可以通过更改PE结构来开辟更多的空洞。
利用节表和节数据之间的空闲区域再新增一个节,网上有很多详细的讲解,这里就不再赘述。
为了不需要移动其他的节数据,一般是在文件末尾新增节。
def add_section(filename, data_size):
"""
新增节
:return: offset of new section data
""" pe = PE(filename)
data_size += 5
# 计算是否足够新增节区头
begin = pe.sections[-1].__file_offset__ + pe.sections[-1].sizeof()
end = pe.sections[0].PointerToRawData
cave_size = end - begin
# data_size = len(shellcode) + 5
if cave_size < 80:
print("[!] There is not enough cave to add a section header.")
return
print(f"[+] There are {cave_size - 40} ({cave_size} - 40) bytes cave to add section header.")
# 构造新的节表
new_section = SectionStructure(pe.__IMAGE_SECTION_HEADER_format__)
new_section.__unpack__(bytearray(new_section.sizeof()))
new_section.set_file_offset(begin)
new_section.Name = b'.new'
new_section.Misc_VirtualSize = data_size
new_section.VirtualAddress = pe.sections[-1].VirtualAddress + math.ceil(
pe.sections[-1].Misc_VirtualSize / pe.OPTIONAL_HEADER.SectionAlignment) * pe.OPTIONAL_HEADER.SectionAlignment
new_section.SizeOfRawData = math.ceil(
data_size / pe.OPTIONAL_HEADER.FileAlignment) * pe.OPTIONAL_HEADER.FileAlignment
new_section.PointerToRawData = pe.sections[-1].PointerToRawData + pe.sections[-1].SizeOfRawData
new_section.Characteristics = 0x60000000
# 修改节区数量和我文件大小
pe.FILE_HEADER.NumberOfSections += 1
pe.OPTIONAL_HEADER.SizeOfImage = math.ceil(pe.OPTIONAL_HEADER.SizeOfImage + pe.sections[
-1].Misc_VirtualSize / pe.OPTIONAL_HEADER.SectionAlignment) * pe.OPTIONAL_HEADER.SectionAlignment
# 添加节头和节数据
pe.__structures__.append(new_section)
padding = b'\x00' * new_section.SizeOfRawData
# pe.__data__ = pe.__data__[:] + padding
pe.set_data_bytes(new_section.PointerToRawData, padding)
print("[*] New section added to PE file.")
print(f"\t[-] FOA: {hex(new_section.PointerToRawData)[2:].rjust(8, '0')}h")
print(f"\t[-] RVA: {hex(new_section.VirtualAddress)[2:].rjust(8, '0')}h")
# 写入文件
modified_filename = filename.replace('.exe', '_modified.exe')
pe.write(modified_filename)
pe.close()
print(f"[+] Modified PE file saved as: {modified_filename}.")
return new_section.PointerToRawData
提升NT头
如果节表和节数据之间的空洞不足以再新增一个节,可以使用此方法将没用的DOS Stub段删除,将其后面的NT头整体上移覆盖掉这段数据,那么节表和节数据之间就可以腾出一些空洞。
def lift_header(filename):
"""
删除 dos_stub, 提升PE头
""" pe = PE(filename)
begin = pe.DOS_HEADER.sizeof()
end = pe.NT_HEADERS.__file_offset__
offset = pe.sections[0].PointerToRawData
height = end - begin
if height <= 0:
print("[!] No DOS stub to cover!")
return
# 修改DOS头属性
pe.DOS_HEADER.e_lfanew -= height
pe.OPTIONAL_HEADER.CheckSum = pe.generate_checksum()
# 上移头部覆盖dos_stub
padding = b'\x00' * height
pe.__data__ = pe.__data__[:begin] + pe.__data__[end:offset] + padding + pe.__data__[offset:]
# 写入文件
modified_filename = filename.replace('.exe', '_lifted.exe')
# pe.write(modified_filename)
open(modified_filename, 'wb').write(pe.__data__)
pe.close()
print(f"[+] File header lifted by {height} bytes.")
print(f"[+] Modified PE file saved as: {modified_filename}.")
1.5 合并节
这个方法也是为了新增节,通过合并两个节,节表就会腾出一个位置用来存放新增的节。
同样,为了不影响其他节,合并最后两个节是最好的选择。
def merge_sections(filename):
"""
合并节
""" pe = PE(filename)
if len(pe.sections) < 2:
print("[!] The section is too little!")
return
section1 = pe.sections[-2]
section2 = pe.sections[-1]
name1 = section1.Name.decode().rstrip('\x00')
name2 = section2.Name.decode().rstrip('\x00')
virtual_size1 = section2.VirtualAddress - section1.VirtualAddress
data1 = section1.get_data()
data2 = section2.get_data()
padding = (virtual_size1 - section1.SizeOfRawData) * b'\x00'
# 修改属性
pe.FILE_HEADER.NumberOfSections -= 1
pe.OPTIONAL_HEADER.SizeOfImage += len(padding)
# 修改节表
section1.SizeOfRawData = virtual_size1 + section2.Misc_VirtualSize
section1.Misc_VirtualSize = virtual_size1 + section2.SizeOfRawData
section1.Characteristics |= section2.Characteristics
# pe.set_bytes_at_offset(section2.__file_offset__, b'\x00' * 40)
# 填充节数据
# begin = section1.PointerToRawData
pe.set_data_bytes(section1.PointerToRawData, data1+padding+data2)
# pe.__data__ = pe.__data__[:begin] + data1 + padding + data2
# 写入文件
modified_filename = filename.replace('.exe', '_merged.exe')
pe.write(modified_filename)
pe.close()
print(f"[+] The section {name2} has been merged to {name1}.")
print(f"[+] Modified PE file saved as: {modified_filename}.")
1.6 扩展节
如果不新增节,而现有的代码空洞又不足以注入,可以通过修改节表数据,将现有的节的长度增加。这里实现的是扩展最后一个节。
def expand_section(filename, data_size):
"""
扩展节(最后一个节)
:return: offset of the expanded part of the last section data
""" pe = PE(filename)
last_section = pe.sections[-1]
data_size += 5
cave_size = last_section.SizeOfRawData - last_section.Misc_VirtualSize
if cave_size >= data_size:
print("[!] The cave is enough to use, No section expand.")
return
offset = last_section.PointerToRawData + last_section.Misc_VirtualSize
rva = last_section.VirtualAddress + last_section.Misc_VirtualSize
last_name = last_section.Name.decode().rstrip('\x00')
expand_size = math.ceil(
(data_size - cave_size) / pe.OPTIONAL_HEADER.FileAlignment) * pe.OPTIONAL_HEADER.FileAlignment
# 修改节区头属性
last_section.SizeOfRawData += expand_size
last_section.Misc_VirtualSize += expand_size
last_section.Characteristics |= 0x60000000
# 计算内存对齐,修改SizeOfImage
pe.OPTIONAL_HEADER.SizeOfImage += pe.OPTIONAL_HEADER.SectionAlignment * (
math.ceil(last_section.Misc_VirtualSize / pe.OPTIONAL_HEADER.SectionAlignment)
- math.ceil((last_section.Misc_VirtualSize - expand_size) / pe.OPTIONAL_HEADER.SectionAlignment))
# 填充节区数据
padding = b'\x00' * expand_size
# pe.__data__ = pe.__data__[:] + padding
pe.set_data_bytes(len(pe.__data__), padding)
# 写入文件
modified_filename = filename.replace('.exe', '_expanded.exe')
pe.write(modified_filename)
pe.close()
print(f"[*] The {last_name} section is extended by {expand_size} bytes.")
print(f"\t[-] FOA:\t{hex(offset)[2:].rjust(8, '0')}h")
print(f"\t[-] RVA:\t{hex(rva)[2:].rjust(8, '0')}h")
print(f"[+] Modified PE file saved as: {modified_filename}.")
return offset
2 DLL注入
DLL注入就是让进程加载我们构造的DLL文件,执行代码。与PE注入不同的是,DLL的注入是动态的,是在内存中进行操作的,相对来说复杂一些,网上提到最多的基本就是这四种,其他还有一些特殊利用以后遇到的话再补充。
参考了injectAllTheThings项目,本来是用Python写的,但是要处理32位程序的话不太方便,于是用C又写了一遍,没啥耐心了,大部分代码都是借鉴这个项目。
2.1 创建远程线程
CreateRemoteThread
通过在目标进程的内存中写入DLL的绝对路径,然后调用CreateRemoteThread函数,将LoadLibrary作为回调函数传入,内存中的DLL路径地址作为回调函数的参数,以此实现DLL的注入。
#include <windows.h>
#include <stdio.h>
#include "headers.h"
void demoCreateRemoteThread(LPCSTR pszDllPath, DWORD dwPid)
{
DWORD dwSize = strlen(pszDllPath);
// 打开进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (hProcess == NULL) {
printf("[!] Open process failed: %lu\n", GetLastError());
return;
}
//printf("Path Length:%d\n", dwSize);
// 开辟内存
LPVOID pszDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (pszDllAddr == NULL) {
printf("[!] VirtualAllocEx failed: %lu\n", GetLastError());
CloseHandle(hProcess);
return;
}
// 写入内存
if (!WriteProcessMemory(hProcess, pszDllAddr, pszDllPath, strlen(pszDllPath) + 1, NULL)) {
printf("[!] Write process memory failed: %lu\n", GetLastError());
return;
}
// 获取LoadLibraryA函数地址
PROC pfnProcAddr= GetProcAddress(GetModuleHandleA("Kernel32.dll"), "LoadLibraryA");
if (pfnProcAddr == NULL) {
printf("[!] GetProcAddress failed: %lu\n", GetLastError());
return;
}
// 创建远程线程
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pfnProcAddr, pszDllAddr, 0, NULL);
if (hThread == NULL) {
printf("[!] Create remote thread failed: %lu\n", GetLastError());
return;
}
printf("[+] Inject DLL by CreateRemoteThread success!\n");
CloseHandle(hThread);
VirtualFreeEx(hProcess, pszDllAddr, 0, MEM_RELEASE);
CloseHandle(hProcess);
}
NtCreateThreadEx
这是一个未公开的API,需要自己定义函数结构体:
typedef NTSTATUS(WINAPI* LPFUN_NtCreateThreadEx)
(
PHANDLE hThread,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
ULONG StackZeroBits,
ULONG SizeOfStackCommit,
ULONG SizeOfStackReserve,
LPVOID lpBytesBuffer
);
注入原理和CreateRemoteThread类似,也是通过创建远程线程:
#include <windows.h>
#include <stdio.h>
#include "headers.h"
void demoNtCreateThreadEx(LPCSTR pszDllPath, DWORD dwPid)
{
HANDLE hRemoteThread = NULL;
DWORD dwSize = strlen(pszDllPath);
// 打开进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (hProcess == NULL) {
printf("[!] Open process failed: %lu\n", GetLastError());
return;
}
// 开辟内存
LPVOID pszDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (pszDllAddr == NULL) {
printf("[!] VirtualAllocEx failed: %lu\n", GetLastError());
CloseHandle(hProcess);
return;
}
// 写入内存
if (!WriteProcessMemory(hProcess, pszDllAddr, pszDllPath, strlen(pszDllPath) + 1, NULL)) {
printf("[!] Write process memory failed: %lu\n", GetLastError());
return;
}
// 获取LoadLibraryA函数地址
PROC pfnProcAddr = GetProcAddress(GetModuleHandleA("Kernel32.dll"), "LoadLibraryA");
if (pfnProcAddr == NULL) {
printf("[!] GetProcAddress failed: %lu\n", GetLastError());
return;
}
// 获取NtCreateThreadEx函数地址
PROC pfnNtCreateThreadEx = GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtCreateThreadEx");
if (pfnNtCreateThreadEx == NULL) {
printf("[!] Get NtCreateThreadEx Address failed: %lu\n", GetLastError());
return;
}
LPFUN_NtCreateThreadEx funNtCreateThreadEx = (LPFUN_NtCreateThreadEx)pfnNtCreateThreadEx;
NTSTATUS status = funNtCreateThreadEx(&hRemoteThread, 0x1FFFFF, NULL, hProcess, (PTHREAD_START_ROUTINE)pfnProcAddr,
pszDllAddr, FALSE, NULL, NULL, NULL, NULL);
if (!status) {
printf("[!] Call NtCreateThreadEx failed: %lu\n", GetLastError());
return;
}
printf("[+] Inject DLL by NtCreateThreadEx success!\n");
VirtualFreeEx(hProcess, pszDllAddr, 0, MEM_RELEASE);
CloseHandle(hProcess);
}
RtlCreateUserThread
对NtCreateThreadEx函数的封装,也是未公开的API。
typedef DWORD(WINAPI* LPFUN_RtlCreateUserThread)
(
IN HANDLE ProcessHandle,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN BOOL CreateSuspended,
IN ULONG StackZeroBits,
IN OUT PULONG StackReserved,
IN OUT PULONG StackCommit,
IN LPVOID StartAddress,
IN LPVOID StartParameter,
OUT HANDLE ThreadHandle,
OUT LPVOID ClientID
);
#include <windows.h>
#include <stdio.h>
#include "headers.h"
void demoRtlCreateUserThread(LPCSTR pszDllPath, DWORD dwPid)
{
HANDLE hRemoteThread = NULL;
DWORD dwSize = strlen(pszDllPath);
// 打开进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (hProcess == NULL) {
printf("[!] Open process failed: %lu\n", GetLastError());
return;
}
// 开辟内存
LPVOID pszDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (pszDllAddr == NULL) {
printf("[!] VirtualAllocEx failed: %lu\n", GetLastError());
CloseHandle(hProcess);
return;
}
// 写入内存
if (!WriteProcessMemory(hProcess, pszDllAddr, pszDllPath, strlen(pszDllPath) + 1, NULL)) {
printf("[!] Write process memory failed: %lu\n", GetLastError());
return;
}
// 获取LoadLibraryA函数地址
PROC pfnProcAddr = GetProcAddress(GetModuleHandleA("Kernel32.dll"), "LoadLibraryA");
if (pfnProcAddr == NULL) {
printf("[!] GetProcAddress failed: %lu\n", GetLastError());
return;
}
PROC pfnRtlCreateUserThread = GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlCreateUserThread");
if (pfnRtlCreateUserThread == NULL) {
printf("[!] Get pfnRtlCreateUserThread Address failed: %lu\n", GetLastError());
return;
}
LPFUN_RtlCreateUserThread funRtlCreateUserThread = (LPFUN_RtlCreateUserThread)pfnRtlCreateUserThread;
DWORD status = funRtlCreateUserThread(hProcess, NULL, 0, 0, 0, 0,
pfnProcAddr, pszDllAddr, &hRemoteThread, NULL);
if (status!=0) {
printf("[!] Call RtlCreateUserThread failed: %lu\n", GetLastError());
return;
}
printf("[+] Inject DLL by RtlCreateUserThread success!\n");
VirtualFreeEx(hProcess, pszDllAddr, 0, MEM_RELEASE);
CloseHandle(hProcess);
}
2.2 线程劫持
QueueUserAPC
通过添加线程的APC队列,使得线程被再次执行时会调用APC队列中的函数。并不是每个线程都会被执行,在注入时可以向目标进程的所有线程中都注入一次,我测试的是Sublime_Text,注入所有线程后可以稳定触发。
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
#include "headers.h"
void demoQueueUserAPC(LPCSTR pszDllPath, DWORD dwPid)
{
DWORD dwSize = strlen(pszDllPath);
// 打开进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (hProcess == NULL) {
printf("[!] Open process failed: %lu\n", GetLastError());
return;
}
// 开辟内存
LPVOID pszDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (pszDllAddr == NULL) {
printf("[!] VirtualAllocEx failed: %lu\n", GetLastError());
CloseHandle(hProcess);
return;
}
// 写入内存
if (!WriteProcessMemory(hProcess, pszDllAddr, pszDllPath, strlen(pszDllPath) + 1, NULL)) {
printf("[!] Write process memory failed: %lu\n", GetLastError());
return;
}
// 获取LoadLibraryA函数地址
PROC pfnProcAddr = GetProcAddress(GetModuleHandleA("Kernel32.dll"), "LoadLibraryA");
if (pfnProcAddr == NULL) {
printf("[!] GetProcAddress failed: %lu\n", GetLastError());
return;
}
// 遍历线程
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (h == INVALID_HANDLE_VALUE) {
printf("[!] Error: Couldn't create snapshot.\n");
return;
}
THREADENTRY32 te;
te.dwSize = sizeof(te);
if (!Thread32First(h, &te)) {
printf("[!] Error: Couldn't get first thread.\n");
CloseHandle(h);
return;
}
DWORD dwThreadId = 0;
while (Thread32Next(h, &te)) {
if (te.th32OwnerProcessID == dwPid) {
dwThreadId = te.th32ThreadID;
HANDLE hThread = OpenThread(THREAD_SET_CONTEXT, FALSE, dwThreadId);
if (hThread == NULL) {
printf("[!] Error: Couldn't open thread %d.\n", dwThreadId);
return;
}
DWORD status = QueueUserAPC((PAPCFUNC)pfnProcAddr, hThread, (ULONG_PTR)pszDllAddr);
if (!status) {
printf("[!] Call QueueUserAPC Failed: %lu\n", GetLastError());
return;
}
printf("[+] Inject DLL by QueueUserAPC success in thread %d!\n", dwThreadId);
}
}
CloseHandle(h);
CloseHandle(hProcess);
}
SetThreadContext
将线程挂起后直接修改线程的上下文,将加载DLL的shellcode注入其中,然后恢复线程,在执行完shellcode后线程就会返回正常执行。
#include <windows.h>
#include <stdio.h>
#include "headers.h"
#ifndef _WIN64
BYTE sc[] =
{
0x68, 0xCC, 0xCC, 0xCC, 0xCC, 0x9C, 0x60, 0x68,
0xCC, 0xCC, 0xCC, 0xCC, 0xB8, 0xCC, 0xCC, 0xCC,
0xCC, 0xFF, 0xD0, 0x61, 0x9D, 0xC3
};
#else
BYTE sc[] =
{
0x50, 0x48, 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x9c, 0x51, 0x52, 0x53, 0x55,
0x56, 0x57, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56,
0x41, 0x57, 0x68, 0xEF, 0xBE, 0xAD, 0xDE, 0x48, 0xB9, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
0xCC, 0x48, 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xFF, 0xD0, 0x58, 0x41, 0x5F,
0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5F, 0x5E,
0x5D, 0x5B, 0x5A, 0x59, 0x9D, 0x58, 0xC3
};
#endif // !WIN_64
void demoSetThreadContext(LPCSTR pszDllPath, DWORD dwPid)
{
CONTEXT ctx;
DWORD dwSize = strlen(pszDllPath);
// 打开进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (hProcess == NULL) {
printf("[!] Open process failed: %lu\n", GetLastError());
return;
}
// 开辟内存
LPVOID pszDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (pszDllAddr == NULL) {
printf("[!] VirtualAllocEx failed: %lu\n", GetLastError());
CloseHandle(hProcess);
return;
}
// 写入内存
if (!WriteProcessMemory(hProcess, pszDllAddr, pszDllPath, strlen(pszDllPath) + 1, NULL)) {
printf("[!] Write process memory failed: %lu\n", GetLastError());
return;
}
// 获取LoadLibraryA函数地址
PROC pfnProcAddr = GetProcAddress(GetModuleHandleA("Kernel32.dll"), "LoadLibraryA");
if (pfnProcAddr == NULL) {
printf("[!] GetProcAddress failed: %lu\n", GetLastError());
return;
}
// 开辟内存,存放shellcode
LPVOID stub = VirtualAllocEx(hProcess, NULL, sizeof(sc), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (stub == NULL) {
printf("[!] VirtualAllocEx failed: %lu\n", GetLastError());
return;
}
// 获取线程句柄
DWORD dwThreadId = getThreadId(dwPid);
if (dwThreadId == NULL) {
return;
}
HANDLE hThread = OpenThread((THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME), false, dwThreadId);
if (hThread == NULL) {
printf("[!] Open thread failed: %lu\n", GetLastError());
return;
}
// 挂起线程,获取上下文
SuspendThread(hThread);
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(hThread, &ctx);
#ifndef _WIN64
DWORD dwOriginIP = ctx.Eip;
ctx.Eip = (DWORD)stub;
ctx.ContextFlags = CONTEXT_CONTROL;
// 构造shellcode,写入上下文
DWORD dwOriginProt = NULL;
VirtualProtect(sc, sizeof(sc), PAGE_EXECUTE_READWRITE, &dwOriginProt);
memcpy(sc + 1, &dwOriginIP, 4);
memcpy(sc + 8, &pszDllAddr, 4);
memcpy(sc + 13, &pfnProcAddr, 4);
#else
DWORD64 dwOriginIP = ctx.Rip;
ctx.Rip = (DWORD64)stub;
ctx.ContextFlags = CONTEXT_CONTROL;
// 构造shellcode,写入上下文
DWORD dwOriginProt = NULL;
VirtualProtect(sc, sizeof(sc), PAGE_EXECUTE_READWRITE, &dwOriginProt);
//printf("%p %p %p\n", dwOriginIP, pszDllAddr, pfnProcAddr);
memcpy(sc + 3, &dwOriginIP, 8);
memcpy(sc + 41, &pszDllAddr, 8);
memcpy(sc + 51, &pfnProcAddr, 8);
#ifdef _DEBUG
printf("shellcode:\n");
for (int i = 0; i < sizeof(sc); i++) {
printf("%02x ", sc[i]);
if (i % 16 == 0 || i == sizeof(sc) - 1) {
printf("\n");
}
}
#endif
#endif
WriteProcessMemory(hProcess, stub, sc, sizeof(sc), NULL);
SetThreadContext(hThread, &ctx);
// 恢复线程
ResumeThread(hThread);
printf("[+] Inject DLL by SetThreadContext success!\n");
CloseHandle(hProcess);
CloseHandle(hThread);
}
2.3 消息Hook
SetWindowsHookEx
此函数在调用时会传入一个钩子函数,而在对其他进程或全部进程进行Hook时,要求此函数必须位于DLL中,所以只要我们构造的DLL中包含要Hook的函数就可以完成注入。
#include <stdio.h>
#include <Windows.h>
#include <tlhelp32.h>
#include "headers.h"
void demoSetWindowsHookEx(LPCSTR pszDllPath, DWORD dwProcessId, LPCSTR strProcName)
{
// 获取第一个线程ID
DWORD dwThreadId = getThreadId(dwProcessId);
if (dwThreadId == 0) {
printf("[!] Get thread ID failed!\n");
return;
}
// 加载DLL和导出的函数
HMODULE dll = LoadLibraryExA(pszDllPath, NULL, DONT_RESOLVE_DLL_REFERENCES);
if (dll == NULL)
{
printf("[!] Open Dll failed!\n");
return;
}
HOOKPROC hookProc = (HOOKPROC)GetProcAddress(dll, strProcName);
if (hookProc == NULL) {
printf("[!] Get proc of the dll failed!\n");
return;
}
// Hook键盘消息
HHOOK hHook = SetWindowsHookEx(WH_KEYBOARD, hookProc, dll, dwThreadId);
if (hHook == NULL) {
printf("[!] Call SetWindowsHookEx failed!\n");
return;
}
printf("[+] Inject DLL by SetWindowsHookEx success!\n");
printf("[*] Press any key to unhook:");
getchar();
if (!UnhookWindowsHookEx(hHook)) {
printf("[!] Failed to unhook\n");
}
else {
printf("[+] Unhook success!\n");
}
FreeLibrary(dll);
}
2.4 反射注入
TODO
3 DLL劫持
DLL劫持是在攻防中会用到的手段,在这里只探讨关于特定程序的简单DLL劫持检测,而不是对系统存在的DLL劫持风险进行检测。
我参考DLLHSC项目使用Python实现了简单的DLL劫持测试,没有实现Hook LoadLibrary函数进行动态检测的部分。
首先从注册表中获取到系统的KnownDLLs列表,凡是在此列表中存在的DLL都是无法被劫持的。
然后获取到程序运行后加载的所有DLL名称(不只是导入表中的,因为导入的DLL还会导入其他DLL),同时排除掉KnownDLLs列表中已经存在的DLL以及WinSxS目录下的DLL(因为系统会对目录下的DLL加载进行安全验证)。
最后将准备好的payload.dll置于程序同一目录下,这个DLL会在加载时在当前目录创建一个temp文件,然后可以通过判断是否存在此文件来验证DLL是否被加载。我们不断地重命名payload.dll为已经获取到的DLL列表中的名称,如果成功创建文件则表明存在DLL劫持;如果程序崩溃则说明payload.dll可能没有实现原先DLL中的某些功能;如果程序正常启动,则认为此DLL不存在劫持。
3.1 获取DLL列表
使用psutil可以直接获取到进程加载的DLL。
known_dlls = []
import_dlls = []
load_dlls = []
def get_known_dlls():
"""
获取系统的KnownDlls列表
:return:
""" access_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs")
try:
i = 0
while True:
dll_name = winreg.EnumValue(access_key, i)[1].lower()
# print(dll_name)
known_dlls.append(dll_name)
i += 1
except Exception as e:
print("[+] Read registry finished:", e)
finally:
winreg.CloseKey(access_key)
def get_import_dlls(fileName):
"""
显示导入表中可能被利用的DLL
:return:
""" pe = PE(fileName)
import_table = pe.DIRECTORY_ENTRY_IMPORT
for i in import_table:
dll_name = i.dll.decode().lower()
if dll_name not in known_dlls:
import_dlls.append(dll_name)
def get_load_dlls(pid):
"""
显示载入时加载的DLL
:return:
""" try:
proc = psutil.Process(pid)
for mem in proc.memory_maps():
if (mem.path.endswith('.dll') or mem.path.endswith('.drv')) and 'WinSxS' not in mem.path:
begin = mem.path.rfind('\\') + 1
dll_name = mem.path[begin:]
if dll_name not in known_dlls and dll_name not in load_dlls:
load_dlls.append(dll_name)
except Exception as e:
print("[!] ", e)
3.2 劫持测试
def test_hijack(filename, dlls):
"""
使用恶意DLL进行劫持测试
:return:
""" for dll in dlls:
os.rename('./files/payload32.dll', './files/'+dll)
proc = psutil.Popen(filename)
time.sleep(0.5)
if os.path.exists('./temp_4396'):
print(f"[+] {dll} for {filename} can be hijacked!")
os.remove('./temp_4396')
try:
proc.terminate()
except psutil.NoSuchProcess:
print(f"[*] {dll} for {filename} may be hijacked!")
finally:
os.rename('./files/'+dll, './files/payload32.dll')
对Windows XP下的notepad.exe进行测试的结果如下:
4 API Hook
网上对于Hook的分类有很多,上面在DLL注入中已经介绍了消息Hook,所以在这里就只介绍Ring3中的API Hook,即对系统DLL中的API函数进行挂钩。网上也有称之为Inject Hook的,但是我感觉还是API Hook最合适。
访问其他进程的最简单方式就是DLL,所以Hook通常也是通过DLL注入实现的。我们在DLL中设置好Hook函数,在DLL加载时就进行挂钩,DLL卸载时进行脱钩就可以实现完整的Hook过程。
此部分的代码很多都参考了selph师傅的博客,只实现了32位程序的Hook。
4.1 Inline Hook
修改API函数地址的开头的机器码为JMP跳转指令,如果还需要使用原有的API函数,则需要先Unhook,调用完后再重新Hook。
#include <Windows.h>
BYTE originOpcode[5] = { 0 };
void InstallHook(PROC hookAddr);
void UninstallHook(void);
int WINAPI Hook(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
BOOL WINAPI DllMain(HMODULE hModule, DWORD fdwReason, LPVOID lpReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
InstallHook((PROC)Hook);
MessageBoxA(NULL, NULL, "InstallHook!", MB_OK);
break;
case DLL_PROCESS_DETACH:
UninstallHook();
break;
}
return TRUE;
}
void InstallHook(PROC hookAddr)
{
BYTE hookOpcode[5] = { 0xE9, 0 };
//HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, GetCurrentProcessId());
HANDLE hProcess = GetCurrentProcess();
// 1. 获取API函数地址
PROC procAddr = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
// 2. 修改内存属性
DWORD originFlags = 0;
VirtualProtectEx(hProcess, procAddr, 5, PAGE_EXECUTE_READWRITE, &originFlags);
// 3. 读取原机器码,保存
ReadProcessMemory(hProcess, procAddr, originOpcode, 5, NULL);
// 4. 构造跳转机器码,写入
DWORD jmpAddr = (DWORD)hookAddr - (DWORD)procAddr - 5;
memcpy(&hookOpcode[1], &jmpAddr, 4);
WriteProcessMemory(hProcess, procAddr, hookOpcode, 5, NULL);
// 5. 恢复内存属性
VirtualProtectEx(hProcess, procAddr, 5, originFlags, NULL);
}
void UninstallHook(void)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, GetCurrentProcessId());
// 1. 获取API函数地址
PROC procAddr = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
// 2. 修改内存属性
DWORD origin_flags = 0;
VirtualProtectEx(hProcess, procAddr, 5, PAGE_EXECUTE_READWRITE, &origin_flags);
// 3. 将原来的机器码写入
WriteProcessMemory(hProcess, procAddr, originOpcode, 5, NULL);
// 4. 恢复内存属性
VirtualProtectEx(hProcess, procAddr, 5, origin_flags, NULL);
}
int WINAPI Hook(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
UninstallHook();
MessageBoxA(NULL, NULL, "Hooked!", MB_OK);
InstallHook((PROC)Hook);
return 0;
}
HotFix
如果想要Hook的API函数的地址前有至少5字节空闲区域可以被利用,则可以在其地址前的5个字节构造JMP指令,在函数地址处构造机器码0xEB, 0xF9跳转到-7字节,即函数前5字节JMP指令,那么正常的API调用就会跳转到JMP指令上。如果我们需要使用原有的API函数,只需要调用函数地址+2就可以了。
#include <Windows.h>
BYTE originOpcode[7] = { 0 };
void InstallHook(PROC hookAddr);
void UninstallHook(void);
int WINAPI Hook(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
BOOL WINAPI DllMain(HMODULE hModule, DWORD fdwReason, LPVOID lpReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
InstallHook((PROC)Hook);
MessageBoxA(NULL, NULL, "InstallHook!", MB_OK);
break;
case DLL_PROCESS_DETACH:
UninstallHook();
break;
}
return TRUE;
}
void InstallHook(PROC hookAddr)
{
BYTE hookOpcode[7] = { 0xE9, 0, 0, 0, 0, 0xEB, 0xF9 };
HANDLE hProcess = GetCurrentProcess();
// 1. 获取API函数地址, 计算Hook的起始地址
PROC procAddr = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
PBYTE beginAddr = (PBYTE)procAddr - 5;
// 2. 读取原来的机器码
DWORD originFlags = 0;
VirtualProtectEx(hProcess, beginAddr, 7, PAGE_EXECUTE_READWRITE, &originFlags);
ReadProcessMemory(hProcess, beginAddr, originOpcode, 7, NULL);
DWORD jmpAddr = (DWORD)hookAddr - (DWORD)procAddr; // 此处加5减5正好抵消了
// 3. 构造跳转机器码,写入
memcpy(&hookOpcode[1], &jmpAddr, 4);
WriteProcessMemory(hProcess, beginAddr, hookOpcode, 7, NULL);
VirtualProtectEx(hProcess, procAddr, 7, originFlags, NULL);
}
void UninstallHook(void)
{
HANDLE hProcess = GetCurrentProcess();
PROC procAddr = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
PBYTE beginAddr = (PBYTE)procAddr - 5;
DWORD originFlags = 0;
VirtualProtectEx(hProcess, beginAddr, 7, PAGE_EXECUTE_READWRITE, &originFlags);
WriteProcessMemory(hProcess, beginAddr, originOpcode, 7, NULL);
VirtualProtectEx(hProcess, procAddr, 7, originFlags, NULL);
}
int WINAPI Hook(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
typedef int (WINAPI* MessageBoxA_t)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
PROC procAddr = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
PBYTE endAddr = (PBYTE)procAddr + 2;
MessageBoxA_t HookMessageBoxA = (MessageBoxA_t)endAddr;
HookMessageBoxA(NULL, NULL, "Hooked!", MB_OK);
return 0;
}
4.2 IAT Hook
此方法是修改加载到内存中的PE结构中的IAT表,此时IAT表存储的已经是函数的地址了,我们只需要遍历IAT,找到要Hook的DLL中对应的API函数地址然后进行修改即可。此时并不会修改DLL中的IAT,所以原有的API函数依然可以正常使用。
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
void InstallHook(LPCSTR DLLName, PROC procAddr, PROC hookAddr);
int WINAPI Hook(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
BOOL WINAPI DllMain(HMODULE hModule, DWORD fdwReason, LPVOID lpReserved)
{
PROC procAddr = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
PROC hookAddr = (PROC)Hook;
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
InstallHook("user32.dll", procAddr, hookAddr);
MessageBoxA(NULL, NULL, "InstallHook!", MB_OK);
break;
case DLL_PROCESS_DETACH:
InstallHook("user32.dll", hookAddr, procAddr);
break;
}
return TRUE;
}
void InstallHook(LPCSTR DLLName, PROC procAddr, PROC hookAddr)
{
HMODULE hModule = GetModuleHandle(NULL);
// 解析PE
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((SIZE_T)hModule + pDosHeader->e_lfanew);
DWORD szImageBase = pNtHeader->OptionalHeader.ImageBase;
DWORD szImpRva = (DWORD)pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
PIMAGE_IMPORT_DESCRIPTOR pImpDes = (PIMAGE_IMPORT_DESCRIPTOR)(szImageBase + szImpRva);
// 变量导入表
while (pImpDes->Name) {
DWORD szNameAddr = szImageBase + pImpDes->Name;
char szName[MAXBYTE] = { 0 };
strcpy_s(szName, (char*)szNameAddr);
if (strcmp(_strlwr(szName), DLLName) == 0) {
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)(pImpDes->FirstThunk + (DWORD)hModule);
while (pThunk->u1.Function) {
if (pThunk->u1.Function == (DWORD)procAddr) {
//修改访问权限
DWORD dwOldProtect;
VirtualProtectEx(GetCurrentProcess(), (LPVOID)&pThunk->u1.Function, 8, PAGE_EXECUTE_READWRITE, &dwOldProtect);
//修改IAT
pThunk->u1.Function = (DWORD)hookAddr;
VirtualProtectEx(GetCurrentProcess(), (LPVOID)&pThunk->u1.Function, 8, dwOldProtect, NULL);
break;
}
pThunk++;
}
break;
}
pImpDes++;
}
}
int WINAPI Hook(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
MessageBoxA(NULL, NULL, "Hooked!", MB_OK);
return 0;
}
4.3 Debug Hook
使用Win32 API 实现一个简单的调试器,在API 函数地址处构造INT3断点,中断后调用Hook函数。感觉没啥太大意义。
#include <Windows.h>
#include <stdio.h>
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE originOpcode = 0;
BYTE intOpcode = 0xCC;
void InstallHook(PROC procAddr, LPDEBUG_EVENT pde);
void UninstallHook(PROC procAddr);
void Hook(PROC procAddr, LPDEBUG_EVENT pde);
void DebugLoop(void);
int main()
{
DebugActiveProcess(27464);
DebugLoop();
return 0;
}
void InstallHook(PROC procAddr, LPDEBUG_EVENT pde)
{
memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
ReadProcessMemory(g_cpdi.hProcess, procAddr, &originOpcode, sizeof(BYTE), NULL);
WriteProcessMemory(g_cpdi.hProcess, procAddr, &intOpcode, sizeof(BYTE), NULL);
printf("[+] 已添加断点!\n");
}
void UninstallHook(PROC procAddr)
{
WriteProcessMemory(g_cpdi.hProcess, procAddr, &originOpcode, sizeof(BYTE), NULL);
printf("[+] 已取消断点!\n");
}
void Hook(PROC procAddr, LPDEBUG_EVENT pde)
{
PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
CONTEXT ctx;
SIZE_T titleAddr;
char title[] = "Hooked";
if (per->ExceptionCode == EXCEPTION_BREAKPOINT) {
if (per->ExceptionAddress == procAddr) {
printf("[+] 程序已中断!\n");
// 1. 解除hook
UninstallHook(procAddr);
// 2. 获取线程上下文
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);
// 3. 更改参数值
ReadProcessMemory(g_cpdi.hProcess, (LPCVOID)(ctx.Esp + 12), &titleAddr, sizeof(SIZE_T), NULL);
//printf("[-] ESP地址:%ph\n", ctx.Esp);
//printf("[-] MSG函数参数为:%x\n", &titleAddr);
DWORD dwOldProtect;
VirtualProtectEx(g_cpdi.hProcess, (LPVOID)titleAddr, 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);
WriteProcessMemory(g_cpdi.hProcess, (LPVOID)titleAddr, (LPVOID)title, 7, NULL);
VirtualProtectEx(g_cpdi.hProcess, (LPVOID)titleAddr, 7, dwOldProtect, NULL);
// 4. 还原EIP指针
ctx.Eip = (SIZE_T)procAddr;
SetThreadContext(g_cpdi.hThread, &ctx);
Sleep(0);
// 5. 恢复运行进程
ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
// 6. Hook
WriteProcessMemory(g_cpdi.hProcess, procAddr, &intOpcode, sizeof(BYTE), NULL);
}
}
}
void DebugLoop(void)
{
PROC procAddr = GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");
printf("[+] 已附加到程序!\n");
printf("[+] Hook API地址:%ph\n", procAddr);
DEBUG_EVENT de; //调试事件
//等待调试事件的发生
while (WaitForDebugEvent(&de, INFINITE)) {//等待调试信息,收到调试信息立即返回进入循环
// 被调试进程生成或附加事件,被调试进程启动或者附加时执行
if (CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode) {
InstallHook(procAddr, &de);
}
//异常事件调试,负责处理INT3中断事件
else if (EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode) {
Hook(procAddr, &de);
}
//被调试进程终止事件,进程中止后,调试器也结束
else if (EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode) {
break;
}
//再次运行被调试者
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
}
}
5 写在最后
这是一篇入门性质的文章,列举了Windows系统中的一些安全问题。我在写这篇文章之前惊奇的发现我做的这些实验竟然都在《逆向工程核心原理》一书中的前几章中提到了,所以就把这4部分放在一起做了总结。
第一篇文章,写的很仓促很潦草,如果有任何错误和遗漏恳请大家的指正和补充。