如何实现.net程序的进程注入

                        如何实现.net程序的进程注入
                                  周银辉

进程注入比较常见,比如用IDE调试程序以及一些Spy程序,如果仅仅为了与调试器通讯,可以使用.net提供的Debugger接口(在EnvDTE.dll的EnvDTE命名空间下).但无论出于什么目的,进程注入都是比较好玩的事情,所以不妨一试 . 进程注入的方法貌似很多(比如像特洛伊一样乔装打扮让目标进程误认为你的程序集合法而加载到目标进程),这里提到的仅是其中的一种或某些方法的结合.

大致原理是这样的:

  • 源进程(也就是你的代码所在的进程)获得目标进程(也就是你的注入目标所在的进程)的ID或进程对象
  • 源进程提供一回调函数methodA(也就是你想要注入到目标进程后所执行的代码)
  • 将目标进程和回调函数methodA的完整路径(其所在的Assembly,Classic以及MethodName)提交给Injector(也就是我们编写的负责注入的类),让Injector来完成注入和让目标进程执行回调函数
  • Injector根据提供的目标进程ID取得目标进程对象,并获得目标进程的一个线程(我们称为目标线程)
  • 在目标线程中分配一块内存,将回调函数methodA的完整路径作为字符串存入该内存中
  • Injector在目标进程中安装一个钩子(Hook)监视某一个Windows消息(messageA),撰写钩子的回调函数methodB(该方法中的内容稍后解释)
  • 像目标进程发消息messageA,并将刚才分配的内存的基地址作为消息参数传递.
  • 由于我们针对messageA安装了钩子,所以目标进程会调用我们钩子函数methodB,并会把分配的内存的基地址包含在函数参数中
  • methodB中, 根据函数参数中的内存基地址在内存中解析出其实际对象,也就是一个表示我们的methodA的完整路径的字符串.根据该字符串中所表示的Assembly,className, methodName利用.net反射,反射出其MethodInfo对象(注意,关键点,methodB被回调时已经是在目标进程的某个线程中了)
  • Invoke反射出的MethodInfo对象, 我们的methodA得到了执行.

下面这个图可能会帮助你理解上面的话:
InjectProcess 

如果还没明白的话,那就看代码吧(这需要一点点C++/CLI知识,但我已经为每句加上了注释,应该蛮好懂的,关于C++/CLI可以点击这里了解更多.)

