 闽公网安备 35020302035485号
                
                闽公网安备 35020302035485号
                    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void button1_Click(object sender, EventArgs e)
        {  
             // 堆代码 duidaima.com
            System.IO.File.WriteAllText("C:\\1.txt", DateTime.Now.ToString());
        }
    }
代码非常简单,点一下按钮就写一条时间日志,接下来分别用 WinDbg 和 自定义调试器 来截获这个时间。BOOL WriteFile( [in] HANDLE hFile, [in] LPCVOID lpBuffer, [in] DWORD nNumberOfBytesToWrite, [out, optional] LPDWORD lpNumberOfBytesWritten, [in, out, optional] LPOVERLAPPED lpOverlapped );其中 lpBuffer 存放的就是 content 信息, nNumberOfBytesToWrite 存放的是长度,有了这些基础,就可以通过 bp 下断点了。
0:007> bp kernel32!WriteFile
0:007> g
Breakpoint 0 hit
eax=0126a4e8 ebx=00000000 ecx=000004a0 edx=76663510 esi=0320eb6c edi=010feaa8
eip=76663510 esp=010fea24 ebp=010fea90 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
KERNEL32!WriteFile:
76663510 ff2558106c76    jmp     dword ptr [KERNEL32!_imp__WriteFile (766c1058)] ds:002b:766c1058={KERNELBASE!WriteFile (75ebd760)}
0:000> kb 3
 # ChildEBP RetAddr      Args to Child              
00 010fea90 6a829fef     00000000 010feaa8 00000013 KERNEL32!WriteFile
01 010feab8 6a829f2c     010fead4 00000000 00000013 mscorlib_ni!System.IO.FileStream.WriteFileNative+0x6f
02 010feae0 6a829ec5     00000013 00000000 0320d69c mscorlib_ni!System.IO.FileStream.WriteCore+0x3c 
因为 kernel32!WriteFile 用的是 stdcall 协定,所以 lpBuffer 变量在 esp+0x8 的位置, nNumberOfBytesToWrite 变量在 esp+0xc 的位置。0:000> da poi(esp+8) 0320eb6c "2022/11/24 17:25:39" 0:000> dp esp+0xc L1 010fea30 00000013 0:000> ? poi(esp+0xc) Evaluate expression: 19 = 00000013从卦中看,content 和 length 都出来了,非常完美,接下来看下如何自定义实现调试器。
0:011> x kernel32!WriteFile 76663510 KERNEL32!WriteFile (_WriteFile@20) 0:011> db 76663510 L1 76663510 ff . 0:011> eb 76663510 cc 0:011> db 76663510 L1 76663510 cc.


刚才看到的是中断给WinDbg,那怎么中断给程序呢?其实 Win32 API 中有一个叫 DebugActiveProcess 函数可以让宿主程序充当调试器,mdsn 中的描述如下:
	 
// HookDebug.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <iostream>
#include <Windows.h>
LPVOID writefile_addr = NULL;
CREATE_PROCESS_DEBUG_INFO cpdi;
BYTE int3 = 0xCC;
BYTE ff = 0;
BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde) {
 writefile_addr = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "WriteFile");
 memcpy(&cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
 ReadProcessMemory(cpdi.hProcess, writefile_addr, &ff, sizeof(BYTE), NULL);
 WriteProcessMemory(cpdi.hProcess, writefile_addr, &int3, sizeof(BYTE), NULL);
 return TRUE;
}
BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde) {
 CONTEXT ctx;
 PBYTE lpBuffer = NULL;
 DWORD lpBufferStart, nNumberOfBytesToWrite;
 PEXCEPTION_RECORD pr = &pde->u.Exception.ExceptionRecord;
 //int3 断点
 if (pr->ExceptionCode == EXCEPTION_BREAKPOINT && writefile_addr == pr->ExceptionAddress) {
  //1. unhook,恢复 writefile 的
  WriteProcessMemory(cpdi.hProcess, writefile_addr, &ff, sizeof(BYTE), NULL);
  //2. 获取上下文
  ctx.ContextFlags = CONTEXT_ALL;
  GetThreadContext(cpdi.hThread, &ctx);
  //3. 获取 WriteFile 写入的内容
  ReadProcessMemory(cpdi.hProcess, (PVOID)(ctx.Esp + 0x8), &lpBufferStart, sizeof(DWORD), NULL);
  ReadProcessMemory(cpdi.hProcess, (PVOID)(ctx.Esp + 0xc), &nNumberOfBytesToWrite, sizeof(DWORD), NULL);
  //4. 分配缓冲区
  lpBuffer = (PBYTE)calloc(nNumberOfBytesToWrite + 1, sizeof(BYTE));
  //5. copy 数据到缓冲区中
  ReadProcessMemory(cpdi.hProcess, (LPVOID)lpBufferStart, lpBuffer, nNumberOfBytesToWrite, NULL);
  printf("截获的内容: %s \n", lpBuffer);
  //6. 重新修改 eip ,指向 writefile 开头,写回到线程上下文中
  ctx.Eip = (DWORD)writefile_addr;
  SetThreadContext(cpdi.hThread, &ctx);
  //7. 继续执行
  ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
  Sleep(0);
  //8. 重新 hook
  WriteProcessMemory(cpdi.hProcess, writefile_addr, &int3, sizeof(BYTE), NULL);
  return TRUE;
 }
 return FALSE;
}
void loop() {
 DEBUG_EVENT de;
 while (WaitForDebugEvent(&de, INFINITE))
 {
  //注入事件
  if (de.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
   OnCreateProcessDebugEvent(&de);
  }
  //异常事件
  if (de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) {
   if (OnExceptionDebugEvent(&de)) continue;
  }
  ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
 }
}
int main()
{
 //程序日志
 DWORD dwPID = 23264;
 if (!DebugActiveProcess(dwPID)) {
  printf("fail");
  return 1;
 }
 loop();
 return 0;
}
代码中的 dwPID 是 WPF 程序的 PID,指定好之后把程序跑起来,点击 button 按钮观察,截图如下,非常完美。
