href="css.css" rel="stylesheet"/>
【目 标】:商业类的东西不便说出名字
【工 具】:Olydbg1.1(diy版)、LORDPE、ImportREC1.6F
【目 的】: 通过注入代码,在程序运行前显示一个提示框
【操作平台】:Windows 2003 server
【作 者】: LOVEBOOM[DFCG][FCG][US]
【简要说明】: 相信会脱这个壳的人已经有很多很多了,在你想明白怎么注入代码之前,你得有一点基础知识,对这个壳比较了解,这篇文章是
讲如何注入,并非脱壳分析,如果你想知道如何脱壳,可以看我以前的文章和别的网友的文章,如果那些文章你都看懂了,而你有一定的基础,相
信这篇文章对你来说是非常简单的:-)。
【详细过程】:
正式开始前先讲一些:这个壳经常伪装成别的壳来欺骗检测工具的检测,骗过对壳没有了解的朋友。这次我找的目标也同样伪装了,这次伪装
成*Neolite 2.0 -> Neoworx Inc.*的了,如果你按那个壳的方法去操作肯定是没有结果的。
这个壳的反跟踪比较多,有精彩代码值得我们学习的。用OD跟踪比用SICE跟踪更为方便,异常太多了。花指令也是做的比较有个性的:-)。
用OD跟踪的的话,只有一个anti-debug可以防止我们跟踪。
也许你会说,直接在壳代码处就改自己的代码不就行了吗?是的,没错。我选择在程序代码解压出来之后再出现信息框是想说,代码已经解压出来
了,你可以修改程序的流程了,可以暴力破解或找修改壳的流程,去除全部的anti-debug让壳和upx一样等等,只有想不到的,没有做不到的。
要在壳运行过程中添加提示信息的话,先得找入手点,找入手点当然比须对壳比较了解,然后考虑我们的写注入的东西里要用到哪些东西,这些东
西又从哪里获取,是自己添加还是直接借用壳的。Patch代码又存放到哪里,是否有足够的空间。等等。
我选择的是在壳解压出程序的代码后,壳CRC检测时用到的UnmapViewOfFile作为入口点,我做的方式是在壳入口处就开始hook掉该进程的UnmapViewOfFile,
让壳执行这个程序之前先显示我想显示的对话框。Hook的过程我们要修改进程空间的代码,所以我们就得先获取相关权限,然后修改内存页,让相关空
间代码能够修改,这个过程我们就得知道,我这里偷了下懒,作为完整的写法应该是修改完后,还原程序代码,然后把相关权限也写回去的,我没有写还
原代码的。获取和修改权限就得用到VirtualQuery和VirtualProtect,用VirtualQuery获取原有的权限后应该保存一个地方呀,这个地方我选择动态申请,动
态申请用取了VirtualAlloc和VirtualFree这两个API。添加信息框要用到MessageBoxA这个Api,这个API在USER32.DLL中,壳开始的时候并没有加载这个dll,所
以还得用到LoadLibraryA,载入了DLL之后,还得取api的地址呀,所以就用到了GetProcAddress,用GetProcAddress中要用到DLL的句柄,获取句柄的话就得用
到GetModuleHandleA。还好壳里已经有GetModuleHandleA这个api。总结一下用到了这么些API:
GETMODULEHANDLEA
GETPROCADDRESS
LOADLIBRARYA
VIRTUALALLOC
VIRTUALFREE
VIRTUALQUERY
VIRTUALPROTECT
UnmapViewOfFile
MESSAGEBOXA
要用的API已经得到,要提示信息还得到在空地上写上想提示的信息,还得把要动态获取地址的API的名字写上去,还得把那两个DLL的名字都写上去。
OK,自己想了这么些情况,然后开始动手:
用peid看了一下报告成伪壳,随便在段后面的空间里写的点东西,再运行,发现程序不让运行,这说明程序有检测。用LordPE看看只有少数几个api函数:
->Import Table
1. ImageImportDescriptor:
OriginalFirstThunk: 0x0007E079
TimeDateStamp: 0x00000000 (GMT: Thu Jan 01 00:00:00 1970)
ForwarderChain: 0x00000000
Name: 0x0007E089 ("KERNEL32.dll")
FirstThunk: 0x0007E079
Ordinal/Hint API name
------------ ---------------------------------------
0x0000 "GetProcAddress"
0x0000 "LoadLibraryA"
0x0000 "GetModuleHandleA"
看到这些输入表信息我们就能达到目标了(如果输入表为空的话,能不能达到我的目的呢?答案是肯定的。因为壳自己也要用到API,壳用什么方法获取,我们也模拟它来完成使命。)
上面分析后代码量可能比较大,所以还得找找有没有足够的空间。所以用lordpe看看程序的”空地”有多少,壳是加密后,通过在原程序的基础上增加一个段来保存解壳代码,所
以我们看看最后一个段有没有足够的空间。最后一个段的信息:
item:
Name:
VirtualSize: 0x00014000
VirtualAddress: 0x0007E000
SizeOfRawData: 0x00014000
PointerToRawData: 0x00029A00
PointerToRelocations: 0x00000000
PointerToLinenumbers: 0x00000000
NumberOfRelocations: 0x0000
NumberOfLinenumbers: 0x0000
Characteristics: 0xC0000040
(INITIALIZED_DATA, READ, WRITE)
看了下空间不是很多,所以自己修改下,把空间改大点,添加了&H1000的空间,这样可以放足够的代码了。修改后:
Name:
VirtualSize: 0x00015000 ************
VirtualAddress: 0x0007E000
SizeOfRawData: 0x00015000 ************
PointerToRawData: 0x00029A00
PointerToRelocations: 0x00000000
PointerToLinenumbers: 0x00000000
NumberOfRelocations: 0x0000
NumberOfLinenumbers: 0x0000
Characteristics: 0xC0000040
(INITIALIZED_DATA, READ, WRITE)
修改好后用OD载入目标,现在来获取LoadLibraryA、GetModuleHandleA、GetProcAddress ,
00491DAD > /E9 00000000 JMP 00491DB2 ; EP,伪装别的壳的入口
00491DB2 /60 PUSHAD
00491DB3 E8 14000000 CALL 00491DCC
00491DB8 5D POP EBP
载入后,下命令g GetModuleHandleA,断下后,通过堆栈看看GetModuleHandleA载入地址:
0012FF9C 0047E117 /CALL to GetModuleHandleA from cjbo.0047E114
0012FFA0 0047E089 /pModule = "KERNEL32.dll"
……
0047E114 FF53 08 CALL DWORD PTR DS:[EBX+8] ; 这里调用GetModuleHandleA
0047E117 8BF8 MOV EDI,EAX ; 返回到这里
计算一下得知GetModuleHandleA保存在0047E081处。因为IAT地址一般是连续的,这样的话,直接DD 47e081看看数据窗口就知道其它两个API保存的位置了:
0047E079 >77E12DFB kernel32.GetProcAddress
0047E07D >77E1850D kernel32.LoadLibraryA
0047E081 >77E12CD1 kernel32.GetModuleHandleA
因为关系到重定位,所以为了防止意外我用到相对地址的方式来获取实际地址。
第一步已经完成了,第二步看看UnmapViewOfFile在哪里调用,看它的原因是去找CRC检测数据。重来, 这次要去除ZwSetInformationThread这个anti-ring3’debug,否
则壳会脱离Debugger,让调试器不能跟踪。去除ZwSetInformation时要注意不能直接改成RET 10之类的,壳会检测的:
00382CB2 8A06 MOV AL,BYTE PTR DS:[ESI]
00382CB4 3C B8 CMP AL,0B8 ; 开始检测了,判断是否为mov eax,xxxxxx的方式,如果不是则跳
00382CB6 75 04 JNZ SHORT 00382CBC
00382CB8 A4 MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
00382CB9 A5 MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ES>
00382CBA EB 33 JMP SHORT 00382CEF
00382CBC 3C 8D CMP AL,8D
00382CBE 75 03 JNZ SHORT 00382CC3 ; 判断是否为LEA的方式
00382CC0 A5 MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ES>
00382CC1 EB 2C JMP SHORT 00382CEF
00382CC3 3C CD CMP AL,0CD ; 判断是否为Int xx的入口
00382CC5 75 04 JNZ SHORT 00382CCB
00382CC7 66:A5 MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]
00382CC9 EB 24 JMP SHORT 00382CEF
00382CCB 3C BA CMP AL,0BA ; 判断是否为mov edx,address的方式
00382CCD 75 04 JNZ SHORT 00382CD3
00382CCF A4 MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
00382CD0 A5 MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ES>
00382CD1 EB 1C JMP SHORT 00382CEF
00382CD3 3C FF CMP AL,0FF ; 判断是否为push [address]的方式
00382CD5 75 04 JNZ SHORT 00382CDB
00382CD7 66:A5 MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]
00382CD9 EB 14 JMP SHORT 00382CEF
00382CDB 3C C2 CMP AL,0C2 ; 判断是否为RET val的方式
00382CDD 75 0A JNZ SHORT 00382CE9
00382CDF A4 MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
00382CE0 66:A5 MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]
00382CE2 61 POPAD
00382CE3 33C0 XOR EAX,EAX
00382CE5 40 INC EAX
00382CE6 C2 0C00 RETN 0C
00382CE9 61 POPAD
00382CEA 33C0 XOR EAX,EAX
00382CEC C2 0C00 RETN 0C
当然如果你的目标不是新版本的不会检测的。
去除这个anti-Debug后,follow UnmapViewOfFile,在RET 4处下断。运行中断后,取消断点,返回壳代码:
0037DAB9 FFB5 64174100 PUSH DWORD PTR SS:[EBP+411764] ; 返回到这里
0037DABF 7C 05 JL SHORT 0037DAC6
0037DAC1 EB 05 JMP SHORT 0037DAC8
0037DAC3 0FCD BSWAP EBP
0037DAC5 2074F9 8D AND BYTE PTR DS:[ECX+EDI*8-73],DH
0037DAC9 859D C34000EB TEST DWORD PTR SS:[EBP+EB0040C3],EBX
0037DACF 020F ADD CL,BYTE PTR DS:[EDI]
0037DAD1 0F50E8 MOVMSKPS EBP,XMM0
0037DAD4 04 00 ADD AL,0
0037DAD6 0000 ADD BYTE PTR DS:[EAX],AL
0037DAD8 CD 20 INT 20
0037DADA - E9 6883C404 JMP 04FC5E47
0037DADF 8B85 A2184100 MOV EAX,DWORD PTR SS:[EBP+4118A2] ; CloseHandle
0037DAE5 EB 04 JMP SHORT 0037DAEB
没跟多久看会看到,CRC的比较了。
0037DC7E 3BF7 CMP ESI,EDI ; 到这里进行CRC检测
0037DC80 0F85 F65E0000 JNZ 00383B7C ; 不等则over
其中ESI是计算后的结果,EDI就是正确的CRC值,这里就有三种去除方式了,第一种在把crc值保存在文件中,第二种mov esi,edi,的方式,第三种直接改成nop。
用LordPE看一下正确的CRC值正是保存在PE header-4处。
004000F0 00 00 00 00 00 00 00 00 00 00 00 00 61 95 C1 32 ............a暳2
00400100 50 45 00 00 4C 01 03 00 19 5E 42 2A 00 00 00 00 PE..L . ^B*....
原程序没修改时的正确CRC值为32c19561,这个值是换算后的。换算方法是:
KEY==CRC xor Key(048F7032)
key==KEY ROR 3
如果你现在直接在od里是看不到程序解码的,因为我并没有处理DRx部分。这篇文章是讲代码注入的,所以我不讲那些东西。
现在我们的CRC值的信息已经找到,也知道是哪里用UnmapViewOfFile这个函数,现在只需在壳入口处写上自己的代码,然后在上面比较CRC值是否正确的地方,
把正确的检测值,保存到pe header-4处。现在只差写代码,我在这里开始写:
00491DCC 5D POP EBP ; 我从这里开始修改,我通过相当地址来计算实际地址的,EBP== 00491DB8
过了这里后,记下EBP的值,然后和上面获取的地址运算后得到实际地址
00491DCD E8 0E000000 CALL 00491DE0 ; 先取Kernel32.dll的句柄
整理了一下代码:
start:
call @F
@@:
pop ebp ;获取EBP的值
call @F
db 'Kernel32.dll',0,0
@@:
CALL DWORD PTR SS:[EBP+0FFFEC2C9h] ;获取Kernel32.dll的句柄
MOV DWORD PTR SS:[EBP+500h],EAX ;保存句柄
call @F
db 'VirtualAlloc',0
@@:
PUSH DWORD PTR SS:[EBP+500h] ; push hModule
CALL DWORD PTR SS:[EBP+0FFFEC2C1h] ; GetProcAddress
MOV DWORD PTR SS:[EBP+504h],EAX ;保存VirtualAlloc的地址
call @F
db 'VirtualQuery',0
@@:
PUSH DWORD PTR SS:[EBP+500h] ; push hModule
CALL DWORD PTR SS:[EBP+0FFFEC2C1h] ; GetProcAddress
MOV DWORD PTR SS:[EBP+508h],EAX ;保存VirtualQuery的地址
CALL @F
DB 'VirtualProtect',0,0 ;因为我自己是用od直接写的代码,所以我现在整理把那些多余的0都写进来
@@:
PUSH DWORD PTR SS:[EBP+500h] ; push hModule
CALL DWORD PTR SS:[EBP+0FFFEC2C1h] ; GetProcAddress
MOV DWORD PTR SS:[EBP+50Ch],EAX ;Save address
CALL @F
db 'VirtualFree',0,0
@@:
PUSH DWORD PTR SS:[EBP+500h] ; push hModule
CALL DWORD PTR SS:[EBP+0FFFEC2C1h] ; GetProcAddress
MOV DWORD PTR SS:[EBP+510h],EAX ;SaveAddress
CALL @F
db 'User32.dll',0,0
@@:
CALL DWORD PTR SS:[EBP+0FFFEC2C5h] ;Load Library user32.dll
CALL @F
db 'MessageBoxA',0
@@:
push eax ;push hModule
db 5 dup (90h)
CALL DWORD PTR SS:[EBP+0FFFEC2C1h] ; GetProcAddress
MOV DWORD PTR SS:[EBP+514h],EAX ;save address
call @F
db 'UnmapViewOfFile',0,0
@@:
PUSH DWORD PTR SS:[EBP+500h] ; push hModule
CALL DWORD PTR SS:[EBP+0FFFEC2C1h] ; GetProcAddress
MOV DWORD PTR SS:[EBP+518],EAX ;save address
PUSH 4h ; /Protect = PAGE_READWRITE
PUSH 1000h ; |AllocationType = MEM_COMMIT
PUSH 28h ; |Size = 28 (40.)
PUSH 0 ; |Address = NULL
CALL DWORD PTR SS:[EBP+504h] ; /VirtualAlloc
MOV DWORD PTR SS:[EBP+52Ch],EAX ; 保存hMeM
MOV ESI,EAX ; 获取权限
PUSH 28h ; /BufSize = 28 (40.)
PUSH ESI ; |Buffer = 003A0000
PUSH EDI ; |Address = kernel32.UnmapViewOfFile
CALL DWORD PTR SS:[EBP+508h] ; /VirtualQuery
LEA EAX,DWORD PTR DS:[ESI+14h] ; 准备修改权限
PUSH EAX ; /pOldProtect = 003A0014
PUSH 40h ; |NewProtect = PAGE_EXECUTE_READWRITE
LEA EAX,DWORD PTR DS:[ESI+0Ch] ; |
PUSH DWORD PTR DS:[EAX] ; |Size = 78000 (491520.)
PUSH DWORD PTR DS:[ESI] ; |Address = kernel32.77E16000
CALL DWORD PTR SS:[EBP+50Ch] ; /VirtualProtect
PUSH EBX ; 修改API入口代码
MOV EAX,DWORD PTR SS:[EBP+518h]
INC EAX
LEA EBX,DWORD PTR SS:[EBP+196h]
SUB EBX,EAX
SUB EBX,5h
MOV BYTE PTR DS:[EAX],0E8h
MOV DWORD PTR DS:[EAX+1],EBX
POP EBX ; 7FFDF000
POPAD
;JMP 0047E0D6 ; 跳去执行原壳的流程
end start
修改API入口入+1的代码为:
77E1667C > 53 PUSH EBX ;API入口
77E1667D E8 CCB86788 CALL cjb.00491F4E
然后在491F4E处写上相关的代码就可以了:
00491F4E 60 PUSHAD
00491F4F E8 00000000 CALL 00491F54
00491F54 5D POP EBP
00491F55 8B85 7C030000 MOV EAX,DWORD PTR SS:[EBP+37C]
00491F5B C740 01 8B5C240>MOV DWORD PTR DS:[EAX+1],8245C8B ; 还原API的代码
00491F62 C640 05 56 MOV BYTE PTR DS:[EAX+5],56
00491F66 8D40 01 LEA EAX,DWORD PTR DS:[EAX+1]
00491F69 894424 20 MOV DWORD PTR SS:[ESP+20],EAX
00491F6D 9C PUSHFD
00491F6E 6A 40 PUSH 40 ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
00491F70 8D85 AC000000 LEA EAX,DWORD PTR SS:[EBP+AC] ; |
00491F76 50 PUSH EAX ; |Title = "Demo"
00491F77 8D85 B2000000 LEA EAX,DWORD PTR SS:[EBP+B2] ; |
00491F7D 50 PUSH EAX ; |Text = "Hying's Armor v0.7x Code Injection Demo
00491F7E 6A 00 PUSH 0 ; |hOwner = NULL
00491F80 FF95 78030000 CALL DWORD PTR SS:[EBP+378] ; /MessageBoxA
00491F86 9D POPFD
00491F87 61 POPAD
00491F88 C3 RETN
代码已经写好了,保存一下,然后通过上面找CRC 的方式找出实际的正确值。写在PE HEADER-4处:
004000F0 00 00 00 00 00 00 00 00 00 00 00 00 07 61 90 D6 ............a愔
00400100 50 45 00 00 4C 01 03 00 19 5E 42 2A 5B 4C 6F 72 PE..L . ^B*[Lor
全部修改完,保存为文件,试试就可以成功了。再说一下,因为是商业性的东西,所以我不以具体怎么破解之类的来写。
Greetz:
Fly.Jingulong,yock,tDasm.David.hexer,hmimys,ahao.UFO(brother).alan(sister).all of my friends and you!
By loveboom[DFCG][FCG][US]
http://blog.csdn.net/bmd2chen
Email:loveboom#163.com
Date:2005-6-6 17:59