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