聊一聊如何截获 C# 程序产生的日志

news2025/8/10 5:39:40

一:背景

1.讲故事

前段时间分析了一个dump,一顿操作之后,我希望用外力来阻止程序内部对某一个com组件的调用,对,就是想借助外力实现,如果用 windbg 的话,可以说非常轻松,但现实情况比较复杂,客户机没有windbg,也不想加入任何的手工配置,希望全自动化来处理。

真的很无理哈。。。不过这种无理要求花点心思还是可以实现的,方法就是用代码将应用程序变成调试器 来实现自动化阻止,为了简化操作,我们拿 C# 的 File.WriteAllText 来举个例子,让我的调试器来截获它的 content。

2. 测试案例

为了方便讲述,创建一个 WPF 程序,在 button 事件中用 File.WriteAllText 方法来写日志,参考代码如下:


    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            System.IO.File.WriteAllText("C:\\1.txt", DateTime.Now.ToString());
        }
    }

代码非常简单,点一下按钮就写一条时间日志,接下来分别用 WinDbg 和 自定义调试器 来截获这个时间。

二:WinDbg 下的实现

1. 实现原理

要想截获日志,需要知道这个链路的下游方法,比如:kernel32!WriteFile,msdn 上的定义如下:


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 都出来了,非常完美,接下来看下如何自定义实现调试器。

三:自己实现一个调试器

1. 技术原理

要想自定义实现,需要打通这三块。

  1. 如何给 kernel32!WriteFile 下 bp 断点

bp 的原理其实就是 int 3 ,简而言之就是 windbg 会将 kernel32!WriteFile 指令的首字节修改成机器码 0xcc,命中之后又将 0xcc 撤销掉。这一串逻辑是 windbg 内部自己实现的,接下来我们验证下,将首字节直接改成 0xcc


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                                               .

从卦中看已修改成功,接下来直接点击 WPF 窗体的 button 按钮就会直接命中这里的 int 3 实现中断。

到了这一步后,可以在程序中使用 WriteProcessMemory 恢复 WriteFile 原始字节为 ff

  1. 如何让 int 3 中断给程序

刚才看到的是中断给WinDbg,那怎么中断给程序呢? 其实 Win32 API 中有一个叫 DebugActiveProcess 函数可以让宿主程序充当调试器,mdsn 中的描述如下:

  1. 如何读写 wpf 的内存和寄存器

只要获取到了 wpf 程序的进程和线程句柄,可以用 WriteProcessMemoryReadProcessMemory 读写内存,用 GetThreadContextSetThreadContext 读写寄存器。

2. 代码实现

思路和技术都搞清楚后,代码落地就非常简单了,参考如下:


// 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 按钮观察,截图如下,非常完美。

三:总结

在无法安装 windbg 的受限环境下,部署 HookDebug.exe 就是我们的另一种选择,而且完全自动化拦截,基本实现无人工干预。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/33948.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

当下互联网行业趋势,你顶得住吗?

持续三年的疫情导致经济形式大不如前&#xff0c;特别是互联网行业&#xff0c;不少员工面临着失业的压力&#xff0c;在如此恶劣的大环境下&#xff0c;计算机行业的我们应该如何生存&#xff1f;有一个很好的办法就是 —— 考证&#xff01;&#xff01;&#xff01;如今越来…

多线程与并发 - 常见的几种锁的实现方式

1、悲观锁 正如其名&#xff0c;它是指对数据修改时持保守态度&#xff0c;认为其他人也会修改数据。因此在操作数据时&#xff0c;会把数据锁住&#xff0c;直到操作完成。悲观锁大多数情况下依靠数据库的锁机制实现&#xff0c;以保证操作最大程度的独占性。如果加锁的时间过…

深度学习入门(6)误差反向传播基础---计算图与链式法则

在我的第三篇博文《深度学习入门&#xff08;3&#xff09;神经网络参数梯度的计算方式》中详细介绍了通过微分方式计算神经网络权重参数的梯度。但是数值微分的方式计算梯度效率较低。后续博文会介绍另外一种更加高效的梯度计算方式---误差的反向传播。 这篇文章介绍的是误差…

CorelDRAW2023最新版矢量设计软件

CorelDRAW2023最新版是我比较用的比较好的一款软件&#xff0c;因为其作为一款优秀的矢量设计软件&#xff0c;兼具功能和性能&#xff0c;它是由Corel公司出品的矢量设计工具&#xff0c;被广泛应用于排版印刷、矢量图形编辑、网页设计等行业。CDR软件的优势在于&#xff1a;易…

ROS2 机器人操作系统入门和安装以及如何使用 .NET 进行开发

本文是 ROS2 入门的第一课&#xff0c;简单介绍了 ROS 系统&#xff0c;并演示了 ROS2 系统在 Ubuntu 22.04 中的安装&#xff08;使用 gitee 和清华源&#xff09;以及其中错误的解决。最后对其优势进行总结&#xff0c;为什么选择 ROS。最后介绍简单 Demo 和如何使用 .NET 接…

ThingsBoard源码解析-规则引擎

