逆向中简单的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,参数0x1FFFFFPROCESS_ALL_ACCESS)表示获取完全控制权限,用于后续内存操作。

3. 在目标进程中分配内存

c


v19 = qword_7FF6B82EF370(v18, 0LL, 405LL, 4096LL, 64); // VirtualAllocEx调用

特征

  • 函数qword_7FF6B82EF370实际是VirtualAllocEx,参数:
    • 405LL:分配大小(与Shellcode长度一致)。
    • 4096LLMEM_COMMIT | MEM_RESERVE(提交并保留内存)。
    • 64PAGE_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用于启动记事本,确保目标进程存在

总结:关键特征代码

  1. 进程遍历CreateToolhelp32Snapshot + Process32FirstW/Process32NextW
  2. 内存操作VirtualAllocEx + WriteProcessMemory + PAGE_EXECUTE_READWRITE权限。
  3. 远程执行CreateRemoteThread调用。
  4. 目标进程:硬编码notepad.exe和主动启动行为。
  5. 反调试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再打开:

一下就明了了

posted @ 2025-03-22 23:42  漫宿骄盛  阅读(83)  评论(0)    收藏  举报