【逆向】【Part 3】DLL注入

目录

一、通过自制调试器来理解其原理

1.调试器的工作原理

实现反汇编功能(重点)

重点分析exception_debug_event

重点:1.对调试器程序增加异常处理操作功能,核心API, CONTEXT结构

二、DLL注入

重点:2.DLL注入的三种基本方法

1.利用全局消息钩子(Windos消息钩取( SetWindowsHookEx() API ))

2.写注册表

3. 利用远程线程注入来实现DLL注入

一、Windows消息钩取

SetWindowsHookEx()

键盘消息钩取练习

练习示例

重点:3.通过键盘消息钩取的例子,理解如何实现DLL注入的,重要的API,回调函数

二、创建远程线程

重点:4. 创建远程线程完成DLL注入,步骤,关键API 函数,dllmain,LoadLibrary,Creatremotethread

总结:创建远程注入的步骤:

三、注册表 Applnit_DLLs

重点:5.修改注册表,限制


一、通过自制调试器来理解其原理

1.调试器的工作原理

前置基础知识:

  • stdafx.h的作用

当我们使用AppWizard(应用程序(Application)向导(wizard))来自动生成某些项目的时候,系统会自动把所需要include的头文件在stdafx.h中先include一下,这样,我们只需要直接include这个stdafx.h文件即可.

因为同一个项目中的不同源文件CPP都包含相同的include文件,这样,为每个.CPP文件都重复include这些文件就显得很傻了。

https://blog.csdn.net/boyaaboy/article/details/89838658

  • _tmain()

_tmain()是为了支持unicode所使用的main一个别名而已,既然是别名,应该有宏定义过的,在哪里定义的呢?就在那个让你困惑的<stdafx.h>里,有这么两行

#include <stdio.h>

#include <tchar.h>

可以在头文件<tchar.h>里找到_tmain的宏定义

#define _tmain main

所以,经过预编译以后, _tmain就变成main了

main()是标准C++的函数入口。标准C++的程序入口点函数,默认字符编码格式ANSI函数签名为:
 

int main();
int main(int argc, char* argv[]);
  • int argc, char* argv[]命令行参数

https://blog.csdn.net/renyhui/article/details/19112315

注意:

输出的是一串数字而非我们想要的路径,这是因为_TCHAR的声明:typedef wchar_t _TCHAR

在Unicode中_TCHAR被认为是宽字符,输出宽字符时我们要使用wcout进行输出。不然下面输出的是数字。

修改后代码:

#include "stdafx.h"
#include <iostream>
using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
	wcout<<"argc"<<argc<<endl;
	for(int i=0;i<argc;i++)
		wcout<<"argv["<<i<<"]"<<argv[i]<<endl;

	return 0;
}

运行结果:

则:

argv[0]表示输入程序的路径及名称

argv[1],argv[2]...表示自己输入的参数

argc用来统计参数的个数,因为路径为默认的参数,所以argc至少为1

 

先大概了解一下上面的这些知识,主要是写代码刚临门就被绊倒了,看一下再向下走。

注意PROCESS_INFORMATION在头文件#include "windows.h"中,(我用的是VC++2010)

下面是关于调试器的代码:

#include "stdafx.h"
#include "windows.h"