#include "stdafx.h"
#include "Injector.h"
#include <vcclr.h>
using namespace ManagedInjector;
//defines a new window message that is guaranteed to be unique throughout the system.
//The message value can be used when sending or posting messages.
static unsigned int WM_GOBABYGO = ::RegisterWindowMessage(L"Injector_GOBABYGO!");
static HHOOK _messageHookHandle;
//-----------------------------------------------------------------------------
//Spying Process functions follow
//-----------------------------------------------------------------------------
void Injector::Launch(System::IntPtr windowHandle, System::Reflection::Assembly^ assembly, System::String^ className, System::String^ methodName) {
System::String^ assemblyClassAndMethod = assembly->Location + "$" + className + "$" + methodName;
//convert String to local wchar_t* or char*
pin_ptr<const wchar_t> acmLocal = PtrToStringChars(assemblyClassAndMethod);
//Maps the specified executable module into the address space of the calling process.
HINSTANCE hinstDLL = ::LoadLibrary((LPCTSTR) _T("ManagedInjector.dll"));
if (hinstDLL)
{
DWORD processID = 0;
//get the process id and thread id
DWORD threadID = ::GetWindowThreadProcessId((HWND)windowHandle.ToPointer(), &processID);
if (processID)
{
//get the target process object (handle)
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);
if (hProcess)
{
int buffLen = (assemblyClassAndMethod->Length + 1) * sizeof(wchar_t);
//Allocates physical storage in memory or in the paging file on disk for the specified reserved memory pages.
//The function initializes the memory to zero.
//The return value is the base address of the allocated region of pages.
void* acmRemote = ::VirtualAllocEx(hProcess, NULL, buffLen, MEM_COMMIT, PAGE_READWRITE);
if (acmRemote)
{
//copies the data(the assemblyClassAndMethod string)
//from the specified buffer in the current process
//to the address range of the target process
::WriteProcessMemory(hProcess, acmRemote, acmLocal, buffLen, NULL);
//Retrieves the address of MessageHookProc method from the hintsDLL
HOOKPROC procAddress = (HOOKPROC)GetProcAddress(hinstDLL, "MessageHookProc");
//install a hook procedure to the target thread(before the system sends the messages to the destination window procedure)
_messageHookHandle = ::SetWindowsHookEx(WH_CALLWNDPROC, procAddress, hinstDLL, threadID);
if (_messageHookHandle)
{
//send our custom message to the target window of the target process
::SendMessage((HWND)windowHandle.ToPointer(), WM_GOBABYGO, (WPARAM)acmRemote, 0);
//removes the hook procedure installed in a hook chain by the SetWindowsHookEx function.
::UnhookWindowsHookEx(_messageHookHandle);
}
//removes a hook procedure installed in a hook chain by the SetWindowsHookEx function.
::VirtualFreeEx(hProcess, acmRemote, buffLen, MEM_RELEASE);
}
::CloseHandle(hProcess);
}
}
//Decrements the reference count of the loaded DLL
::FreeLibrary(hinstDLL);
}
}
__declspec( dllexport )
// The procedure for hooking, this will be called back after hooked
int __stdcall MessageHookProc(int nCode, WPARAM wparam, LPARAM lparam) {
//HC_ACTION: indicate that there are argments in wparam and lparam
if (nCode == HC_ACTION)
{
CWPSTRUCT* msg = (CWPSTRUCT*)lparam;
//when the target window received our custom message
if (msg != NULL && msg->message == WM_GOBABYGO)
{
//get the argument passed by the message
//actually, the argument is the base address (a pointer)
//of the assemblyClassAndMethod string in the target process memory
wchar_t* acmRemote = (wchar_t*)msg->wParam;
//gcnew: creates an instance of a managed type (reference or value type) on the garbage collected heap
System::String^ acmLocal = gcnew System::String(acmRemote);
//split the string into substring array with $. Under this context:
//acmSplit[0]:the assembly's location
//acmSplit[1]:className;
//acmSplit[2]:methodName
//we use these infomation to reflect the method in the source assembly, and invoke it in the target process
cli::array<System::String^>^ acmSplit = acmLocal->Split('$');
//refect the method, and invoke it
System::Reflection::Assembly^ assembly = System::Reflection::Assembly::LoadFile(acmSplit[0]);
if (assembly != nullptr)
{
System::Type^ type = assembly->GetType(acmSplit[1]);
if (type != nullptr)
{
System::Reflection::MethodInfo^ methodInfo =
type->GetMethod(acmSplit[2], System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public);
if (methodInfo != nullptr)
{
methodInfo->Invoke(nullptr, nullptr);
}
}
}
}
}
return CallNextHookEx(_messageHookHandle, nCode, wparam, lparam);
}

接下来,做个DEMO尝试一下:
 demo_Solution

解决方案中的InjectorDemo就是我们上述的源进程,它会利用Injector将下面这段代码注入到Target进程中并执行:
public static void DoSomethingEvie()
{
    vartargetWindow = Application.Current.MainWindow;

    if(targetWindow != null)
    {
        varlb = newLabel{Content = "haha, i caught you :)"};
        targetWindow.Content = lb;
    }
}

 

也就是说InjectorDemo进程会将InjectTargetApp进程的主窗口的内容修改成"haha, i caught you"这样的一个Label.

运行程序:
demo 
上面的两个窗口分别处于不同的进程中, 点击 "Inject it" 按钮, 其辉调用如下代码:

ManagedInjector.Injector.Launch(targetProcess.MainWindowHandle, typeof(InjectorWindow).Assembly, typeof(InjectorWindow).FullName, "DoSomethingEvie");

然后:
demo 
点击这里下载Demo

