Safengine Shielden 2.3.8.0 脱壳 之 为了能下断
本帖最后由 xiaohang99 于 2017-4-12 22:36 编辑最近又在论坛上看到不少人求Safengine Shielden (以下称SE)的脱壳方法,其实之前论坛里的几位大牛都已经放出过不少牛文,虽然这些牛文是对应老版本的SE的,文中附带的代码可能已经失效,但是里面的方法确实在新版本中依然有效的,可能大神们对自己思路的阐述过于跳跃了,所以难免让新人们有些跟不上。在这里,我把我对大牛们的思路理解放上来,以供参考吧。SE 2.3.8.0目前已知的最大反调试手段就是anti断点,主要是anti了硬件断点和int3软断点,加壳后无法对被加壳的程序下断,客观上提高了脱壳的难度,所以第一步是要想办法把anti断点去掉。
我的思路
int3软断点的anti必然是基于内存效验的方法(int3软断点需要修改内存代码),对壳代码进行hash效验保护,如若发现内存有被修改,则结束进程。如果要patch掉内存hash效验,必须知道hash代码的位置,由于SE的垃圾混淆代码很多,更本不可能通过搜索的方式获取,最好的办法就是用硬件断点下一个读取断,所以问题就是要先修复硬件断点。
对于硬件断点,SE采用的是先通过SetThreadContext设置四个DRx寄存器为1byte读取特定地址的断点,然后通过在虚拟机中构造读取特定地址以产生异常,再用SEH handler处理异常,并检测DRx寄存器的值。如果该产生异常的时候没有产生异常,又或者DRx的值不为先前SetThreadContext设定的值,那么就退出进程。
对于内存断点反anti,暂时没有思路,还需要继续研究。
处理过程
先获取SE对DRx设置的值。
1、先在KiUserExceptionDispatcher下断
2、断下两次后记录
= 0012F7FC 00E8A7CAunpackme.00E8A7CA//异常产生的位置,就是后面的vm:ds位置
3、CPU窗口中显示调试寄存器
DR0 =00E57016
DR1 =00E57000
DR2 =00E57004
DR3 = 00E57008
DR7 = 33330555
PatchShadow_CreateThread
由于SE会产生数个线程来进行反调试检测,内存完整性检测等,为了减少干扰项,把SE创建的检测线程先patch掉,不让他启动,可能会对后面的程序运行有副作用,但是便于我一开始的脱壳调试。
PS: SE会把NTDLL.dll 和krenel32.dll中的一些代码Shadow到一段自己分配的空间中,所以需要利用内存搜索的方法,搜索特定代码找到这些被Shadow的API。
[*]先在KiUserExceptionDispatcher下断
[*]第一次断下后,搜索特征代码
#8BFF558BECFF751CFF7518FF7514FF7510FF750CFF75086AFFE8????????5DC21800#
搜索到的代码:
00D1FB55 8BFF mov edi, edi ;改成 ret 18
00D1FB57 55 pushebp
00D1FB58 8BEC mov ebp, esp
00D1FB5A FF75 1C pushdword ptr
00D1FB5D FF75 18 pushdword ptr
00D1FB60 FF75 14 pushdword ptr
00D1FB63 FF75 10 pushdword ptr
00D1FB66 FF75 0C pushdword ptr
00D1FB69 FF75 08 pushdword ptr
00D1FB6C 6A FF push-1
00D1FB6E E8 D9FDFFFFcall00D1F94C
00D1FB73 5D pop ebp
00D1FB74 C2 1800 ret 18
[*]修改函数开头位置为ret1800D1FB55 8BFF mov edi, edi ;改成 ret 18
Patch Shadow_GetThreadContext
SE会调用Shadow_GetThreadContext来检测是否存在调试软件下的硬件断点,如果有就会退出进程。
[*]在刚才搜索到Shadow_CreateThread的内存段内搜索特征代码:
#8BFF558BECFF750CFF7508FF15????????85C00F8C??????0033C0405DC208009090909090#
搜索到的代码:
01888ABF 8BFF mov edi, edi
01888AC1 55 push ebp
01888AC2 8BEC mov ebp, esp
01888AC4 FF75 0C push dword ptr
01888AC7 FF75 08 push dword ptr
01888ACA FF15 9E088501 call dword ptr //获取这里地址
01888AD0 85C0 test eax, eax
01888AD2 0F8C 89B20000 jl 01893D61
01888AD8 33C0 xor eax, eax
01888ADA 40 inc eax
01888ADB 5D pop ebp
01888ADC C2 0800 retn 8
[*]如果直接修改以上代码会被SE检测到,我的方法是inline hook 函数调用KiFastSystemCall之前的地址(壳另外加载了一块内存)
01888ACA FF15 9E088501 call dword ptr //获取这里地址
//获取到的地址标准的ntdll.ZwGetContextThread调用
0169C597 B8 55000000 mov eax, 55 //服务号
0169C59C BA 00005901 mov edx, 00390000 //这里改成我自己分配的空间
0169C5A1 FFE2 jmp edx//跳到我自己分配的空间执行
0169C5A3 C2 0800 retn 8
//我构造的代码
00390000 B8 55000000 mov eax, 55
00390005 BA 0003FE7F mov edx, 7FFE0300
0039000A FF12 call dword ptr
0039000C 50 push eax
0039000D 8B4424 0C mov eax, dword ptr
00390011 8038 10 cmp byte ptr , 10
00390014 75 16 jnz short 0039002C
00390016 33D2 xor edx, edx
00390018 8950 04 mov dword ptr , edx
0039001B 8950 08 mov dword ptr , edx
0039001E 8950 0C mov dword ptr , edx
00390021 8950 10 mov dword ptr , edx
00390024 52 push edx
00390025 6A 06 push 6
00390027 E8 299C477C call kernel32.TlsSetValue
0039002C 58 pop eax
0039002D C2 0800 retn 8
壳总是会判断这 TlsValue 是否等于 Dr0+Dr1+Dr2+Dr3 之 Total 值
我们在壳欲取得 Drx 的值时,将之清为 0,并设 TlsValue 为 0
至于 SetTlsValue 的 Index 应为多少才对, 很多方法可以得知.
例如断 Shadow 的 SetTlsValue ,XP SP2 用的 Index 是 4 , XP SP3 则是 6
Hook ExecuteHandler2
这里是重头戏,反anti 硬件断点必须从这里开始,由于壳会检测异常信息中的DRx寄存器,所以我们必须在SE 的SEH handler被调用前,给_CONTEXT结构中的DRx赋予之前获得的特定值。
_CONTEXT结构如下:
typedef struct _CONTEXT
{
DWORD ContextFlags;
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
FLOATING_SAVE_AREA FloatSave;
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
DWORD Ebp;
DWORD Eip;
DWORD SegCs;
DWORD EFlags;
DWORD Esp;
DWORD SegSs;
}CONTEXT;
typedef struct _FLOATING_SAVE_AREA
{
ULONG ControlWord;
ULONG StatusWord;
ULONG TagWord;
ULONG ErrorOffset;
ULONG ErrorSelector;
ULONG DataOffset;
ULONG DataSelector;
UCHAR RegisterArea;
ULONG Cr0NpxState;
} FLOATING_SAVE_AREA, *PFLOATING_SAVE_AREA;
[*]在NTDLL.dll的CODE段中搜索特征码
#558BECFF750C5264FF350000000064892500000000FF7514FF7510FF750CFF75#
搜索到的代码:
.text:7C923282 push ebp
.text:7C923283 mov ebp, esp
.text:7C923285 push
.text:7C923288 push edx ; 如果定义的异常处理handle中出错,这个handle是最终处理者
.text:7C923289 push large dword ptr fs:0
.text:7C923290 mov large fs:0, esp
.text:7C923297 push
.text:7C92329A push
.text:7C92329D push
.text:7C9232A0 push
.text:7C9232A3 mov ecx,
.text:7C9232A6 call ecx ; _except_handler(
.text:7C9232A6 ; struct _EXCEPTION_RECORD *ExceptionRecord,
.text:7C9232A6 ; void * EstablisherFrame,
.text:7C9232A6 ; struct _CONTEXT *ContextRecord,
.text:7C9232A6 ; void * DispatcherContext )
.text:7C9232A8 mov esp, large fs:0
.text:7C9232AF pop large dword ptr fs:0
.text:7C9232B6 mov esp, ebp
.text:7C9232B8 pop ebp
.text:7C9232B9 retn 14h
[*]构造inline hook 代码
// hook executehandler2
7C99AFC0 E8 00000000 call 7C99AFC5
7C99AFC5 5B pop ebx
7C99AFC6 83EB 05 sub ebx, 5
7C99AFC9 81EB 00060000 sub ebx, 600 //减出myexceptionhandle的地址
7C99AFCF 3B8B FC0F0000 cmp ecx, dword ptr //比较是否是SE的SEHhandler
7C99AFD5 75 02 jnz short 7C99AFD9
7C99AFD7 8BCB mov ecx, ebx //是的话就HOOK,否则放过
7C99AFD9 33DB xor ebx, ebx
7C99AFDB FFD1 call ecx
7C99AFDD 64:8B25 0000000>mov esp, dword ptr fs:
7C99AFE4 64:8F05 0000000>pop dword ptr fs:
7C99AFEB 8BE5 mov esp, ebp
7C99AFED 5D pop ebp
7C99AFEE C2 1400 retn 14
E8 00 00 00 00 5B 83 EB 05 81 EB 00 0600 00 3B 8B FC 0F 00 00 75 02 8B CB 33 DB FF D1 64 8B 2500 00 00 00 64 8F 05 00 00 00 00 8B E55D C2 14
// myexceptionhandle
00401000 60 pushad
00401001 E8 00000000 call 00401006
00401006 5B pop ebx
00401007 83EB 06 sub ebx, 6
0040100A 81C3 00080000 add ebx, 800
00401010 8BFB mov edi, ebx
00401012 8B7424 24 mov esi, dword ptr //pExceptionRecord
00401016 B9 06000000 mov ecx, 6
0040101B F3:A5 rep movs dword ptr es:, dword ptr
0040101D 8BFB mov edi, ebx
0040101F 83C7 30 add edi, 30
00401022 8B7424 2C mov esi, dword ptr //pContextRecord
00401026 B9 18000000 mov ecx, 18
0040102B F3:A5 rep movs dword ptr es:, dword ptr
0040102D 8B83 90000000 mov eax, dword ptr
00401033 8D8B A0000000 lea ecx, dword ptr
00401039 C1E0 02 shl eax, 2
0040103C 03C8 add ecx, eax
0040103E 8B7424 24 mov esi, dword ptr
00401042 8B46 0C mov eax, dword ptr
00401045 8901 mov dword ptr , eax
00401047 FF83 90000000 inc dword ptr
0040104D 8D7B 30 lea edi, dword ptr
00401050 8B83 E8070000 mov eax, dword ptr //这里考虑可以在hook setthreadcontext中赋值
015A0056 8947 04 mov dword ptr , eax
015A0059 8B83 EC070000 mov eax, dword ptr
015A005F 8947 08 mov dword ptr , eax
015A0062 8B83 F0070000 mov eax, dword ptr
015A0068 8947 0C mov dword ptr , eax
015A006B 8B83 F4070000 mov eax, dword ptr
015A0071 8947 10 mov dword ptr , eax
015A0074 8B83 F8070000 mov eax, dword ptr
015A007A 8947 18 mov dword ptr , eax
015A007D 8B4424 30 mov eax, dword ptr
015A0081 8B4C24 28 mov ecx, dword ptr
015A0085 8D7B 30 lea edi, dword ptr
015A0088 50 push eax
015A0089 57 push edi
015A008A 51 push ecx
015A008B 53 push ebx
015A008C 8B8B FC070000 mov ecx, dword ptr
015A0092 33C0 xor eax, eax
015A0094 33DB xor ebx, ebx
015A0096 33FF xor edi, edi
015A0098 33F6 xor esi, esi
015A009A FFD1 call ecx
015A009C 61 popad
015A009D C3 retn
60 E8 00 00 00 00 5B 83 EB 06 81 C3 0008 00 00 8B FB 8B 74 24 24 B9 06 00 00 00 F3 A5 8B FB 83 C7 30 8B 74 24 2C B9 18 00 00 00 F3 A58B 83 90 00 00 00 8D 8B A0 00 00 00 C1 E0 02 03 C8 8B 7424 24 8B 46 0C 89 01 FF 83 90 00 00 008D 7B 30 8B 83 E8 07 00 00 89 47 04 8B 83 EC 07 00 00 8947 08 8B 83 F0 07 00 00 89 47 0C 8B 83F4 07 00 00 89 47 10 8B 83 F8 07 00 00 89 47 18 8B 44 2430 8B 4C 24 28 8D 7B 30 50 57 51 53 8B8B FC 07 00 00 33 C0 33 DB 33 FF 33 F6 FF D1 61 C3 00 00
[*]Inline Hook ExecuteHandler2Hook在这里.text:7C9232A6 call ecx 改成Jmp to // hook executehandler2
Patch vm:ds
这个地址是前面已经找到的,由于暂时还没找到HASH检测的代码位置,所以不能直接改代码,我的方法是,用脚本在这个位置上设置一个硬件执行断点在,当脚本断下后,脚本自动修改代码跳转到我们自己分配的空间内执行一段代码,在代码的开头立即修复之前被脚本改掉的代码,然后产生一个single step异常给系统,再执行之前由于改成跳转没有执行到的程序源码后跳转到原程序下一行继续。
这个方法是确定是要被占用一个硬件断点。
以下是我构造的代码:
//跳转来自0E8A7C8
00390100 60 pushad
00390101 BE 00023900 mov esi, 390200
00390106 BF C8A7E800 mov edi, 0E8A7C8
0039010B B9 05000000 mov ecx, 5
00390110 F3:A4 rep movs byte ptr es:, byte ptr //还原ds被我修改用来跳转的代码
00390112 61 popad
00390113 9C pushfd \
00390114 66:810C24 0001or word ptr , 100 3条指令用来产生一个80000004 的 EXCEPTION_SINGLE_STEP
0039011A 9D popfd / 就是通过符号位中的单步标记来产生一个异常
0039011B 8B10 mov edx, dword ptr --读取数值,由于硬件执行断点的那行没有执行
0039011D- E9 A8A6AF00 jmp 乔巴.00E8A7CA --返回执行下一行代码
Patch Shadow_SetThreadContext
为了防止硬件断点被覆盖,这个Shadow函数必须Patch掉,当然,如果前面的部分没有Patch 而直接来改这个函数,SE就不会获得需要检测的异常,造成程序的退出。
1、搜索代码
通过之前patch Shadow_GetThreadContext获得的调用KiFastSystemCall前代码区域,查找调用服务号为0D5 的调用,
[*]修改代码只需要把跳入KiFastSystemCall的位置直接nop掉就行
FFE2 jmp edx// 这里是跳入KiFastSystemCall,直接nop
Patch掉HASH 计算
通过上面几步,现在我可以下硬件断点了,能断下来的第一件事,当然是找HASH效验代码。
[*]在前面vm:ds的位置后面几行下个硬件读取断点断下之后:
.sedata:00E6323F push esi
.sedata:00E63240
.sedata:00E63240 loc_E63240: ; CODE XREF: .sedata:00E6324Ej
.sedata:00E63240 movzx esi, byte ptr
.sedata:00E63244 rol eax, 7 //断在这里
.sedata:00E63247 xor eax, esi
.sedata:00E63249 inc ecx
.sedata:00E6324A cmp ecx,
.sedata:00E6324E jb short loc_E63240
.sedata:00E63250 pop esi
.sedata:00E63251 jmp short locret_E6325C
[*]Run Trace 后获得整个循环体,直接找到跳出循环的位置来到这里:
00E63247 Main xor eax, esi ; FL=PZ, EAX=F4E889ED, ECX=00093F9F, ESI=000000D0
00E63250 Main pop esi ; ESP=0012FEC8, ESI=F4E889ED
00E63251 Main jmp short 00E6325C
00E6325C Main retn 4 ; ESP=0012FED0 //hash 值保存在eax
[*]重新加载几次之后,发现HASH计算的参数和结果是不变的,我只能说,呵呵
检测开始位置:0E7C91D
检测大小: 0093F9F
返回EAX: F4E889ED
[*]偷懒直接patch 掉
//------ Fix HASH------
00E63240 B8 ED89E8F4 mov eax, F4E889ED
00E63245 90 nop
00E63246 90 nop
00E63247 90 nop
00E63248 90 nop
00E63249 90 nop
00E6324A 90 nop
00E6324B 90 nop
00E6324C 90 nop
00E6324D 90 nop
00E6324E 90 nop
00E6324F 90 nop
Sound 发表于 2017-4-14 15:39
不错不错 自从手动解决比较麻烦后 现在一般都是跑脚本 。
大佬,请指教哪里有破se反断点脚本?
最近一段时间,一直在研究这个帖子,看了不下二十遍,无奈基础还是欠缺,还是没法完全读懂。
请求指点迷津!!! xiaohang99 发表于 2017-4-12 22:34
样本我也想上传,可惜太大,压缩好有50mb,没办法,个人等级太低
可以使用网盘,如 微云,百度云 66666666666666谢谢分享, 学习了 太神啦 谢谢楼主 可以提供样本么?提高针对性, 好文!!{:1_921:} 好文!!好文!!好文!! 好文~这必须支持下~~mark~ 这都是牛人啊! 这都是牛人啊! 都是牛人啊!我看这些太多看不懂的。