int _tmain(int argc, _TCHAR* argv[])
{
	//预先定义的数据结构
	PROCESS_INFORMATION pi;//在新建进程函数CreateProcess中使用用来返回新建进程的相关信息。
/*typedef struct{
HANDLE hProcess; //新建进程的内核句柄
HANDLE hThread; //新建进程中主线程的内核句柄
DWORD dwProcessId; //新建进程的ID
DWORD dwThreadId; //新建进程主线程ID
}PROCESS_INFOMATION,*LPPROCESS_INFOMATION;
*/
	STARTUPINFO si;//指定新进程的主窗口特性的一个结构

	if(argc<2){
		fprintf(stderr,"C:\\>%s <sample.exe>\n",argv[0]);//fprintf格式化输出到一个流文件中
		//stderr:标准错误输出设备。标准错误输出的屏幕显示,当参数少于两个提示应该正确输入的格式
		return 1;
	}
	memset(&pi,0,sizeof(pi));
	memset(&si,0,sizeof(si));//结构体全部填充为0(每个字节都是0)
	si.cb = sizeof(STARTUPINFO);
	/*DWORD cb;
	包含STARTUPINFO结构中的字节数.如果Microsoft将来扩展该结构,
	它可用作版本控制手段,应用程序必须将cb初始化为sizeof(STARTUPINFO)。 */

BOOL r = CreateProcess(
//CreateProcess用来创建一个新的进程和它的主线程,这个新进程运行指定的可执行文件。
NULL,//可执行模块名称
argv[1],//命令行字符串
NULL,//安全属性
NULL,//也是安全属性
FALSE,//句柄继承选项
//接下来这个是创建标志(有三个用|连接)
NORMAL_PRIORITY_CLASS  //指示这个进程没有特殊的任务调度要求。
| CREATE_SUSPENDED //新进程的主线程会以暂停的状态被创建,直到调用ResumeThread函数被调用时才运行。
| DEBUG_PROCESS,
/*如果这个标志被设置,调用进程将被当做一个调试程序,并且新进程会被当做被调试的进程。
系统把被调试程序发生的所有调试事件通知给调试器。
如果你使用这个标志创建进程,只有调用进程(调用CreateProcess函数的进程)
可以调用WaitForDebugEvent函数。*/
NULL,//新进程的环境变量块
NULL,//当前路径
&si,//启动信息,指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体。
&pi//进程信息,指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体。
);
	if(!r)//如果函数执行成功,返回非零值。如果函数执行失败,返回零.
		return -1;
	ResumeThread(pi.hThread);//线程恢复函数,使线程的挂起时间计数减一

	while(1){
		DEBUG_EVENT de;//DEBUG_EVENT结构描述了调试事件
		if(!WaitForDebugEvent(&de,INFINITE))//de等待无限长时间
/*Kernel32.dll提供WaitForDebugEvent来监听调试事件,当目标进程发生调试事件时会通知我们的调试器
进行处理,我们用一个循环不断调用此函数来在处理完一个调试事件后立即监听下一个调试事件。
函数原型
WaiteForDebugEvent(LPDEBUG_EVENT _DEBUG_EVENT,DWORD dwMilliseconds)
第一个参数指向event结构,这个结构描述了一个调试事件,第二个参数为等待事件的毫秒数。
返回一个BOOL值*/
			break;
		DWORD dwContinueStatus = DBG_CONTINUE;//表示已处理异常,继续执行在异常代码
		switch (de.dwDebugEventCode)
		{
		case CREATE_PROCESS_DEBUG_EVENT:
			printf("CREATE_PROCESS_DEBUG_EVENT\n");
			break;
		case CREATE_THREAD_DEBUG_EVENT:
			printf("CREATE_THREAD_DEBUG_EVENT\n");
			break;
		case EXIT_THREAD_DEBUG_EVENT:
			printf("EXIT_THREAD_DEBUG_EVENT\n");
			break;
		case EXIT_PROCESS_DEBUG_EVENT:
			printf("EXIT_PROCESS_DEBUG_EVENT\n");
			break;
		case EXCEPTION_DEBUG_EVENT:
			{
			DWORD r = de.u.Exception.ExceptionRecord.ExceptionCode;//哪一种异常
			if(r!=EXCEPTION_BREAKPOINT)//如果不是断点异常
			dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;//没有被处理掉
			printf(" EXCEPTION_DEBUG_EVENT\n");
			}
			break;
		case OUTPUT_DEBUG_STRING_EVENT:
			printf("OUTPUT_DEBUG_STRING_EVENT\n");
			break;
		case RIP_EVENT:
			printf("RIP_EVENT\n");
			break;
		case LOAD_DLL_DEBUG_EVENT://调试中会出现各种事件都进行捕获
			printf("LOAD_DLL_DEBUG_EVENT\n");
			break;
		case UNLOAD_DLL_DEBUG_EVENT:
			printf("UNLOAD_DLL_DEBUG_EVENT\n");
			break;
		}
		if(de.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT)//进程退出调试异常
			break;
		ContinueDebugEvent(
//ContinueDebugEvent函数:此函数允许调试器回复先前由于调试事件而挂起的线程。
			de.dwProcessId,de.dwThreadId,dwContinueStatus);
	}
	CloseHandle(pi.hThread);
	CloseHandle(pi.hProcess);
	return 0;

}

百科:https://baike.baidu.com/item/PROCESS_INFOMATION%E7%BB%93%E6%9E%84/6900641?fr=aladdin

该结构体用于在创建一个新的进程时,这个新进程包含的属性,以及与父进程之间的关系。

  • PROCESS_INFOMATION结构
typedef struct{
HANDLE hProcess; //新建进程的内核句柄
HANDLE hThread; //新建进程中主线程的内核句柄
DWORD dwProcessId; //新建进程的ID
DWORD dwThreadId; //新建进程主线程ID
}PROCESS_INFOMATION,*LPPROCESS_INFOMATION;

PROCESS_INFOMATION结构主要在新建进程函数CreateProcess中使用用来返回新建进程的相关信息。

  • STARTUPINFO

用于指定新进程的主窗口特性的一个结构。 

