DLL劫持技术

上一篇 / 下一篇  2008-08-24 07:05:32

C O6` F8a N3M)R0在逆向工程中,经常需要改变程序原有的执行流程,使其增加或者减少一些功能代码,这就需要对原文件进行补丁。补丁分文件补丁和内存补丁两种。文件补丁就是修改文件本身某个数据,达到一劳永逸的效果。顾名思义,内存补丁是在内存中打补丁、修改,确切地说,是对正在运行的程序的数据进行修改,以达到某种效果。IXPUB技术博客NyQw"L H+g

%n2i/j UTp0……略……

`0Ou u)s4Dz0

y}QO*V9CB018.2.4  DLL劫持技术

J \o.d cA |X0IXPUB技术博客sr!Ih d;^;N#q#P

当一个可执行文件运行时,Windows加载器将可执行模块映射到进程的地址空间中,加载器分析可执行模块的输入表,并设法找出任何需要的DLL,并将它们映射到进程的地址空间中。IXPUB技术博客6d Z;?0S'C

IXPUB技术博客+`2v e7e.J6p*{

由于输入表中只包含DLL名而没有它的路径名,因此加载程序必须在磁盘上搜索DLL文件。首先会尝试从当前程序所在的目录加载DLL,如果没找到,则在Windows系统目录中查找,最后是在环境变量中列出的各个目录下查找。利用这个特点,先伪造一个系统同名的DLL,提供同样的输出表,每个输出函数转向真正的系统DLL。程序调用系统DLL时会先调用当前目录下伪造的DLL,完成相关功能后,再跳到系统DLL同名函数里执行,如图18.3所示。这个过程用个形象的词来描述就是系统DLL被劫持(hijack)了。

L$aOd8`"c`!v'dg\0
 
图18.3  DLL劫持技术演示

%Ys"Q8E}3Wgz0利用这种方法取得控制权后,可以对主程序进行补丁。此种方法只对除kernel32.dll、ntdll.dll等核心系统库以外的DLL有效,如网络应用程序的ws2_32.dll、游戏程序中的d3d8.dll,还有大部分应用程序都调用的lpk.dll,这些DLL都可被劫持。

~4W X'[t n5_D0

N f*w#?5vXT-v0利用第5章5.6.2节提供的CrackMeNet.exe来演示一下如何利用劫持技术制作补丁,目标文件用Themida v1.9.2.0加壳保护。

,Y@/m9YG)ML7X*S3n0IXPUB技术博客^&fc1Jfb}1p pX,e

1.补丁地址IXPUB技术博客S.f&i#?9f3vh/g.K,Bc

IXPUB技术博客q2H!R,r |q Q

去除这个CrackMe网络验证方法参考第5章5.6.2节,将相关补丁代码存放到函数PatchProcess()里。例如将401496h改成:

] C'H%} q&LN gY0O0
00401496   EB 29    jmp     short 004014C1
补丁编程实现就是:
&czJ:M+R0
unsigned char p401496[2] = {0xEB, 0x29};IXPUB技术博客6S!Q*[3u)f
lU
WriteProcessMemory(hProcess,(LPVOID)0x401496, p401496, 2, NULL);

oA6]'nW G0p401496这个数组的数据格式,可以用OllyDbg插件获得,或十六进制工具转换。例如Hex Workshop打开文件,执行菜单“Edit/Copy As/Source”即可得到相应的代码格式。IXPUB技术博客W],S!K8H

IXPUB技术博客ZF$ZO.a9\P,a&Q

2.构建输出函数

E\'~/pvHGD)F0IXPUB技术博客*?7on$\h

查看实例CrackMeNet.exe输入表,会发现名称为“ws2_32.dll”的DLL,因此构造一个同名的DLL来完成补丁任务。伪造的ws2_32.dll有着真实ws2_32.dll一样的输出函数,完整源码见光盘映像文件。实现时,可以利用DLL模块中的函数转发器来实现这个目标,其会将对一个函数的调用转至另一个DLL中的另一个函数。可以这样使用一个pragma指令:

