-
-
[原创]代码注入
-
发表于: 2021-4-16 21:39 12301
-
代码注入是一种向目标进程插入独立运行的代码并使之运行的技术,它一般调用CreateRemoteThread() API以远程线程形式运行插入的代码,所以也可称为线程注入。
向目标进程插入代码和数据,代码以线程过程(Thread Procedure)形式插入,而代码中的数据则以线程参数的形式传入。
对于dll注入来说,整个dll都会被插入目标进程,代码和数据共享于内存,所以代码能够正常的运行。与dll注入不同,代码注入仅仅向目标进程注入必要的代码,要想使注入的代码正常运行,还必须将代码中的数据一同注入(并将已注入数据的地址明确告知代码)。
使用代码注入的优缺分析:
占用的内存少。难以查找痕迹,采用dll注入的话会在目标进程的内存中留下痕迹,很容易就能判断出目标进程是否被执行过注入操作,使用代码注入的话几乎不会留下任何痕迹(还是有方法可以检测的),所以恶意代码中大量使用代码注入技术。
简介:查看进程的pid,然后在控制台运行程序:CodeInjection.exe [pid],向对应进程插入一个消息窗口(MessageBoxA)。
注:生成可执行文件的时候一定要使用release版本
先运行notepad++.exe,然后使用ProcessExploer查看进程PID
管理员模式运行CMD, 进入对应的文件夹后,输入命令
执行结果
将notepad拖入,并让其运行起来
然后点击选项
然后注入代码
然后就会断到我们的线程函数
随便使用一个程序,拖入OD,我们使用OD来编写我们的注入汇编代码
PUSH EBP
MOV EBP,ESP
注释:
生成注入代码ThreadProc自己的栈帧,可以稍微记下硬编码55 8BEC
MOV ESI,DWORD PTR SS:[EBP+8]
注释:
[EBP+8],ThreadProc函数的唯一一个参数LPVOID lParam,这个参数就是我们定义的线程参数的结构体的地址,将其mov给了esi,那么之后ESI和ESI+4就可以取到我们定义的结构体的两个成员(API的函数地址)这个定义的结构体看后面具体代码
[EBP+4]是调用该函数时call压入的返回地址
PUSH 00006C6C "\0\0ll"
PUSH 642E3233 "d.23"
PUSH 72657375 "resu"
注释:
将user32.dll字符串压入栈中(仔细思考一下,结合小端序和栈的排列)
这算是一个必须掌握的一个简单技能
PUSH ESP
注释:
将ESP压入栈中,因为从ESP开始是储存的字符串,ESP就是字符串的起始地址,就相当于把字符串压入了栈中,作为下面调用LoadLibraryA()函数的参数
CALL DWORD PTR DS:[ESI]
注释:
调用ESI处 储存的函数地址 对应的函数,LoadLibraryA(),汇编语言调用完函数之后返回值一般都是储存到eax寄存器中
PUSH 41786F "\0Axo"
PUSH 42656761 "Bega"
PUSH 7373654D "sseM"
注释:
将"MessageBoxA"字符串压入栈中
PUSH ESP
PUSH EAX
CALL DWORD PTR DS:[ESI+4]
注释:
现在的ESP就是字符串"MessageBoxA"的起始地址,将其压入了栈中作为函数的第二个参数,EAX里面的值是调用LoadLibraryA()函数得到的模块的起始地址,hMod。
这里就是GetProcAddress(hMod,"MessageBoxA");
接下来,我们就要调用MessageBoxA这个函数了,因为GetProcAddress调用完毕后EAX中储存的已经 是函数MessageBoxA的地址了
MessageBoxA(NULL,"www.reversecore.com", "ReverseCore", MB_OK);
先压入从右到左的第一个参数MB_OK
PUSH 0
然后压入"ReverseCore"这个字符串,这里有一个小技巧,可以记住,就是利用call等价于push + jmp的特点
假设上一条指令所处:
002D002C 6A 00 PUSH 0
那么接下来,在下方002D0033处,即call指令之后开始,存放我们的字符串"ReverseCore",存放的区域就是002D0033-002D003F之间
然后我们call的话,就直接call 002D003F
这样先将字符串“ReverseCore” push进入了栈中,然后有jmp跳转到了地址002D003F处
同理,现在我们要压入参数"www.reversecore.com"
002D003F-002D0043之间存放指令call 002D0058
002D0044-002D0057之间存放我们的字符串"www.reversecore.com"
这样既将下面的数据push进入了栈中,还跳转到了下方
然后压入第一个参数NULL
push 0
最后调用我们的MessageBoxA函数
CALL EAX
之后调用完函数之后,我们要将返回值设为0
使用指令XOR EAX, EAX便可以简单并快捷的将返回值设置为0
MOV ESP,EBP
POP EBP
RETN
注释:
删除栈帧及函数返回
所有的汇编指令
编辑完了之后, 右键,Copy to executable-All modifications
弹出消息框之后点击Copy all
然后弹出一个显示所有修改的窗口,我们鼠标右键Save file,即可保存出去
然后我们得到shellcode
#include <windows.h>
#include <stdio.h>
#include <windef.h>
/
*
FARPROC 是一个
4
字节指针,指向一个函数的内存地址,例如
GetProcAddress 的返回类型就是一个FARPROC。
如果你要存放这个地址,可以声明以一个 FARPROC变量来存放。
*
/
typedef struct _THREAD_PARAM
{
FARPROC pFunc[
2
];
/
/
LoadLibraryA(), GetProcAddress(),存放两个API函数的地址
char szBuf[
4
][
128
];
/
/
"user32.dll"
,
"MessageBoxA"
,
"www.reversecore.com"
,
"ReverseCore"
} THREAD_PARAM,
*
PTHREAD_PARAM;
typedef HMODULE(WINAPI
*
PFLOADLIBRARYA)
(
LPCSTR lpLibFileName
/
/
LoadLibraryA的函数指针,HMODULE的返回值,调用约定为WINAPI,指针名叫PFLOADLIBRARYA,参数为字符串指针lpLibFileName
);
typedef FARPROC(WINAPI
*
PFGETPROCADDRESS)
/
/
同理GetProcAddress()的函数指针
(
HMODULE hModule,
/
/
模块句柄
LPCSTR lpProcName
/
/
查找的函数名字符串指针
);
typedef
int
(WINAPI
*
PFMESSAGEBOXA)
/
/
MessageBoxA的函数指针
(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
DWORD WINAPI ThreadProc(LPVOID lParam)
/
/
线程过程,用来存放我们的注入代码
{
PTHREAD_PARAM pParam
=
(PTHREAD_PARAM)lParam;
/
/
将传入的指针强制类型转换为我们定义的线程参数结构体类型的指针,其实指针本质都是相同的,只是语言的语法要求
HMODULE hMod
=
NULL;
/
/
定义一个hMod来存放模块句柄
FARPROC pFunc
=
NULL;
/
/
定义一个FARPROC类型的变量来存放GetProcAddress的返回值
hMod
=
((PFLOADLIBRARYA)pParam
-
>pFunc[
0
])(pParam
-
>szBuf[
0
]);
/
/
LoadLibrary(
"user32.dll"
)
if
(!hMod)
return
1
;
pFunc
=
(FARPROC)((PFGETPROCADDRESS)pParam
-
>pFunc[
1
])(hMod, pParam
-
>szBuf[
1
]);
/
/
GetProcAddress(hMod,
"MessageBoxA"
);
if
(!pFunc)
return
1
;
((PFMESSAGEBOXA)pFunc)(NULL, pParam
-
>szBuf[
2
], pParam
-
>szBuf[
3
], MB_OK);
/
/
MessageBoxA(NULL,
"www.reversecore.com"
,
"ReverseCore"
, MB_OK);
return
0
;
}
BOOL
InjectCode(DWORD dwPID)
{
HMODULE hMod
=
NULL;
THREAD_PARAM param
=
{
0
, };
HANDLE hProcess
=
NULL;
HANDLE hThread
=
NULL;
LPVOID pRemoteBuf[
2
]
=
{
0
, };
/
/
void型的长指针,用来指向分配的内存
DWORD dwSize
=
0
;
hMod
=
GetModuleHandleA(
"kernel32.dll"
);
param.pFunc[
0
]
=
GetProcAddress(hMod,
"LoadLibraryA"
);
/
/
给线程参数结构体赋值
param.pFunc[
1
]
=
GetProcAddress(hMod,
"GetProcAddress"
);
strcpy_s(param.szBuf[
0
],
"user32.dll"
);
strcpy_s(param.szBuf[
1
],
"MessageBoxA"
);
strcpy_s(param.szBuf[
2
],
"www.reversecore.com"
);
strcpy_s(param.szBuf[
3
],
"ReverseCore"
);
if
(!(hProcess
=
OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID)))
/
/
根据PID获取进程句柄
{
printf(
"OpenProcess() fail : err_code = %d\n"
, GetLastError());
return
FALSE;
}
dwSize
=
sizeof(THREAD_PARAM);
if
(!(pRemoteBuf[
0
]
=
VirtualAllocEx(hProcess,
NULL,
dwSize,
MEM_COMMIT,
PAGE_READWRITE)))
/
/
在对应的进程内部给线程参数,即注入代码要用到的数据分配空间
{
printf(
"VirtualAllocEx() fail : err_code = %d\n"
, GetLastError());
return
FALSE;
}
if
(!WriteProcessMemory(hProcess,
/
/
将线程参数(注入代码要使用的数据)结构体写入分配的内存
pRemoteBuf[
0
],
(LPVOID)¶m,
dwSize,
NULL))
{
printf(
"WriteProcessMemory() fail : err_code = %d\n"
, GetLastError());
return
FALSE;
}
/
/
就是因为这个的原因,生成的时候必须用release
dwSize
=
(DWORD)InjectCode
-
(DWORD)ThreadProc;
/
/
函数名就是函数的起始地址,在内存中紧跟着ThreadProc函数的就是InjectCode,所以首地址相减就能够得到ThreadProc代码占用的空间
if
(!(pRemoteBuf[
1
]
=
VirtualAllocEx(hProcess,
NULL,
dwSize,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE)))
/
/
为注入代码在对应进程分配空间
{
printf(
"VirtualAllocEx() fail : err_code = %d\n"
, GetLastError());
return
FALSE;
}
if
(!WriteProcessMemory(hProcess,
pRemoteBuf[
1
],
(LPVOID)ThreadProc,
dwSize,
NULL))
/
/
在对应空间中写入代码
{
printf(
"WriteProcessMemory() fail : err_code = %d\n"
, GetLastError());
return
FALSE;
}
if
(!(hThread
=
CreateRemoteThread(hProcess,
/
/
创建远程线程执行注入代码
NULL,
0
,
(LPTHREAD_START_ROUTINE)pRemoteBuf[
1
],
pRemoteBuf[
0
],
0
,
NULL)))
{
printf(
"CreateRemoteThread() fail : err_code = %d\n"
, GetLastError());
return
FALSE;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return
TRUE;
}
BOOL
SetPrivilege(LPCTSTR lpszPrivilege,
BOOL
bEnablePrivilege)
/
/
设置权限
{
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;
if
(!OpenProcessToken(GetCurrentProcess(),
/
/
打开一个与进程相关联的访问token
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken))
{
printf(
"OpenProcessToken error: %u\n"
, GetLastError());
return
FALSE;
}
if
(!LookupPrivilegeValue(NULL,
/
/
查找系统对应的权限值并储存到luid中
lpszPrivilege,
&luid))
{
printf(
"LookupPrivilegeValue error: %u\n"
, GetLastError());
return
FALSE;
}
tp.PrivilegeCount
=
1
;
/
/
将这些都存入到TOKEN_PRIVILEGES结构体中
tp.Privileges[
0
].Luid
=
luid;
if
(bEnablePrivilege)
tp.Privileges[
0
].Attributes
=
SE_PRIVILEGE_ENABLED;
/
/
并设置权限属性
else
tp.Privileges[
0
].Attributes
=
0
;
if
(!AdjustTokenPrivileges(hToken,
/
/
启用提权
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL))
{
printf(
"AdjustTokenPrivileges error: %u\n"
, GetLastError());
return
FALSE;
}
if
(GetLastError()
=
=
ERROR_NOT_ALL_ASSIGNED)
{
printf(
"The token does not have the specified privilege. \n"
);
return
FALSE;
}
return
TRUE;
}
int
main(
int
argc, char
*
argv[])
{
DWORD dwPID
=
0
;
if
(argc !
=
2
)
{
printf(
"\n USAGE : %s <pid>\n"
, argv[
0
]);
return
1
;
}
if
(!SetPrivilege(SE_DEBUG_NAME, TRUE))
return
1
;
dwPID
=
(DWORD)atol(argv[
1
]);
/
/
将字符串转换为长整型
InjectCode(dwPID);
return
0
;
}
#include <windows.h>
#include <stdio.h>
#include <windef.h>
/
*
FARPROC 是一个
4
字节指针,指向一个函数的内存地址,例如
GetProcAddress 的返回类型就是一个FARPROC。
如果你要存放这个地址,可以声明以一个 FARPROC变量来存放。
*
/
typedef struct _THREAD_PARAM
{
FARPROC pFunc[
2
];
/
/
LoadLibraryA(), GetProcAddress(),存放两个API函数的地址
char szBuf[
4
][
128
];
/
/
"user32.dll"
,
"MessageBoxA"
,
"www.reversecore.com"
,
"ReverseCore"
} THREAD_PARAM,
*
PTHREAD_PARAM;
typedef HMODULE(WINAPI
*
PFLOADLIBRARYA)
(
LPCSTR lpLibFileName
/
/
LoadLibraryA的函数指针,HMODULE的返回值,调用约定为WINAPI,指针名叫PFLOADLIBRARYA,参数为字符串指针lpLibFileName
);
typedef FARPROC(WINAPI
*
PFGETPROCADDRESS)
/
/
同理GetProcAddress()的函数指针
(
HMODULE hModule,
/
/
模块句柄
LPCSTR lpProcName
/
/
查找的函数名字符串指针
);
typedef
int
(WINAPI
*
PFMESSAGEBOXA)
/
/
MessageBoxA的函数指针
(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
DWORD WINAPI ThreadProc(LPVOID lParam)
/
/
线程过程,用来存放我们的注入代码
{
PTHREAD_PARAM pParam
=
(PTHREAD_PARAM)lParam;
/
/
将传入的指针强制类型转换为我们定义的线程参数结构体类型的指针,其实指针本质都是相同的,只是语言的语法要求
HMODULE hMod
=
NULL;
/
/
定义一个hMod来存放模块句柄
FARPROC pFunc
=
NULL;
/
/
定义一个FARPROC类型的变量来存放GetProcAddress的返回值
hMod
=
((PFLOADLIBRARYA)pParam
-
>pFunc[
0
])(pParam
-
>szBuf[
0
]);
/
/
LoadLibrary(
"user32.dll"
)
if
(!hMod)
return
1
;
pFunc
=
(FARPROC)((PFGETPROCADDRESS)pParam
-
>pFunc[
1
])(hMod, pParam
-
>szBuf[
1
]);
/
/
GetProcAddress(hMod,
"MessageBoxA"
);
if
(!pFunc)
return
1
;
((PFMESSAGEBOXA)pFunc)(NULL, pParam
-
>szBuf[
2
], pParam
-
>szBuf[
3
], MB_OK);
/
/
MessageBoxA(NULL,
"www.reversecore.com"
,
"ReverseCore"
, MB_OK);
return
0
;
}
BOOL
InjectCode(DWORD dwPID)
{
HMODULE hMod
=
NULL;
THREAD_PARAM param
=
{
0
, };
HANDLE hProcess
=
NULL;
HANDLE hThread
=
NULL;
LPVOID pRemoteBuf[
2
]
=
{
0
, };
/
/
void型的长指针,用来指向分配的内存
DWORD dwSize
=
0
;
hMod
=
GetModuleHandleA(
"kernel32.dll"
);
param.pFunc[
0
]
=
GetProcAddress(hMod,
"LoadLibraryA"
);
/
/
给线程参数结构体赋值
param.pFunc[
1
]
=
GetProcAddress(hMod,
"GetProcAddress"
);
strcpy_s(param.szBuf[
0
],
"user32.dll"
);
strcpy_s(param.szBuf[
1
],
"MessageBoxA"
);
strcpy_s(param.szBuf[
2
],
"www.reversecore.com"
);
strcpy_s(param.szBuf[
3
],
"ReverseCore"
);
if
(!(hProcess
=
OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID)))
/
/
根据PID获取进程句柄
{
printf(
"OpenProcess() fail : err_code = %d\n"
, GetLastError());
return
FALSE;
}
dwSize
=
sizeof(THREAD_PARAM);
if
(!(pRemoteBuf[
0
]
=
VirtualAllocEx(hProcess,
NULL,
dwSize,
MEM_COMMIT,
PAGE_READWRITE)))
/
/
在对应的进程内部给线程参数,即注入代码要用到的数据分配空间
{
printf(
"VirtualAllocEx() fail : err_code = %d\n"
, GetLastError());
return
FALSE;
}
if
(!WriteProcessMemory(hProcess,
/
/
将线程参数(注入代码要使用的数据)结构体写入分配的内存
pRemoteBuf[
0
],
(LPVOID)¶m,
dwSize,
NULL))
{
printf(
"WriteProcessMemory() fail : err_code = %d\n"
, GetLastError());
return
FALSE;
}
/
/
就是因为这个的原因,生成的时候必须用release
dwSize
=
(DWORD)InjectCode
-
(DWORD)ThreadProc;
/
/
函数名就是函数的起始地址,在内存中紧跟着ThreadProc函数的就是InjectCode,所以首地址相减就能够得到ThreadProc代码占用的空间
if
(!(pRemoteBuf[
1
]
=
VirtualAllocEx(hProcess,
NULL,
dwSize,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE)))
/
/
为注入代码在对应进程分配空间
{
printf(
"VirtualAllocEx() fail : err_code = %d\n"
, GetLastError());
return
FALSE;
}
if
(!WriteProcessMemory(hProcess,
pRemoteBuf[
1
],
(LPVOID)ThreadProc,
dwSize,
NULL))
/
/
在对应空间中写入代码
{
printf(
"WriteProcessMemory() fail : err_code = %d\n"
, GetLastError());
return
FALSE;
}
if
(!(hThread
=
CreateRemoteThread(hProcess,
/
/
创建远程线程执行注入代码
NULL,
0
,
(LPTHREAD_START_ROUTINE)pRemoteBuf[
1
],
pRemoteBuf[
0
],
0
,
NULL)))
{
printf(
"CreateRemoteThread() fail : err_code = %d\n"
, GetLastError());
return
FALSE;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return
TRUE;
}
BOOL
SetPrivilege(LPCTSTR lpszPrivilege,
BOOL
bEnablePrivilege)
/
/
设置权限
{
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;
if
(!OpenProcessToken(GetCurrentProcess(),
/
/
打开一个与进程相关联的访问token
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken))
{
printf(
"OpenProcessToken error: %u\n"
, GetLastError());
return
FALSE;
}
if
(!LookupPrivilegeValue(NULL,
/
/
查找系统对应的权限值并储存到luid中
lpszPrivilege,
&luid))
{
printf(
"LookupPrivilegeValue error: %u\n"
, GetLastError());
return
FALSE;
}
tp.PrivilegeCount
=
1
;
/
/
将这些都存入到TOKEN_PRIVILEGES结构体中
tp.Privileges[
0
].Luid
=
luid;
if
(bEnablePrivilege)
tp.Privileges[
0
].Attributes
=
SE_PRIVILEGE_ENABLED;
/
/
并设置权限属性
else
tp.Privileges[
0
].Attributes
=
0
;
if
(!AdjustTokenPrivileges(hToken,
/
/
启用提权
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL))
{
printf(
"AdjustTokenPrivileges error: %u\n"
, GetLastError());
return
FALSE;
}
if
(GetLastError()
=
=
ERROR_NOT_ALL_ASSIGNED)
{
printf(
"The token does not have the specified privilege. \n"
);
return
FALSE;
}
return
TRUE;
}
int
main(
int
argc, char
*
argv[])
{
DWORD dwPID
=
0
;
if
(argc !
=
2
)
{
printf(
"\n USAGE : %s <pid>\n"
, argv[
0
]);
return
1
;
}
if
(!SetPrivilege(SE_DEBUG_NAME, TRUE))
return
1
;
dwPID
=
(DWORD)atol(argv[
1
]);
/
/
将字符串转换为长整型
InjectCode(dwPID);
return
0
;
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!