仅供参考(日志内容仅仅记录了一种存在性,并非特定问题的解决方案,讨论其意义性是毫无意义的,谢谢)

0
0
(请您对文章做出评价)
« 上一篇:[转]Managed, Unmanaged, Native: What Kind of Code Is This?
» 下一篇:.net中模拟键盘和鼠标操作
posted @ 2009-06-10 01:29 周银辉 阅读(4778) 评论(33)  编辑 收藏 网摘 所属分类: .Net

  回复  引用    
#1楼2009-06-10 02:00 | 蓝玄[未注册用户]
好东西.正需要
  回复  引用  查看    
#2楼2009-06-10 02:18 | xiaotie      
赞一把
  回复  引用  查看    
#3楼2009-06-10 07:26 | 温景良(Jason)      
好东西
  回复  引用  查看    
#4楼2009-06-10 07:54 | lhking      
曾经看到的,C#注入,没有这么麻烦,只是调用几个函数就可以了。C#注入后,能干什么呢?
  回复  引用  查看    
#5楼2009-06-10 07:55 | Old      
good job
  回复  引用  查看    
#6楼[楼主]2009-06-10 08:28 | 周银辉      
@lhking
能说说是如何注入的吗?thanks

  回复  引用  查看    
#7楼2009-06-10 09:05 | 置身珠海,学习与奋斗      
--引用--------------------------------------------------
lhking: 曾经看到的,C#注入,没有这么麻烦,只是调用几个函数就可以了。C#注入后,能干什么呢?
--------------------------------------------------------
注入了,能做的事还少吗?

  回复  引用  查看    
#8楼2009-06-10 09:10 | 小庄      
楼主提供的demo好像抓不住哦?
  回复  引用  查看    
#9楼[楼主]2009-06-10 09:15 | 周银辉      
@小庄
我试试,可以的啊, 先点击第一个按钮,待第2个程序启动后,点击第二个按钮哈:)

  回复  引用  查看    
#10楼2009-06-10 09:31 | 一线风      
是不是用来做游戏修改器呀什么的?

  回复  引用  查看    
#11楼2009-06-10 09:34 | zitsing      
vs版本这么多,为什么不说明一下你使用的是哪个版本呢?
  回复  引用    
#12楼2009-06-10 09:40 | cyberhedgehog[未注册用户]
C#注入,几行代码,那是调用winAPI的。
真正能实现的,还是写C C++好

  回复  引用  查看    
#13楼2009-06-10 09:46 | pythonic      
怎么看怎么觉得C++/CLI好丑陋……
  回复  引用  查看    
#14楼[楼主]2009-06-10 09:48 | 周银辉      
@zitsing
sorry~ vs2008, .net3.5

  回复  引用  查看    
#15楼2009-06-10 10:18 | lhking      
@周银辉 可能有点多
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace dllinject
{
public partial class Form1 : Form
{
[DllImport(kernel32.dll)] //声明API函数
public static extern int VirtualAllocEx(IntPtr hwnd, int lpaddress, int size, int type, int tect);
[DllImport(kernel32.dll)]
public static extern int WriteProcessMemory(IntPtr hwnd, int baseaddress, string buffer, int nsize, int filewriten );
[DllImport(kernel32.dll)]
public static extern int GetProcAddress(int hwnd, string lpname);
[DllImport(kernel32.dll)]
public static extern int GetModuleHandleA(string name);
[DllImport(kernel32.dll)]
public static extern int CreateRemoteThread(IntPtr hwnd, int attrib, int size, int address, int par, int flags, int threadid);
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
int ok1;
//int ok2;
//int hwnd;
int baseaddress;
int temp=0;
int hack;
int yan;
string dllname;
dllname = c:\\dll.dll;
int dlllength;
dlllength = dllname.Length 1;
Process[] pname = Process.GetProcesses(); //取得所有进程
foreach (Process name in pname) //遍历进程
{
//MessageBox.Show(name.ProcessName.ToLower());
if (name.ProcessName.ToLower().IndexOf(notepad) != -1) //所示记事本,那么下面开始注入
{

baseaddress = VirtualAllocEx(name.Handle, 0, dlllength , 4096, 4); //申请内存空间
if (baseaddress == 0) //返回0则操作失败,下面都是
{
MessageBox.Show(申请内存空间失败!!);
Application.Exit();
}
ok1 = WriteProcessMemory(name.Handle, baseaddress, dllname, dlllength, temp); //写内存
if (ok1 == 0)
{

MessageBox.Show(写内存失败!!);
Application.Exit();
}
hack = GetProcAddress(GetModuleHandleA(Kernel32), LoadLibraryA); //取得loadlibarary在kernek32.dll地址
if (hack == 0)
{
MessageBox.Show(无法取得函数的入口点!!);
Application.Exit();
}
yan = CreateRemoteThread(name.Handle, 0, 0, hack, baseaddress, 0, temp); //创建远程线程。
if (yan == 0)
{
MessageBox.Show(创建远程线程失败!!);
Application.Exit();
}
else
{
MessageBox.Show(已成功注入dll!!);
}

}

}

}
}


  回复  引用  查看    
