.Net,C#程序的HOOK
最近有个需求,就是要在一个软件里面获取一个编号
当时我想这还不是so easy,找到那个框的句柄,发个消息就能拿到,百度一下
https://www.cnblogs.com/lujin49/p/4796502.html
几行代码不就搞定了,哈哈哈~
SPYXX掏出来,好吧,根本没有控件句柄!!!
再后来查壳看了软件,原来是.net写的。。。
CheatEngine一顿搜,还找不到基址。
嘿嘿,.net写的软件不就是源码么,掏出.net大杀器dnSpy,马上找到关键代码
private string xxx(SomeStruct ss)
{
Config.CyclePressValue = 25;
Config.TrgCycleTime = (DateTime.Now - this.TrgTime).TotalSeconds;
.
.
.
GC.Collect();
GC.WaitForPendingFinalizers();
return "";
}
SomeStruct那个结构体里面就有要找的那个数字,代码我修改了下,直接贴出来好像不太好。。。
这要是c写的就好办了,.net这种我以前还没搞过,好吧再百度找找有没有人整过这种类型的
欸,有了!!https://blog.csdn.net/xfgryujk/article/details/79053312
不过这只管hook到自己的代码,回不到原来的函数,而且还是clrcall类型的。。。
后来又搜了好多,基本没什么有用的信息,.net的hook资料是真的少~
好吧,那就自己来吧。既然JIT是运行时编译,那编译好的代码看看先吧,继续CE掏出一顿操作,CE还蛮强,.net的类和函数都能看到
找到上面的那个函数名称双击就能看到已经编译好的代码:
这里其实有个坑,等会说。。。
现在思路清晰了,可以直接内存爆搜Opcode,找到JIT后的地址,然后直接hook这个地址
mov rax,我的代码地址
jmp rax
直接写个dll,然后注入就完事了。。。
这里有个问题,VS写64位asm代码不支持__asm{}这种类型了,要么你换intel的编译器,要么你写个.asm文件再asm+c混合编译。
vs里面Cpp文件下,要引用asm中的函数,声明一下
extern "C" void asmcode();
asm文件中要引用cpp的函数,要这样写
extrn RunMyCode : proc
这样可以完美互相引用对方的函数
.asm文件中代码
extrn RunMyCode : proc
.code
asmcode proc
push rbx
push rcx
push rdx
push rbp
push rsi
push rdi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
pushfq
//上面的代码保存现场,下面是运行hook后我们自己的代码,找到我们需要的那串数字
push rax
lea rcx,[rdx+170h]
call RunMyCode
pop rax
//恢复现场
popfq
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rbp
pop rdx
pop rcx
pop rbx
//这里是被替换掉的原始代码,需要补上
push rdi
push rsi
push rbp
push rbx
sub rsp,28h
mov rsi,rcx
mov rdi,rdx
//补完后可以跳回原来的函数地址
mov rax,1234567812345678h
jmp rax
asmcode endp
end
CPP文件大概代码
#include "pch.h"
#include <vector>
using namespace std;
static BYTE m_Shared[100] = { 0 };
extern "C" void RunMyCode(LPVOID pAddr)
{
//HANDLE hMap = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, L"MySharedMemory");
//if (hMap == NULL)
//{
// hMap = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1024, L"MySharedMemory");
// LPVOID pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
// ZeroMemory(pBuffer, 0x100);
memcpy(m_Shared, pAddr, 10);
// ::UnmapViewOfFile(pBuffer);
// ::CloseHandle(hMap);
//}
}
extern "C" void asmcode();
DWORD WINAPI InitHookThread(LPVOID dllMainThread)
{
WaitForSingleObject(dllMainThread, INFINITE);
CloseHandle(dllMainThread);
std::vector<unsigned __int64> ResultArray;
SearchMemory(GetCurrentProcess(), (char*)"57 56 55 53 48 83 EC 28 48 8B F1 48 8B FA C7 05 ?? ?? ?? ?? 19 00 00 00 E8 ?? ?? ?? ?? 48 8B C8 48 8B 56 50 E8 ?? ?? ?? ?? C4 E1 78 57 C0 C4 E1 FB 2A C0", 0x0, 0x7FFFFFFFFFFF, 10, ResultArray);
if (ResultArray.size()>0)
{
LPVOID paddr = (LPVOID)ResultArray.at(0);
__int64 dwFunc = GetFunAddress((PUCHAR)asmcode);
for (PUCHAR pBuf = (PUCHAR)dwFunc; pBuf < ((PUCHAR)dwFunc + 1024); ++pBuf)
{
if (*(__int64*)pBuf == 0x1234567812345678)
{
__int64 tmp = (__int64)paddr + 0xE;
DWORD dOldProtect;
VirtualProtect((LPVOID)dwFunc,0x100, PAGE_EXECUTE_READWRITE, &dOldProtect);
memcpy(pBuf, &tmp, sizeof(tmp));
break;
}
}
BYTE jmpcode[] = { 0x48, 0xB8 , 0x78 , 0x56 , 0x34 , 0x12 , 0x78 , 0x56 , 0x34 , 0x12 , 0xFF , 0xE0 };
memcpy(jmpcode + 2, &dwFunc, 8);
memcpy(paddr, jmpcode, sizeof(jmpcode));
}
while (1)
{
Sleep(3000);
}
return 0;
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
//DisableThreadLibraryCalls(hModule);
HANDLE curThread;
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &curThread, SYNCHRONIZE, FALSE, 0))
return FALSE;
CloseHandle(CreateThread(NULL, 0, InitHookThread, curThread, 0, NULL));
}
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
hook到那串数字后,本来打算用filemapping映射到内存来和自己的程序通信,后来省事直接在dll整了静态内存,直接读dll的内存好了。
RunMyCode函数带一个参数,所以
lea rcx,[rdx+170h] ->rcx取到那串数字的偏移地址,作为参数
call RunMyCode
好,到这里注入后就OK成功了。。。
去现场一试,BOOM!!!其实到了反汇编这里,不BOOM才不正常。。。这就是上面说的坑!
后来调试发现,.net版本不一样,JIT后的OPCode显然会大大的不一样。。。包括结构体的偏移量,全都变~~
其实上面提到的那篇文章也说了,“hook .NET程序的难点在于.NET程序都是JIT临时编译的,函数的地址不确定。我翻了一下MSDN,发现可以用RuntimeMethodHandle的GetFunctionPointer获取编译后的函数地址,于是用C++/CLI调用.NET的反射和GetFunctionPointer就可以实现hook了”,但是整了那个clr问题好多,编译都编译不起来,只能这样了。。。
特此记录下hook .net遇到的坑~