[qO,uF\#J9C0
#pragma comment(linker, "/EXPORT:SomeFunc=DllWork.someOtherFunc")
IXPUB技术博客t~ CwFS_

这个pragma告诉链接程序,被编译的DLL应该输出一个名叫SomeFunc的函数。但是SomeFunc函数的实现实际上位于另一个名叫SomeOtherFunc的函数中,该函数包含在称为DllWork. dll的模块中。

o+Igh q{ c8V!W0IXPUB技术博客+?1EM[3G4Y7RV M8N

如果要达到劫持DLL的目的,生成的DLL输出函数必须与目标DLL输出函数名一样。本例可以这样构造pragma指令:
:i2{#?N W,Zv}Q#Xc0IXPUB技术博客%~'y0Q&|v9x e&]

#pragma comment(linker, "/EXPORT:WSAStartup=_MemCode_WSAStartup,@115")

hJ1l:dc0编译后的DLL,会有与ws2_32.dll同名的一个输出函数WSAStartup,实际操作时,必须为想要转发的每个函数创建一个单独的pragma代码行,读者可以用工具AheadLib或用其他办法,将ws2_32.dll输出函数转换成相应的pragma指令。

6sa!O^ UpZo W0

K2|&?:v jx0当应用程序调用伪装ws2_32.dll的输出函数时,必须将其转到系统ws2_32.dll中,这部分的代码自己实现。例如,WSAStartup输出函数构造如下:

|M {(_*`u RV0
ALCDECL MemCode_WSAStartup(void)IXPUB技术博客8h:d8j.rV}
{
+F Nm8q%xN$G'?"d+f"N0GetAddress("WSAStartup");IXPUB技术博客-@^S? Q
__asm JMP EAX;//转到系统ws2_32.dll的WSAStartup输出函数
b[*v o#u G K0ZA0}
其中,GetAddress()函数的代码如下:IXPUB技术博客y$p8}0Yx])K]G
// MemCode 命名空间IXPUB技术博客{\	Rc!Y4DQ
namespace MemCode
*D"J'Qd0lK+H'zko9C8x0@0{IXPUB技术博客7hBx Pd'_8T
HMODULE m_hModule = NULL;     //原始模块句柄
| hm;j5I0DWORD m_dwReturn[500] = {0};     //原始函数返回地址
cS `5I6uQ@M1B0// 加载原始模块
n8|M]5j)hq&v0inline BOOL WINAPI Load()IXPUB技术博客6[(}/e _@X@
{
M)m3wg@7mO_G)\;\0TCHAR tzPath[MAX_PATH]={0};IXPUB技术博客 N`bpa[ v&d
TCHAR tzTemp[MAX_PATH]={0};
DZF X!{F~0GetSystemDirectory(tzPath, sizeof(tzPath));IXPUB技术博客Wh-U%Wp N9p#KBe
strcat(tzPath,"\\ws2_32.dll");
p.P"n%[(mC&L;JA0m_hModule = LoadLibrary(tzPath);//加载系统系统目录下ws2_32.dllIXPUB技术博客Xx^)ZT%QF*{
if (m_hModule == NULL)
*ben*E%K.YV|h0{IXPUB技术博客!FD*u(\A
wsprintf(tzTemp, TEXT("无法加载 %s,程序无法正常运行。"), tzPath);IXPUB技术博客-Ze!Z`wI4H t
MessageBox(NULL, tzTemp, TEXT("MemCode"), MB_ICONSTOP);IXPUB技术博客)A+A{)Y!y Lzm
}IXPUB技术博客&oq*rT0L|3n
return (m_hModule != NULL); 
/k*MN4U!U7`2Ck.y C0}
// 释放原始模块IXPUB技术博客`r*`F0W*o r
inline VOID WINAPI Free()IXPUB技术博客*J#T y1e&q [Q
{IXPUB技术博客 S X^~,E%_!L%S
if (m_hModule)IXPUB技术博客0T2?!I @c:WwE
FreeLibrary(m_hModule);
?&T Dd3u0}
}#Q{!zW \0// 获取原始函数地址
:k&Oe0\8I g6HR0FARPROC WINAPI GetAddress(PCSTR pszProcName)
J T8C2h3O4M.o0{
$U$JysU0FARPROC fpAddress;
%m6w&B X f0A9q-p0TCHAR szProcName[16]={0};IXPUB技术博客 @3A;M k7Y']A6Z
TCHAR tzTemp[MAX_PATH]={0};IXPUB技术博客Z|z c s.Be

rZ(nd;gKD0if (m_hModule == NULL)IXPUB技术博客4m+B&qy1c
{
8X"C4}T9{ S _ GG!R0if (Load() == FALSE)IXPUB技术博客.n3g/v:X+m RE!r+s&M
ExitProcess(-1);
"z{.G#h*ZR0}
.E"_#]iT3f&i*B0IXPUB技术博客Q{5g K6RM#h4Ur
fpAddress = GetProcAddress(m_hModule, pszProcName);
1o)lZ-T-Yz3U0if (fpAddress == NULL)IXPUB技术博客:^+M pf6j?z;W
{IXPUB技术博客${0Zn3u/D1gF P
if (HIWORD(pszProcName) == 0)
AfOj)WT.U9{0{IXPUB技术博客3xv,Rc#L;LO0mJdd+\
wsprintf(szProcName, "%d", pszProcName);IXPUB技术博客.b2lt'^I&|)z
pszProcName = szProcName;
&Sz,p2`Q9[&e0}IXPUB技术博客 Qi t6WMO
wsprintf(tzTemp, TEXT("无法找到函数 %hs,程序无法正常运行。"), pszProcName);IXPUB技术博客` x[Sj{F
MessageBox(NULL, tzTemp, TEXT("MemCode"), MB_ICONSTOP);IXPUB技术博客4r~};LtIdJ
ExitProcess(-2);
_ z[,bx O{0}IXPUB技术博客 ]7n |ncc6C7_8v
return fpAddress;IXPUB技术博客 HIIs,yr.]
}
-z/Gm;y L6g,J ZRl0}
}2O2s-e8Hh+n%y0using namespace MemCode;
编译后,用LordPE查看伪造的ws2_32.dll输出函数,和真实ws2_32.dll完全一样,如图18.4所示。IXPUB技术博客W$Ew? mKV3s

Z`e8LU D?wb0
 
(点击查看大图)图18.4  伪造ws2_32.dll的输出表
查看伪造的ws2_32.dll中任意一个输出函数,例如WSACleanup:
OoY4s4GU0
.text:10001CC0 ; int __stdcall WSACleanup()IXPUB技术博客"o	p$H6bf
.text:10001CC0 WSACleanup   proc nearIXPUB技术博客Wzf ?P
.text:10001CC0                push    offset aWsacleanup ;"WSACleanup"
$a5NC\8y)Y.y6?0.text:10001CC5                call    sub_10001000    ;GetAddress(WSACleanup)IXPUB技术博客 I3@5S}$f
.text:10001CCA                jmp     eax
!U6O wcx)A Q8O)V L0.text:10001CCA WSACleanup   endp
IXPUB技术博客&^-F-sf/M&g