很多参数参考(https://baike.baidu.com/item/STARTUPINFO/2373656

(代码中用到的话再具体分析)

  • fprintf

fprintf是C/C++中的一个格式化库函数,位于头文件<cstdio>中,其作用是格式化输出到一个流文件中;

函数原型为int fprintf( FILE *stream, const char *format, [ argument ]...),

fprintf()函数根据指定的格式(format),向输出流(stream)写入数据(argument)。

  • stderr(标准错误输出设备)

The standard error stream is the default destination for error messages and other diagnostic warnings. Like stdout, it is usually also directed by default to the text console (generally, on the screen).

stderr can be used as an argument for any function that takes an argument of type FILE* expecting an output stream, like fputs or fprintf.

  • CreateProcess

WIN32API函数CreateProcess用来创建一个新的进程和它的主线程,这个新进程运行指定的可执行文件。

如果函数执行成功,返回非零值。

如果函数执行失败,返回零,可以使用GetLastError函数获得错误的附加信息。

  • ResumeThread线程恢复函数

使线程的挂起时间计数减一。创建一个挂起的线程或者手动挂起一个线程后调用。调用该函数后线程不一定会立刻执行,而是由操作系统继续调度,直到计数为0,系统为其分配资源时才开始执行。

DWORD ResumeThread{
HANDLE hThread  //线程句柄
);
  • DEBUG_EVENT结构描述了调试事件。

https://docs.microsoft.com/en-us/previous-versions/bb202796(v=msdn.10)?redirectedfrom=MSDN

http://www.yfvb.com/help/win32sdk/index.htm?page=html/5j5av0.htm

typedef struct _DEBUG_EVENT { 
  DWORD dwDebugEventCode; //调试事件编号。
  DWORD dwProcessId; //进程ID
  DWORD dwThreadId; //线程ID
  union { 
    EXCEPTION_DEBUG_INFO Exception; 
    CREATE_THREAD_DEBUG_INFO CreateThread; 
    CREATE_PROCESS_DEBUG_INFO CreateProcessInfo; 
    EXIT_THREAD_DEBUG_INFO ExitThread; 
    EXIT_PROCESS_DEBUG_INFO ExitProcess; 
    LOAD_DLL_DEBUG_INFO LoadDll; 
    UNLOAD_DLL_DEBUG_INFO UnloadDll; 
    OUTPUT_DEBUG_STRING_INFO DebugString; 
    RIP_INFO RipInfo; 
  } u; 
} DEBUG_EVENT;

这个结构体简单了解下,第一个成员dwDebugEventCode代表调试事件编号。

dwProcessID为进程ID,dwThreadID为线程ID

union结构中的数据会随着dwDebugEventCode的不同而发生变化。

dwDebugEventCode可以取下列值:

EXCEPTION_DEBUG_EVENT 发生异常

CREATE_THREAD_DEBUG_EVENT 创建线程

CREATE_PROCESS_DEBUG_EVENT 创建进程

EXIT_THREAD_DEBUG_EVENT 线程结束

EXIT_PROCESS_DEBUG_EVENT 进程结束

LOAD_DLL_DEBUG_EVENT 加载DLL

UNLOAD_DLL_DEBUG_EVENT 卸载DLL

OUTPUT_DEBUG_STRING_EVENT 调用OutputDebugString函数

RIP_EVENT 发生系统调试错误

dwProcessId

指定调试事件发生的进程的标识符。调试器使用此值来定位调试器的每个进程结构。这些值不一定是可以用作表索引的小整数。

dwThreadId

指定调试事件发生的线程的标识符。调试器使用此值来定位调试器的每个线程结构。这些值不一定是可以用作表索引的小整数。

u

指定与调试事件相关的其他信息。该联合会接受与调试事件类型相适应的类型和值,如dwDebugEventCode成员所述。

运行结果:(一开始运行错误,上面给si赋初值的时候把si写成了pi结果就错误了,改了下就好了。随便用的一个hellword.exe程序调试的结果如下)

实现反汇编功能(重点)

为wdbg01a.exe增加一些新功能。

希望在发生异常时,能够显示出发生异常的地址以及当前寄存器的值。同时,我们还希望显示发生异常时所执行的命令,因此下面我们来实现反汇编功能。

首先引入:#include "udis86.h"  和#pragma comment(lib,"libudis86.lib")

PS:如果你常使用它们,扔进你的VC库Microsoft Visual Studio 10.0\VC\include和lib。在项目中用尖括号包含头文件。
如果你仅在某一项目中使用它们,把它们放在项目相关目录。

代码如下:

#include "stdafx.h"
#include "windows.h"
#include "udis86.h"

#pragma comment(lib,"libudis86.lib")

int disas(unsigned char *buff,char *out,int size)//机器语言反汇编到汇编代码(这部分了解即可)
{
	ud_t ud_obj;
	ud_init(&ud_obj);
	ud_set_input_buffer(&ud_obj,buff,32);

	ud_set_mode(&ud_obj, 32);
	ud_set_syntax(&ud_obj,UD_SYN_INTEL);

	if(ud_disassemble(&ud_obj)){
		sprintf_s(out,size,"%14s   %s",
			ud_insn_hex(&ud_obj),ud_insn_asm(&ud_obj));
	}else{
		return -1;
	}
	return (int)ud_insn_len(&ud_obj);
}
//重点在下面,分析exception_debug_event(DEBUG_EVENT *pde),如何处理异常
int exception_debug_event(DEBUG_EVENT *pde)//DEBUG_EVENT结构描述了调试事件
{
	DWORD dwReadBytes;
	HANDLE ph=OpenProcess(//
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值