调试方式:场景是调试本地程序,用调试方式可以对被调用进程的完全访问。
基本原理:在记事本调用WriteFile之前设置断点(0xcc也就是int 3)引发调试异常(EXCEPTION_BACKPOIT事件)修改掉应该传入的参数(要保存数据的内容和大小)再恢复程序原本的状态。
main函数
#include"windows.h"
#include"stdio.h"
LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xcc, g_chorgByte = 0;
int main(int argc, char *argv[])
{
DWORD dwPID;
if (argc != 2)
{
printf("\nUSAGE : hookdbg.exe pid\n");
return 1;
}
//attach Process
dwPID = atoi(argv[1]);
if (!DebugActiveProcess(dwPID))//将调试器捆绑到一个正常运行的进程上
{
printf("DebugActiveProcess(%d) failed!!\nError Code=%d\n",dwPID,GetLastError());
return 1;
}
DebugLoop();
return 1;
}
为一个进程捆绑到调试器上。
DebugLoop
函数
void DebugLoop()
{
DEBUG_EVENT de;
DWORD dwContinueStatus;
//等待被调式事件发生
while (WaitForDebugEvent(&de, INFINITE))//获取调试事件存放到de里,等待的毫秒数为默认值
{
dwContinueStatus = DBG_CONTINUE;
//被调试进程生成或附加事件
if (CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)//创建进程调试事件
{
OnCreateProcessDebugEvent(&de);
}
//异常事件
else if (EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode)//异常调试事件
{
if (OnExcptionDebugEvent(&de))
continue;
}
//被调试进程终止事件
else if (EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)//进程退出调试事件
{
//被调试这终止,调试器终止
break;
}
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);//到这里被调试的进程正常运行
}
}
一个循环用来等待异常事件,对创建进程调试事件,异常调试事件,进程退出调试事件添加处理。
EXIT_PROCESS_DEBUG_EVENT 被调试进程终止时触发该事件。
CREATE_PROCESS_DEBUG_EVENT 被调试事件启动(或者被附加)。
在CREATE_PROCESS_DEBUG_EVENT事件发生执行OnCreateProcessDebugEvent处理
BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
//获取WriteFile的API地址
g_pfWriteFile = GetProcAddress(GetModuleHandle((LPCWSTR)"kernel32.dll"),"WriteFile");
//API钩子 - WriteFile
//更改第一个字节位0xcc
//originalbyte是g_ch0rgByte的备份
memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chorgByte, sizeof(BYTE), NULL);//CREATE_PROCESS_DEBUG_INFO的hProcess是被调试进程的句柄
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,&g_chINT3, sizeof(BYTE), NULL);//设置断点1.保存原本数据。2.向指令处写入0xcc(也就是INT 3)
}
获取到kernel32.dll中的WriteFile函数的地址,这里之所以成功是因为不同程序的windows系统库几乎(除了某些加载地址冲突发生重定位)拥有相同的加载地址。
先拷贝原本的CREATE_PROCESS_DEBUG_INFO结构(防止数据被修改造成句柄等结构丢失)
然后设置断点。
这样当被调试进程调用WriteFile()API时控制权会转移给调试器。
当执行int 3指令会产生一个EXCEPTION_DEBUG_EVENT下面看它的处理
BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
CONTEXT ctx;
PBYTE lpBuffer = NULL;
DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
EXCEPTION_RECORD *per = &pde->u.Exception.ExceptionRecord;//异常记录
//是断点int 3时
if (EXCEPTION_BREAKPOINT == per->ExceptionCode)
{
//断点地址位WriteFile()API地址时
if (g_pfWriteFile == per->ExceptionAddress)
{
//Unhook
//将0xcc回复位orignal byte
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chorgByte, sizeof(BYTE), NULL);
//获取线程上下文
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);//获取context的内容
//获取WriteFile的参数2,3值
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8), &dwAddrOfBuffer, sizeof(DWORD), NULL);//获取WriteFile的第二个参数 这里的2个写入的硬长度只是决定了注入的不能是64位的
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC), &dwNumOfBytesToWrite, sizeof(DWORD), NULL);//获取WriteFile的第三个参数
//分配临时缓冲区
lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite + 1);
memset(lpBuffer, 0, dwNumOfBytesToWrite);//申请空间,初始化为0
//复制WriteFile缓冲区到临时缓冲区
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, lpBuffer, dwNumOfBytesToWrite, NULL);//被调试进程栈里面存的第二个参数是一个地址,这里把数据读取出来放进缓冲区
printf("\n### original string : %s\n", lpBuffer);
//将小写字母转化为大写字母
for (i = 0; i < dwNumOfBytesToWrite; i++)
{
if (0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A)
{
lpBuffer[i] -= 0x20;
}
}
printf("\n###coverted string : %s\n", lpBuffer);
//将变化后的缓冲区复制到WriteFile()缓冲区
WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, lpBuffer, dwNumOfBytesToWrite, NULL);//写入到内存地址
//释放临时缓冲区
free(lpBuffer);
//将线程上下文的EIP更改为WriteFile的首地址
ctx.Eip = (DWORD)g_pfWriteFile; //位置为INT 3执行之后所以要修改EIP
SetThreadContext(g_cpdi.hThread, &ctx);
//运行被调试程序
ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
Sleep(0);
//API钩子
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chINT3, sizeof(BYTE), NULL);
return TRUE;
}
}
return TRUE;
}
BOOL ReadFile(
HANDLE hFile, //文件的句柄
LPVOID lpBuffer //用于保存读入数据的一个缓冲区
DWORD nNumberOfBytesToRead, //要读入的字节数
LPDWORD lpNumberOfBytesRead, //指向实际读取字节数的指针
LPOVERLAPPED lpOverlapped
//如文件打开时指定了FILE_FLAG_OVERLAPPED,那么必须,用这个参数引用一个特殊的结构。
//该结构定义了一次异步读取操作。否则,应将这个参数设为NULL
//(暂时不看异步读取,只看基本的话,用NULL就好了)
);
作者:00000000_4571
链接:https://www.jianshu.com/p/787fde709ca0
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
之前验证了事件是EXCEPTION_DEBUG_EVENT,该事件里面包括好多异常,对其中EXCEPTION_BREAKPOINT进行验证
然后验证断点位置是否是WriteFile的起始地址(这里说的起始地址是C语言的,对应的汇编语言是push参数之前)
1、脱钩–把之前写入的0xcc恢复(用WriteProceessMemory函数)
2、获取被调试进程的上下文(用GetThreadText函数)
3、用ReadProcessMemory函数读栈内的第二第三个参数,注意这里获取到的 LPVOID lpBuffer 是一个指针
4、分配调试器的缓冲区,并用ReadProcessMemory函数把数据从3得到的指针中获取到缓冲区中
5、把缓冲区中字母小写转大写
6、用WriteProcessMemory将变化后的数据写入到原本的地址中(不要忘了释放掉调试器进程中的内存哦)
7、将被调试进程的EIP重新返回到WriteFile()API的首地址(用SetThreadContext函数)。
8、用ContinueDebugEvent函数让被调试程序继续运行
9、用WriteProcessMemory重新写入INT 3指令即放入钩子。
Sleep(0);
作用是释放掉CPU当前正在执行的被调函数的时间片,不释放的话可能会引起内存访问冲突。
但是这个脚本只能实现32位的程序勾取,64位的失败不知道怎么回事
如图,我下载了一个32位的notepad++实现了这个过程