会发现输出函数WSACleanup()首先调用GetAddress(WSACleanup),获得真实ws2_32.dll中WSACleanup的地址,然后跳过去执行,也就是说,ws2_32.dll各输出函数被Hook了。

EE;PxAf8X0IXPUB技术博客{,c] R+M:[g:J;]

3.劫持输出函数IXPUB技术博客F!])cd;eRD,`DG

-GUf.^ M9_t&k3I0ws2_32.dll有许多输出函数,经分析,程序发包或接包时,WSAStartup输出函数调用的比较早,因此在这个输出函数放上补丁的代码。代码如下:IXPUB技术博客#_@*}i*J a,[

ALCDECL MemCode_WSAStartup(void)
1\(vauQ~ t-~0{
D;K)\ | ^%xS0hijack();
g([he,xd]L#Pg0GetAddress("WSAStartup");IXPUB技术博客/c2b g~,V6|
__asm JMP EAX;IXPUB技术博客R"S)au6S5F9W]
}

ie@-L9S m9R+D)X Ao0hijack()函数主要是判断是不是目标程序,如果是就调用PatchProcess()进行补丁。
void hijack()
t)t6W+Q!uAQWiq0{
E]$o"LFXG K0if (isTarget(GetCurrentProcess()))   //判断主程序是不是目标程序,是则补丁之
J4{C*]?)}Ya0{IXPUB技术博客4pP8{;N^ V }
PatchProcess(GetCurrentProcess());
x'Q[|Q0}IXPUB技术博客/M {Bk:F:Ek7V
}
IXPUB技术博客%MD2O,G6H-h#J4YC,r

IXPUB技术博客-nvm,C2I}j
伪造的ws2_32.dll制作好后,放到程序当前目录下,这样当原程序调用WSASTartup函数时就调用了伪造的ws2_32.dll的WSASTartup函数,此时hijack()函数负责核对目标程序校验,并将相关数据补丁好,处理完毕后,转到系统目录下的ws2_32.dll执行。IXPUB技术博客(l Pm!Vq|#cK

O~p2I;_RY:gR8X0这种补丁技术,对加壳保护的软件很有效,选择挂接的函数最好是在壳中没有被调用的,当挂接函数被执行时,相关的代码已被解压,可以直接补丁了。在有些情况下,必须用计数器统计挂接的函数的调用次数来接近OEP。此方法巧妙地绕过了壳的复杂检测,很适合加壳程序的补丁制作。IXPUB技术博客p]M m4wwD5sq

.X |0h6z2C3M2T0一些木马或病毒也会利用DLL劫持技术搞破坏,因此当在应用程序目录下发现系统一些DLL文件存在时,如lpk.dll,应引起注意。IXPUB技术博客zb(b.Y2j,rD a


TAG: 系统安全

 

评分:0

我来说两句

显示全部

:loveliness: :handshake :victory: :funk: :time: :kiss: :call: :hug: :lol :'( :Q :L ;P :$ :P :o :@ :D :( :)

日历

« 2009-01-08  
    123
45678910
11121314151617
18192021222324
25262728293031

数据统计

  • 访问量: 66429
  • 日志数: 1219
  • 文件数: 1
  • 建立时间: 2007-08-10
  • 更新时间: 2009-01-08

RSS订阅

Open Toolbar