一、Loader.sys分析
虚拟机系统环境:Windows 10 x64 1909 18363.418
调试环境:KDNET network kernel debugging
老规矩查壳
![image1.png](./upload/attach/202404/910874_DMPV89B4RV6T94E.png)
很好,什么也看不出来;直接拖IDA看看。
![image2.png](./upload/attach/202404/910874_6J6UNVEMUM66Y35.png)
导入表有个ExAllocatePoolWithTag,尝试Hook了不过并没有什么卵用,跑起来只会拷贝一个“2024.wo.shi.chu.ti.ren”的字符串上去......直接看Disassembly。
![image3.png](./upload/attach/202404/910874_CATR8M66XH4PFRJ.png)
一眼VMP,等分析完成后Shift+F12看字符串。
![image4.png](./upload/attach/202404/910874_2WBVTU94S4D9FES.png)
“bad array new length”和“vector too long”明显来自stl库,应该是被std::string和std::vector的某个类成员函数所引用的。
1 2 3 4 5 6 7 | struct std::string
{
char *data;
__int64 unknown;
__int64 length;
__int64 capacity;
};
|
![image5.png](./upload/attach/202404/910874_RNQMARWWA3ZQG9F.png)
跑一下Lumina私服能出一些符号,其中std::string成员函数:
![image6.png](./upload/attach/202404/910874_NXHHUER2AJ7VVCF.png)
挨个xref找访问,std::string::~string和std::string::find_last_of能追到同一个地方。
![image7.png](./upload/attach/202404/910874_QCUYYEYS8XDAX9F.png)
word_140675E20数组逐字与0xACE进行了异或,拷贝出代码来跑一下:
![image8.png](./upload/attach/202404/910874_V235PQKS72XB7VD.png)
得到存放key的文件路径
![image9.png](./upload/attach/202404/910874_3GAMT8Q24MDWVD8.png)
![image10.png](./upload/attach/202404/910874_C9HY4M9YJSG3QCS.png)
联想到key的格式“ACE-4051574845”,可以知道此处正在进行字符串分割。
其次,sub_140AFC7E2通过复制给chatgpt解析能大致得出其为字符串转整数的功能。
![image11.png](./upload/attach/202404/910874_J7TNRPVYXMCDBSB.png)
下断验证:
![image12.png](./upload/attach/202404/910874_9QNCAHGJQ22GED8.png)
还原大致逻辑,sub_140009CBC是对用户名进行计算,得到一个输出值。算法如下:
![image13.png](./upload/attach/202404/910874_KY88R34F5MFZBKH.png)
整体逻辑:
![image14.png](./upload/attach/202404/910874_ETPUHDSXP2X3DEH.png)
用户名匹配时,函数返回1即true,失败返回false。
exp思路:修改用户名输出值与key的比较结果,使函数返回值恒为true即可。
具体方法:注册LoadImage回调,判断加载的映像是否为目标驱动,使判断函数尾部的xor al, al变成mov a1, 1
![image15.png](./upload/attach/202404/910874_AYXN7AN57BJMZWE.png)
二、shellcode分析
成功加载Loader驱动后,System进程的CPU占有直接飙升至99%。
![image16.png](./upload/attach/202404/910874_RXU322G9RP2VN55.png)
说明Shellcode应当是启动了。而Loader想在内核跑shellcode必然要写到可执行内存并获得执行时机。可执行内存的获取方式一般为:
ExAllocatePool/ExAllocatePoolWithTag/MmAllocateIndenpendentPages/MmAllocateContiguousMemory....
执行时机:PsCreateSystemThread/WorkItem/APC/DPC/Timer/...
延续初赛的解题代码,直接用DetoursX挂钩PsCreateSystemThread、ExAllocatePoolWithTag
![image17.png](./upload/attach/202404/910874_Q7Y9MURKKCD7EZ8.png)
BSOD,看调用栈是PsCreateSystemThread后的双重错误。而ExAllocatePoolWithTag直接执行到了挂钩后面的int3上去了。
![image18.png](./upload/attach/202404/910874_4B9GHR8UVR2ESTT.png)
![image19.png](./upload/attach/202404/910874_YHC9B6VGZXK67BV.png)
应该是程序自己构造函数序言部分,执行完成自行构造的序言部分后jmp到后面的代码验证构造call:
![image20.png](./upload/attach/202404/910874_HYN5X8276FHY5P4.png)
直接去挂钩更底层的PsCreateSystemThreadEx和ExAllocateHeapPool一劳永逸,可以成功让目标驱动走我们的钩子。
不过看到跳板push ret有种莫名熟悉的感觉(XXX-BASE),尝试在物理机用CE附加虚拟机进程即vmware-vmx.exe,搜索跳板特征。
1 2 3 4 5 6 | ffff8003`b49f5456 48895c2418 mov qword ptr [rsp+18h],rbx
ffff8003`b49f545b 6825c5017f push 7F01C525h
ffff8003`b49f5460 c744240402f8ffff mov dword ptr [rsp+4],0FFFFF802h
ffff8003`b49f5468 c3 ret
0: kd> db ffff8003`b49f545b
ffff8003`b49f545b 68 25 c5 01 7f c7 44 24-04 02 f8 ff ff c3 31 04
|
68 ?? ?? ?? ?? C7 44 24 04 ?? ?? ?? ?? C3
![image21.png](./upload/attach/202404/910874_AEMNZTSUKHKQAK3.png)
94个结果,估计跳板格式是定死的。
![image22.png](./upload/attach/202404/910874_35VGX5B7QDN8ZW3.png)
![image23.png](./upload/attach/202404/910874_XHBVPA3MNJ3JRNS.png)
编写CE lua脚本dump出所有的跳转函数,测试对跳板写入0xCC,windbg中可以接管到断点。
之后把dump出的函数列表去重后依次在windbg里面看符号地址,这样就能得出shellcode的所有导入函数了;依次下断分析,可以知道那些函数是正在被调用的。写个下断脚本一键下断。
![image25.png](./upload/attach/202404/910874_JHWY3CR7EKQ47VF.png)
最终得出正常运行时调用的系统函数大致如下:
![image26.png](./upload/attach/202404/910874_XTACW3FMDWZTEB6.png)
程序会死循环遍历进程ID,并调用PsLookupProcessByProcessId获取进程对象。
1 2 | for ( auto pid=0x10ull; pid < 0x40000; pid+=4)
PsLookupProcessByProcessId(pid, &Process);
|
![image27.png](./upload/attach/202404/910874_JAAW6Z758XPVZKY.png)
Hook更底层的PspReferenceCidTableEnty可以得出最大PID为0x40000找到shellcode调用地址,按4K对齐的方式找到一个大致的shellcode头。
![图片1.png](./upload/attach/202404/910874_5UR4K5M26MFKCN2.png)
![image28.png](./upload/attach/202404/910874_X828GEBWRQX2Z33.png)
之后dump shellcode到文件,拖到IDA一条龙服务。
![image29.png](./upload/attach/202404/910874_ME8NPSTNVGS9SSZ.png)
![image30.png](./upload/attach/202404/910874_SKB9P2EHFAYRMCV.png)
编写一个简单的脚本去除shellcode中两处影响分析的控制流混淆指令,去掉混淆后只剩一个jmp指令,虽然还有很多混淆,但是基本不影响IDA创建函数分析控制流了。
![image31.png](./upload/attach/202404/910874_2Y9DWV5RG5CQKP3.png)
仔细观察被混淆的指令
![image32.png](./upload/attach/202404/910874_YNC62WN7RA6MHDV.png)
找到规律:
call的传参指令及返回值寄存器操作,非易失寄存器读写,内存读写指令,堆栈操作指令,cmovcc指令,cmp/test+jcc指令,以及某些特定代码块结束的add rdx,[x+y] -> jmp rdx...
混淆的SSE/AVX指令穿插在这些正常指令之间,但是混淆指令的寄存器和算术逻辑运算结果并没有保留或被引用,因此笔者感觉可以使用活跃变量分析去除这些垃圾指令,但奈何比赛时间有限,笔者对以上代码熟练度并不高,故选择手动删除混淆代码(删除之后还有ollvm的控制流平坦化,但是不影响分析)。
去掉混淆之后,代码逻辑清晰可见,参数一传入了保存进程信息的结构体指针。
![image33.png](./upload/attach/202404/910874_DUBEZ96E548ZHN6.png)
1 2 3 4 5 6 | struct process_info_t
{
__int64 pid;
__int64 process;
bool flag;
};
|
分析用同等方式去掉混淆,总耗时5小时,耗能三个馒头。
![5ba87defb19109c8685985ade02bc0c9.png](./upload/attach/202404/910874_XW4AF2SJKPDM6M8.png)
第一个xref:
![image34.png](./upload/attach/202404/910874_Z9A7UD2PFV8Y3YQ.png)
PsGetProcessExitStatus判断进程存在
获取进程名称:
![image35.png](./upload/attach/202404/910874_C8UKJCF85KYZM92.png)
![image36.png](./upload/attach/202404/910874_4AFH5V98WTA6CZY.png)
第二个xref:
![image38.png](./upload/attach/202404/910874_TFPC82HSARUNX3B.png)
与初赛如出一辙的字符串加密方式,在sub_FFFF8003BD0E543C下个断能拿到解密后的字符串 -> “GameSec.exe”
![image39.png](./upload/attach/202404/910874_V4RD7R8ZPAJNU63.png)
然后随便搞个程序改名成GameSec.exe,丢到测试机里运行。再启动Loader跑起shellcode后直接蓝了。
![image40.png](./upload/attach/202404/910874_HSV96Q6ZKSSD72H.png)
调用栈看是MmCopyVirtualMemory里面炸的,说明shellcode是用这个系统函数读取的进程内存。同样用CE附加虚拟机外部写0xCC断点的方式,运行GameSec.exe让虚拟机断在跳板函数上。
![image41.png](./upload/attach/202404/910874_XWSQJGUBKA6QVX3.png)
1 2 3 | NTSTATUS MmCopyVirtualMemory(PEPROCESS FromProcess, PVOID FromAddress, ...);
FromProcess -> Rcx
FromAddress -> Rdx
|
题目要求分析shellcode反复在读取哪个内存地址,找一下Rdx来源即可。观察发现rdx后缀是ACE,直接在shellcode里面搜索0xACE能得到两处指令
![image42.png](./upload/attach/202404/910874_GNPJNWH36EPNUMR.png)
依次对这两处地址下断,第二处地址可以断下来,拿到IDA里分析
![image43.png](./upload/attach/202404/910874_WVP23AMSW8XZXPF.png)
![image44.png](./upload/attach/202404/910874_7337GX9EZ693FKF.png)
sub_FFFF8003BD0E0EA6返回了目标进程的PEB地址,故shellcode反复在读取GameSec.exe进程的PEB+0xACE处的内存,大小为1Byte。
解决方案
search程序搜索shellcode的思路:
1、将自身插入到shellcode执行的上下文环境(线程)中,采集调用堆栈并判断地址是否合法,可以通过Hook Shellcode调用的系统函数或者利用操作系统的某些机制实现,具体方式有EtwHook、HypervisorHook、DPC、APC、NMI、IPI等
2、扫描内存(虚拟、物理内存),通过shellcode执行地址可以判断出其位于NonPagedPool上面,可以通过遍历BigPool找到目标内存的Tag进而判断内存特征是否为shellcode。也可以通过直接对物理内存进行扫描的方法判断内存特征。
笔者根据题目要求选择几种方式实现:
1.通过向全部核心插入异步执行的DPC例程打断执行中的代码并检查调用堆栈
2.让全部核心同步执行DPC例程打断执行中的代码并检查调用堆栈
3.通过注册NMI回调函数并向全部核心发送NMI打断执行中的代码并检查调用堆栈
4.通过向全部核心发送IPI(处理器间中断)的方式打断执行中的代码并检查调用堆栈
5.遍历内核Pool内存寻找shellcode特征以定位shellcode
6.遍历系统进程页表,对比内存定位shellcode
7.遍历系统所有物理内存,对比内存定位shellcode
8.设置DPC定时器,在回调函数中栈回溯查找shellcode
除上述方法以外还可以通过EtwHook、Hypervisor监控、PMI......实现,但是万变不离其宗,大道至简如是说
![](upload/attach/202404/910874_U2YPB5R9YX7XPMM.webp)
实际效果如下:
异步DPC:
![image47.png](./upload/attach/202404/910874_NQPXXVAW2BGQ9DP.png)
同步DPC:
![image49.png](./upload/attach/202404/910874_CCB6ZD4U3TPKANC.png)
IPI(处理器间中断):
![image51.png](./upload/attach/202404/910874_ZBEXNGDXBPYZ2JK.png)
NMI:
![image53.png](./upload/attach/202404/910874_UAMV9XA4CHWJYXY.png)
补:在NMI里不要DbgPrint挂调试器会蓝的,只保留数据就好
池内存扫描:
![image55.png](./upload/attach/202404/910874_BUQ4J4K2Z6PUZHD.png)
物理内存扫描:
![e4f45baa0f4a6a725684f3b0e5f0ca9f.png](./upload/attach/202404/910874_P6K8G26PXKPDCVT.png)
页表扫描:
![image57.png](./upload/attach/202404/910874_HD2W33T629TMPBW.png)
BigPool、页表扫描、物理内存扫描效果:
![image58.png](./upload/attach/202404/910874_ZSMVYAS6XTQ45VR.png)
DPC定时器:
![image59.png](./upload/attach/202404/910874_2EPKRZMRT5Q9MRZ.png)
![image59.png](./upload/attach/202404/910874_JAMHGGYC8WEPHPF.png)
![image60.png](./upload/attach/202404/910874_GZERM89Q8ZMNYJN.png)
整体运行效果图:
![image61.png](./upload/attach/202404/910874_8JZN7VABBV8RCGC.png)
项目地址
后记
不得不说这次题目是真的哇塞,shellcode都是动态混淆的,线程也锁死了IRQL不让插APC。
最后感谢各位老哥平日里在技术上的指点!
Salute!
![](upload/attach/202404/910874_FDYXK7ZHJNXKDY6.webp)
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法
最后于 2024-4-22 05:45
被R0g编辑
,原因: 纠错