#16楼2009-06-10 10:21 | lhking      
@置身珠海,学习与奋斗
c# 注入后,怎样给dll初始化呢?c# dll没有入口呀。

  回复  引用  查看    
#17楼2009-06-10 10:25 | 郑希强      
顶下
  回复  引用  查看    
#18楼[楼主]2009-06-10 10:32 | 周银辉      
@lhking
非常感谢,抽空包装一下~

  回复  引用  查看    
#19楼2009-06-10 10:47 | b0b0      
好东西,学习一下
  回复  引用  查看    
#20楼2009-06-10 11:32 | allahfan      
请问你的那个画图的工具是什么啊?visio?
  回复  引用  查看    
#21楼[楼主]2009-06-10 11:44 | 周银辉      
@allahfan
Excel 2007

  回复  引用    
#22楼2009-06-10 11:46 | sharper[未注册用户]
可以看看 EasyHook
  回复  引用  查看    
#23楼[楼主]2009-06-10 11:51 | 周银辉      
@sharper
在codeplex上看到过easyhook, 好像很强大的样子,但还没有来得及阅读其源代码的,呵呵,看完后和大家分享~

  回复  引用  查看    
#24楼2009-06-10 12:32 | KidYang      
参考了,3Q
  回复  引用  查看    
#25楼2009-06-10 12:51 | 非主流程序员      
周老大是不是教小孩子玩黑客技术啊?
想问一下这种技术的正确用途?

  回复  引用  查看    
#26楼[楼主]2009-06-10 13:21 | 周银辉      
@非主流程序员
------------------
上面写清楚了的哈,呵呵呵,不要误会哦~~~
"仅供参考(日志内容仅仅记录了一种存在性,并非特定问题的解决方案,讨论其意义性是毫无意义的,谢谢)"

我这儿出现了BadImageFormatException.
  回复  引用  查看    
#28楼2009-06-10 17:54 | testName      
小声地问一下:图片中的签名是手写的吗?是如何做到的?要是用鼠标可没法写这么好呢
  回复  引用  查看    
#29楼[楼主]2009-06-10 18:09 | 周银辉      
@testName
普通字体而已,方正静蕾体

  回复  引用  查看    
#30楼2009-06-10 20:33 | b0b0      
@lhking
注入dll 后如何调用dll中的方法?

  回复  引用    
#31楼2009-06-11 09:42 | 无立意[未注册用户]
其实我没明白这样做法会在何种场景里使用到,并且对于程序效率来说会有怎样的影响,本身C#效率就不咋地,还不如直接用C++HOOK来做
  回复  引用    
#32楼2009-06-11 13:56 | gunzhu[未注册用户]
举报一个网站,长期转载博客园文章但不标明原文或原作者链接,只有文字:http://tech.ddvip.com/2009-06/1244617176123127.html
  回复  引用  查看    
#33楼2009-08-30 08:48 | rex xiang      
这个...不就是广为流传的"远程线程注入"么?!