原文:https://www.pediy.com/kssd/pediy07/pediy7-437.htm
program Loader;
(***********************************)
(* *)
(* Memory Access Breakpoint Loader *)
(* by tt.t *)
(* *)
(* *)
(***********************************)
{
目标:拦截对指定地址的写操作,nop掉写地址的指令。
难点:在要补丁地址设Memory Write Breakpoint,查了半天没找到现成的东西,只好自己写。
思路:将要补丁的地址设为不可写,当写时会发生AV错误,然后进行Patch。但VirtualProtectEx会将整
个Page设为不可写属性,所有写Page的操作都会产生Access violation,无法直接找到要Patch的代码。
解决方法:要保存地址所在Page的地址范围,发生AV错误时判断是否因为VirtualProtect产生的Exceptio
n,如不是将Page的属性设为可写,同时设置单步标志,写操作完成后恢复Page为不可写属性,继续执
行,直至找到需Patch代码。
不知道有没有更好的方法。
}
uses
JwaWinType,
JwaWinNt,
JwaWinBase,
JwaWinUser;
{$R *.res}
var
si: STARTUPINFO;
pi: PROCESS_INFORMATION;
function MyExtractFilePath(f: String): String;
var
i, l: integer;
begin
l := Length(f);
for i := l downto 1 do
if f[i] = '' then Break;
result := copy(f, 0, i);
end;
procedure CreateVictimProcess(Path: String);
const
Nop: PChar = Chr($90) + Chr($90) + Chr($90);
var
DbgEvent: TDebugEvent;
DbgParam: DWORD;
OldPrt, NewPrt: DWORD;
pPatch: PByte;
PgMin, PgMax: DWORD;
MemInfo: TMemoryBasicInformation;
WExpAddr: DWORD;
DbgContext: TContext;
rm: Boolean;
hThread: DWORD;
begin
ZeroMemory(@si, sizeof(STARTUPINFO));
si.cb := sizeof(STARTUPINFO);
if not CreateProcess(PChar(Path), nil, nil, nil, False, CREATE_SUSPENDED or
CREATE_DEFAULT_ERROR_MODE, nil,
PChar(MyExtractFilePath(Path)), si, pi) then
begin
MessageBox(0, 'CreateProcess failed! ', 'Error!', 0);
exit;
end;
ResumeThread(pi.hThread);
if WaitForInputIdle(pi.hProcess, INFINITE) <> 0 then
begin
MessageBox(0, 'WaitForInputIdle failed! ', 'Error!', 0);
exit;
end;
if not DebugActiveProcess(pi.dwProcessId) then
begin
MessageBox(0, 'DebugActiveProcess failed! ', 'Error!', 0);
exit;
end;
if VirtualQueryEx(pi.hProcess, Pointer($9c66BC), MemInfo, SizeOf(MemInfo)) = 0 then
begin
MessageBox(0, 'VirtualQueryEx failed! ', 'Error!', 0);
exit;
end;
PgMin := DWORD(MemInfo.BaseAddress);
pgMax := PgMin + MemInfo.RegionSize;
{VirtualProtect会将整个Page设为不可写属性,所有写Page的操作都会产生Access violation,要保存
整个Page的地址范围,
后面可以判断是否因为VirtualProtect产生的Exception}
if not VirtualProtectEx(pi.hProcess, Pointer($9c66BC), 1, PAGE_EXECUTE_READ, @OldPrt) then
begin
MessageBox(0, 'VirtualProtectEx failed! ', 'Error!', 0);
exit;
end;
{改写Page属性为不可写}
rm := false;
while WaitForDebugEvent(DbgEvent, INFINITE) do
begin
DbgParam := DBG_CONTINUE;
case DbgEvent.dwDebugEventCode of
EXCEPTION_DEBUG_EVENT:
begin
if DbgEvent.Exception.ExceptionRecord.ExceptionCode <> EXCEPTION_BREAKPOINT then
case DbgEvent.Exception.ExceptionRecord.ExceptionCode of
EXCEPTION_SINGLE_STEP: {单步中断}
begin
if rm then {由于EXCEPTION_ACCESS_VIOLATION产生的单步中断,恢复Page为不可写属性}
begin
rm := false;
VirtualProtectEx(pi.hProcess, Pointer($9c66BC), 1, PAGE_EXECUTE_READ, @NewPrt);
end;
end;
EXCEPTION_ACCESS_VIOLATION: {AV中断}
begin
DbgParam := DBG_EXCEPTION_NOT_HANDLED;
if DbgEvent.Exception.ExceptionRecord.ExceptionInformation[0] = 1 then {写操作}
begin
WExpAddr := DbgEvent.Exception.ExceptionRecord.ExceptionInformation[1]; {写操作的目标地
址}
if (WExpAddr >= PgMin) and (WExpAddr <= PgMax) then {目标地址在Page范围}
begin
DbgParam := DBG_CONTINUE;
if(WExpAddr <> $9c66BC) then {不是写指定地址}
begin
VirtualProtectEx(pi.hProcess, Pointer($9c66BC), 1, OldPrt, @NewPrt);
DbgContext.ContextFlags := CONTEXT_CONTROL;
hThread := OpenThread(THREAD_ALL_ACCESS, false, DbgEvent.dwThreadId);
GetThreadContext(hThread, DbgContext);
DbgContext.EFlags := DbgContext.EFlags or $100;
{设单步标志,会触发EXCEPTION_SINGLE_STEP}
SetThreadContext(hThread, DbgContext);
rm := true; {标志,表明是EXCEPTION_ACCESS_VIOLATION产生的单步中断}
end
else
begin {Patch}
pPatch := DbgEvent.Exception.ExceptionRecord.ExceptionAddress;
VirtualProtectEx(pi.hProcess, pPatch, 3, PAGE_READWRITE, @NewPrt);
WriteProcessMemory(pi.hProcess, pPatch, Nop, 3, nil);
VirtualProtectEx(pi.hProcess, pPatch, 3, NewPrt, @NewPrt);
end;
end;
end;
end;
else
DbgParam := DBG_EXCEPTION_NOT_HANDLED;
end;
end;
EXIT_PROCESS_DEBUG_EVENT:
begin
ContinueDebugEvent(DbgEvent.dwProcessId, DbgEvent.dwThreadId, DbgParam);
Break;
end;
end;
ContinueDebugEvent(DbgEvent.dwProcessId, DbgEvent.dwThreadId, DbgParam);
end;
end;
var
Victim: string;
begin
Victim := MyExtractFilePath(ParamStr(0)) + 'Wise***.exe'; //受害程序
CreateVictimProcess(Victim);
halt;
end.
ft,既然看不懂那就那就详细注释下。
记得要大体了解下deubg api的用法和使用流程先.
procedure CreateVictimProcess(Path: String);
const
Nop: PChar = Chr($90) + Chr($90) + Chr($90);
var
DbgEvent: TDebugEvent;
DbgParam: DWORD; //ContinueDebugEvent用,标志如何处理调试消息
OldPrt, NewPrt: DWORD;
pPatch: PByte;
PgMin, PgMax: DWORD;
MemInfo: TMemoryBasicInformation;
WExpAddr: DWORD;
DbgContext: TContext;
rm: Boolean;
hThread: DWORD;
begin
ZeroMemory(@si, sizeof(STARTUPINFO));
si.cb := sizeof(STARTUPINFO);
if not CreateProcess(PChar(Path), nil, nil, nil, False, CREATE_SUSPENDED or CREATE_DEFAULT_ERROR_MODE, nil,
PChar(MyExtractFilePath(Path)), si, pi) then
begin
MessageBox(0, 'CreateProcess failed! ', 'Error!', 0);
exit;
end;
//建立目标进程
ResumeThread(pi.hThread);
//恢复进程执行.其实CreateProcess时不加CREATE_SUSPENDED标志就可以省掉这句
if WaitForInputIdle(pi.hProcess, INFINITE) <> 0 then
begin
MessageBox(0, 'WaitForInputIdle failed! ', 'Error!', 0);
exit;
end;
//等待目标进程完全运行至其开始等待用户输入.
//因为目标程序是加过壳的,壳的部分会检查调试器,所以等到壳运行结束在去debug它.
if not DebugActiveProcess(pi.dwProcessId) then
begin
MessageBox(0, 'DebugActiveProcess failed! ', 'Error!', 0);
exit;
end;
//挂上目标程序
if VirtualQueryEx(pi.hProcess, Pointer($9c66BC), MemInfo, SizeOf(MemInfo)) = 0 then
begin
MessageBox(0, 'VirtualQueryEx failed! ', 'Error!', 0);
exit;
end;
//查询需要设内存断点的地址($9c66BC,也就是0x9c66BC,后面简称addr)所在page的情况
PgMin := DWORD(MemInfo.BaseAddress);
pgMax := PgMin + MemInfo.RegionSize;
//得到addr所在的page的始末地址.
{因为VirtualProtect会将addr所在整个Page设为不可写属性(见VirtualProtect的API说明),所有写Page的操作,即使不是写我们感兴趣(但位于那个page上)的地址的操作,都会因为将addr设为不可写属性而产生Access violation.所以这里要保存整个Page的地址范围,以便后面判断AV是不是因写addr而产生的}
if not VirtualProtectEx(pi.hProcess, Pointer($9c66BC), 1, PAGE_EXECUTE_READ, @OldPrt) then
begin
MessageBox(0, 'VirtualProtectEx failed! ', 'Error!', 0);
exit;
end;
{改写addr属性为不可写.这里会将addr所在的整个Page设为不可写属性}
rm := false; {标志是不是因处理AV设置的单步中断产生的中断}
while WaitForDebugEvent(DbgEvent, INFINITE) do //等待调试消息
begin
DbgParam := DBG_CONTINUE; //默认处理调试消息
case DbgEvent.dwDebugEventCode of
EXCEPTION_DEBUG_EVENT:
begin
if DbgEvent.Exception.ExceptionRecord.ExceptionCode <> EXCEPTION_BREAKPOINT then
//判断是不是int 3产生的中断.因为DebugActiveProcess挂上程序后会向debuger发出一个EXCEPTION_BREAKPOINT调试消息,这里忽略它
case DbgEvent.Exception.ExceptionRecord.ExceptionCode of //判断异常类型
EXCEPTION_ACCESS_VIOLATION: {AV异常}
begin
DbgParam := DBG_EXCEPTION_NOT_HANDLED; //表示由目标程序的SEH处理异常
if DbgEvent.Exception.ExceptionRecord.ExceptionInformation[0] = 1 then
begin {是写操作产生的AV}
WExpAddr := DbgEvent.Exception.ExceptionRecord.ExceptionInformation[1]; {WExpAddr =写操作的目标地址}
if (WExpAddr >= PgMin) and (WExpAddr <= PgMax) then {WExpAddr在addr所在Page上}
begin
DbgParam := DBG_CONTINUE;//表示我们来处理异常,让目标程序从发生异常处继续执行.
if(WExpAddr <> $9c66BC) then {WExpAddr不是写addr,要允许目标程序写操作}
begin
VirtualProtectEx(pi.hProcess, Pointer($9c66BC), 1, OldPrt, @NewPrt);//恢复addr所在page为原来的属性(可写)
DbgContext.ContextFlags := CONTEXT_CONTROL;
hThread := OpenThread(THREAD_ALL_ACCESS, false, DbgEvent.dwThreadId);
//由Thread Id得到Thread handle
GetThreadContext(hThread, DbgContext);
//得到thread的Context,设置单步执行标志,让写操作完成后发生单步中断,以便将addr重新设为不可写,拦截下一次写操作.
DbgContext.EFlags := DbgContext.EFlags or $100; {设单步标志}
SetThreadContext(hThread, DbgContext);
//设置thread的Context
rm := true; {标志,表明是我们处理AV而产生的单步中断}
end
else //{WExpAddr是写addr,进行Patch}
begin
pPatch := DbgEvent.Exception.ExceptionRecord.ExceptionAddress;
//写地址操作语句地址
VirtualProtectEx(pi.hProcess, pPatch, 3, PAGE_READWRITE, @NewPrt);
//设操作语句地址为可写
WriteProcessMemory(pi.hProcess, pPatch, Nop, 3, nil);
//nop掉写地址语句
VirtualProtectEx(pi.hProcess, pPatch, 3, NewPrt, @NewPrt);
//恢复语句地址属性
end;
end;
end;
end;{AV异常处理完毕}
EXCEPTION_SINGLE_STEP: {发生单步中断}
begin
if rm then {由于处理AV产生的单步中断.}
begin
rm := false;
//重置标志
VirtualProtectEx(pi.hProcess, Pointer($9c66BC), 1, PAGE_EXECUTE_READ, @NewPrt);
//恢复Page为不可写属性,以便拦截下次写操作
end;
end;
else//不是AV中断或单步中断,由目标程序的SEH处理异常
DbgParam := DBG_EXCEPTION_NOT_HANDLED;
end;
end;
EXIT_PROCESS_DEBUG_EVENT:
begin
ContinueDebugEvent(DbgEvent.dwProcessId, DbgEvent.dwThreadId, DbgParam);
//目标程序推出,loader使命结束,退出
Break;
end;
end;
ContinueDebugEvent(DbgEvent.dwProcessId, DbgEvent.dwThreadId, DbgParam);
end;
end;