描述 规则引擎是Thingsboard的核心部分&#xff0c;基于Actor编程模型&#xff0c;类似事件驱动&#xff1b; 每个actor都有自己的消息队列&#xff08;mailBox&#xff09;保存接收到的消息 actor可以创建actor actor可以将消息转发给其他actor 分析 Actor模型实现 系统…

戴尔科技集团通过多云数据保护和安全创新增强网络弹性

中国北京——2022年11月18日 Dell PowerProtect Data Manager软件更新和新一代备份一体机可帮助客户提高运维安全和网络弹性 戴尔多云数据保护解决方案利用内置的安全运维功能加速采用零信任原则 2022年全球数据保护指数(GDPI)调查结果公布 戴尔科技集团(NYSE:Dell)扩大其在数据…

OA系统,有效提升企业办公效率落实执行力

企业管理的成功将最终取决于企业的执行情况&#xff0c;只要有良好的经营管理&#xff0c;管理系统&#xff0c;一个好的领导者&#xff0c;充分调动员工的积极性&#xff0c;将能最大限度的管理执行力。 OA协同办公系统提供了工作流和协同工作互补结合。工作流程严格规定了工作…

PCB铺铜的优点与缺点

PCB设计铺铜是电路板设计的一个非常重要的环节。 什么是PCB铺铜&#xff0c;就是将PCB上无布线区域闲置的空间用固体铜填充。铺铜的意义在于减小地线阻抗&#xff0c;提高抗干扰能力;降低压降&#xff0c;提高电源效率&#xff0c;与地线相连&#xff0c;还可以减小环路面积。 …

基于蛙跳算法求解简单调度问题附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

python与Electron联合编程记录之九(Electron与Flask联合编程实现)

前面铺垫了这么多&#xff0c;这一节就要真正的实现Electron与python联合编程。这一节我通过加法器这个简单的例子来演示如何真正实现Electron和Flask联合编程。 1、安装Axios包 在终端工具选项卡中输入如下命令安装Axios包: npm i --save-dev axios2、项目结构 项目结构如下…

C语言源代码系列-管理系统之家庭财务小管家

往期文章分享点击跳转>《导航贴》- Unity手册&#xff0c;系统实战学习点击跳转>《导航贴》- Android手册&#xff0c;重温移动开发 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断反思前进的过程。在这个过…

COLMAP输出的文件类型(bin, txt)

默认情况下&#xff0c;COLMAP使用二进制文件格式(bin&#xff0c;机器可读&#xff0c;速度速)来存储稀疏模型。此外&#xff0c;COLMAP也可以将稀疏模型存储为文本文件(txt&#xff0c;人类可读&#xff0c;速度慢)。在这两种情况下&#xff0c;模型导出的信息被分为关于相机…

【吴恩达机器学习笔记】三、矩阵

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为学习吴恩达机器学习视频的同学提供的随堂笔记。 &#x1f4da;专栏简介&#xff1a;在这个专栏&#xff0c;我将整理吴恩达机器学习视频的所有内容的笔记&…

Cygwin安装

Cygwin是一个在Windows平台上运行的类UNIX模拟环境&#xff0c;在其提供的命令行界面中完美地兼容了Windows和Linux的命令行指令&#xff0c;安装和使用教程很容易百度到&#xff0c;可从官网下载安装包&#xff1a;Cygwin官网。安装步骤如下所示&#xff0c;也可自行百度安装方…

web网页设计实例作业HTML+CSS+JavaScript蔬菜水果商城购物设计

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

测试工程师们需要认真思考的几个问题

一、如何保证合适的测试用例覆盖率 测试是一个经济学的概念&#xff0c;不计成本的测试最终会受到市场的惩罚和用户的抛弃。所以为了体现这种明智&#xff0c;测试用例设计所追求的目标不是100%覆盖&#xff0c;而应该是均匀覆盖。让测试用例均匀覆盖功能点的理念&#xff0c;其…

Buildroot 开发

转载&#xff1a;https://wiki.t-firefly.com/AIO-3288C/buildroot_develop.html Buildroot 开发 Buildroot 是 Linux 平台上一个构建嵌入式 Linux 系统的框架。整个 Buildroot 是由 Makefile(*.mk) 脚本和 Kconfig(Config.in) 配置文件构成的。你可以和编译 Linux 内核一样&am…

PreScan快速入门到精通第三十八讲基于车道线识别传感器的车道偏离算法Demo讲解

车道偏离系统介绍: 什么是车道偏离警告? 车道偏离警告是一种先进的驾驶辅助系统(ADAS),在许多较新的车辆中发现。它在司机无意离开自己的车道时发出声音、视觉或者通过方向盘振动,甚至安全带预紧的方式给与驾驶员警告。 当汽车意外地离开道路时,就会发生车祸--而且可能…

户外运动耳机如何选择、最优秀的五款户外运动耳机推荐

有些人花时间在户外纯粹是为了听听大自然的声音。其他人可能不想在没有娱乐或鼓舞人心的音频选择的情况下跑步、徒步、散步或骑自行车。找到适合锻炼的耳机相当简单&#xff0c;就像健身耳机一样&#xff0c;您会希望这些耳机能够舒适、安全地贴合您的耳朵&#xff0c;这样它们…