逆向中简单的shellcode
做题时遇到了,简单记录一下
一,介绍:
shellcode分为广义和狭义,狭义指的仅仅是通过命令行shell攻击靶机,并取得控制权的代码,广义的指能完成类似任务的代码,通常是汇编/机器码。
不过这里是RE,不是PWN,所以不会有靶机,那么在下文指的是广义的shellcode,注入程序,控制程序的代码。
二,特征:
我也就仅仅是遇到,不知道怎么解决,也没看到什么WP说的清楚的,那就仅仅记录特征,等以后遇到了能知道是shellcode就行。
例题1.[HDCTF 2023]double_code:
主要代码
int __fastcall main_0(int argc, const char **argv, const char **envp)
{
char *v3; // rdi
__int64 i; // rcx
DWORD LastError; // eax
DWORD err; // eax
DWORD err_1; // eax
DWORD err_2; // eax
DWORD err_3; // eax
DWORD err_4; // eax
char v12; // [rsp+40h] [rbp+0h] BYREF
_QWORD buf[17]; // [rsp+50h] [rbp+10h] BYREF
HANDLE hSnapshot; // [rsp+D8h] [rbp+98h]
PROCESSENTRY32W pe; // [rsp+100h] [rbp+C0h] BYREF
BOOL v16; // [rsp+354h] [rbp+314h]
DWORD th32ProcessID; // [rsp+374h] [rbp+334h]
__int64 v18; // [rsp+398h] [rbp+358h]
__int64 v19; // [rsp+3B8h] [rbp+378h]
char v22; // [rsp+594h] [rbp+554h]
v3 = &v12;
for ( i = 254LL; i; --i )
{
*(_DWORD *)v3 = -858993460;
v3 += 4;
}
v22 = 0;
j___CheckForDebuggerJustMyCode(&unk_7FF6B82F401B, argv, envp);
printf("alloc:%p\n", sub_7FF6B82EF000);
memset(buf, 0, 0x70uLL);
buf[0] = 0x4000000070LL;
buf[2] = L"open";
buf[3] = L"notepad.exe";
sub_7FF6B82E10FA(buf);
hSnapshot = j_CreateToolhelp32Snapshot(2u, 0);
if ( hSnapshot == (HANDLE)-1LL )
{
LastError = GetLastError();
printf("CreateToolhelp32Snapshot:%d\n", LastError);
}
pe.dwSize = 568;
v16 = j_Process32FirstW(hSnapshot, &pe);
if ( !v16 )
{
err = GetLastError();
printf("Process32First:%d\n", err);
}
while ( v16 )
{
if ( !wcscmp(pe.szExeFile, L"notepad.exe") )
{
v22 = 1;
th32ProcessID = pe.th32ProcessID;
break;
}
v16 = j_Process32NextW(hSnapshot, &pe);
}
v18 = qword_7FF6B82EF368(0x1FFFFFLL, 0LL, th32ProcessID);
if ( !v18 )
{
err_1 = GetLastError();
printf("\nopenprocess error%d\n", err_1);
}
printf("pid:%d", th32ProcessID);
v19 = qword_7FF6B82EF370(v18, 0LL, 405LL, 4096LL, 64);
if ( !v19 )
{
err_2 = GetLastError();
printf("VirtualAllocEx error%d\n", err_2);
}
if ( !(unsigned int)qword_7FF6B82EF360(v18, v19, sub_7FF6B82EF000, 405LL, 0LL) )
{
err_3 = GetLastError();
printf("WriteProcessMemory:%d\n", err_3);
}
if ( !qword_7FF6B82EF378(v18, 0LL, 0LL, v19, 0LL, 0, 0LL) )
{
err_4 = GetLastError();
printf("CreateRemoteThread:%d\n", err_4);
}
return 0;
}
以下是AI给出的特征:
以下是代码中关键特征代码的标注与分析,用于判断其为进程注入型Shellcode:
1. 进程遍历与目标查找
c
hSnapshot = j_CreateToolhelp32Snapshot(2u, 0); // 创建进程快照(TH32CS_SNAPPROCESS=2)
pe.dwSize = 568; // 初始化PROCESSENTRY32结构大小
v16 = j_Process32FirstW(hSnapshot, &pe); // 获取第一个进程信息
while ( v16 ) {
if ( !wcscmp(pe.szExeFile, L"notepad.exe") ) { // 匹配目标进程名
th32ProcessID = pe.th32ProcessID; // 记录目标进程PID
break;
}
v16 = j_Process32NextW(hSnapshot, &pe); // 遍历下一个进程
}
特征:
- 使用
CreateToolhelp32Snapshot+Process32FirstW/Process32NextW遍历进程列表,定位notepad.exe的PID,是注入前的目标选择阶段。
2. 打开目标进程
c
v18 = qword_7FF6B82EF368(0x1FFFFFLL, 0LL, th32ProcessID); // OpenProcess调用
特征:
- 函数
qword_7FF6B82EF368实际是OpenProcess,参数0x1FFFFF(PROCESS_ALL_ACCESS)表示获取完全控制权限,用于后续内存操作。
3. 在目标进程中分配内存
c
v19 = qword_7FF6B82EF370(v18, 0LL, 405LL, 4096LL, 64); // VirtualAllocEx调用
特征:
- 函数
qword_7FF6B82EF370实际是VirtualAllocEx,参数:
405LL:分配大小(与Shellcode长度一致)。4096LL:MEM_COMMIT | MEM_RESERVE(提交并保留内存)。64:PAGE_EXECUTE_READWRITE(内存可执行、可读写),典型Shellcode内存权限。
4. 写入Shellcode到目标进程
c
qword_7FF6B82EF360(v18, v19, sub_7FF6B82EF000, 405LL, 0LL); // WriteProcessMemory调用
特征:
- 函数
qword_7FF6B82EF360实际是WriteProcessMemory,参数:
sub_7FF6B82EF000:Shellcode起始地址。405LL:写入长度,与分配的405字节内存匹配。- 关键证据:直接向远程进程写入二进制数据(Shellcode)。
5. 创建远程线程执行Shellcode
c
qword_7FF6B82EF378(v18, 0LL, 0LL, v19, 0LL, 0, 0LL); // CreateRemoteThread调用
特征:
- 函数
qword_7FF6B82EF378实际是CreateRemoteThread,参数:
v19:注入的Shellcode内存地址。- 核心行为:在目标进程中启动线程执行Shellcode。
6. 辅助特征(日志与调试)
c
printf("alloc:%p\n", sub_7FF6B82EF000); // 输出Shellcode内存地址
j___CheckForDebuggerJustMyCode(...); // 反调试检测
if (!v18) { printf("openprocess error%d\n", err_1); } // 错误处理
特征:
- 输出Shellcode地址(
sub_7FF6B82EF000)用于调试。 CheckForDebuggerJustMyCode检测调试器,增加逆向难度。- 详细的错误日志(如
CreateRemoteThread:%d\n)帮助攻击者排查注入问题。
7. 主动启动目标进程(可选)
c
buf[2] = L"open";
buf[3] = L"notepad.exe";
sub_7FF6B82E10FA(buf); // ShellExecuteW调用
特征:
- 函数
sub_7FF6B82E10FA实际是ShellExecuteW,参数open notepad.exe用于启动记事本,确保目标进程存在。
总结:关键特征代码
- 进程遍历:
CreateToolhelp32Snapshot+Process32FirstW/Process32NextW。 - 内存操作:
VirtualAllocEx+WriteProcessMemory+PAGE_EXECUTE_READWRITE权限。 - 远程执行:
CreateRemoteThread调用。 - 目标进程:硬编码
notepad.exe和主动启动行为。 - 反调试:
CheckForDebuggerJustMyCode。
这些代码组合是进程注入Shellcode的明确标志,防御系统可通过监控此类API调用链进行检测。
三,可能解决方法:
1.提取机器码:
__int64 sub_7FF6B82EF000()
{
__int64 v0; // rdx
__int64 v1; // rcx
__int64 v2; // r8
__int64 v3; // r9
int v4; // esp
unsigned __int64 v5; // rax
int v6; // esp
unsigned __int64 v7; // rax
int v8; // esp
unsigned __int64 v9; // rax
int v10; // esp
unsigned __int64 v11; // rax
int v12; // esp
unsigned __int64 v13; // rax
_BYTE ____________________________________[57]; // [rsp+1Fh] [rbp-41h] BYREF
int v16; // [rsp+58h] [rbp-8h]
signed int i; // [rsp+5Ch] [rbp-4h]
MEMORY[0x7FF6B82F9C60]();
*(_DWORD *)&____________________________________[37] = 1;
*(_DWORD *)&____________________________________[41] = 5;
*(_DWORD *)&____________________________________[45] = 2;
*(_DWORD *)&____________________________________[49] = 4;
*(_DWORD *)&____________________________________[53] = 3;
strcpy(____________________________________, "************************************");
for ( i = 0;
(unsigned int)MEMORY[0x7FF6B830A250](
v1,
v0,
v2,
v3,
*(_QWORD *)&____________________________________[1],
*(_QWORD *)&____________________________________[9],
*(_QWORD *)&____________________________________[17]) > i;
++i )
{
v0 = (unsigned int)(i / 5);
v1 = (unsigned int)(i % 5);
v16 = i % 5;
if ( i % 5 == 1 )
{
v5 = (unsigned int)(v4 + 31 + i);
v1 = *(unsigned __int8 *)v5 ^ 0x23u;
v0 = (unsigned int)(v4 + 31);
*(_BYTE *)(unsigned int)(v0 + i) = *(_BYTE *)v5 ^ 0x23;
}
else
{
switch ( v16 )
{
case 2:
v7 = (unsigned int)(v6 + 31 + i);
v0 = (unsigned int)*(unsigned __int8 *)v7 + 2;
v1 = (unsigned int)(v6 + 31);
*(_BYTE *)(unsigned int)(v1 + i) = *(_BYTE *)v7 + 2;
break;
case 3:
v9 = (unsigned int)(v8 + 31 + i);
v0 = (unsigned int)*(unsigned __int8 *)v9 - 3;
v1 = (unsigned int)(v8 + 31);
*(_BYTE *)(unsigned int)(v1 + i) = *(_BYTE *)v9 - 3;
break;
case 4:
v11 = (unsigned int)(v10 + 31 + i);
v0 = (unsigned int)*(unsigned __int8 *)v11 - 4;
v1 = (unsigned int)(v10 + 31);
*(_BYTE *)(unsigned int)(v1 + i) = *(_BYTE *)v11 - 4;
break;
case 5:
v13 = (unsigned int)(v12 + 31 + i);
v0 = (unsigned int)*(unsigned __int8 *)v13 - 25;
v1 = (unsigned int)(v12 + 31);
*(_BYTE *)(unsigned int)(v1 + i) = *(_BYTE *)v13 - 25;
break;
}
}
}
return 0LL;
}
这是例题1中shellcode所植入的代码,虽然IDA能分析处理,但还是有一些混淆的,通过看别人的WP,可以看其机器码:

复制下来,保存到txt文件中,再将后缀改为 .bin,然后通过IDA再打开:

一下就明了了
本文来自博客园,作者:漫宿骄盛,转载请注明原文链接:https://www.cnblogs.com/msjs/p/18787340
都是顺手发的,写的时候可能有错误,如果发现了,望各位指出。

浙公网安备 33010602011771号