调试方式勾取API WriteFile函数实现大小写转化(逆向工程核心原理 记事本WriteFile api勾取)

调试方式:场景是调试本地程序,用调试方式可以对被调用进程的完全访问。
基本原理:在记事本调用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位的失败不知道怎么回事
调试方式勾取API WriteFile函数实现大小写转化(逆向工程核心原理 记事本WriteFile api勾取)_第1张图片
如图,我下载了一个32位的notepad++实现了这个过程

你可能感兴趣的:(逆向)