大家好我是 火哥,交流QQ群:1026716399(新群),希望多支持哈
我用IDA 来看代码
在syscall进入内核后 第一句话就会调用 swapgs指令 来替换GS段的base(这是保护模式段的知识,不懂请搜索看雪段的相关文章)
此时的GS段的内容指向了一个KPCR结构,结构比较长,我就不在文章中贴出来了。可以用windbg查看 dt _KPCR
然后把R3的RSP寄存器 也就是堆栈的值 保存起来,并且替换成内核的堆栈
大家可以看到 下面连续好几个push,这是在保存现场,此现场是用一个 KTrap_frame的结构来维护的
kd> dt _KTRAP_FRAME
nt!_KTRAP_FRAME
+0x000 P1Home : Uint8B
+0x008 P2Home : Uint8B
+0x010 P3Home : Uint8B
+0x018 P4Home : Uint8B
+0x020 P5 : Uint8B
+0x028 PreviousMode : Char
+0x029 PreviousIrql : UChar
+0x02a FaultIndicator : UChar
+0x02b ExceptionActive : UChar
+0x02c MxCsr : Uint4B
+0x030 Rax : Uint8B
+0x038 Rcx : Uint8B
+0x040 Rdx : Uint8B
+0x048 R8 : Uint8B
+0x050 R9 : Uint8B
+0x058 R10 : Uint8B
+0x060 R11 : Uint8B
+0x068 GsBase : Uint8B
+0x068 GsSwap : Uint8B
+0x070 Xmm0 : _M128A
+0x080 Xmm1 : _M128A
+0x090 Xmm2 : _M128A
+0x0a0 Xmm3 : _M128A
+0x0b0 Xmm4 : _M128A
+0x0c0 Xmm5 : _M128A
+0x0d0 FaultAddress : Uint8B
+0x0d0 ContextRecord : Uint8B
+0x0d0 TimeStampCKCL : Uint8B
+0x0d8 Dr0 : Uint8B
+0x0e0 Dr1 : Uint8B
+0x0e8 Dr2 : Uint8B
+0x0f0 Dr3 : Uint8B
+0x0f8 Dr6 : Uint8B
+0x100 Dr7 : Uint8B
+0x108 DebugControl : Uint8B
+0x110 LastBranchToRip : Uint8B
+0x118 LastBranchFromRip : Uint8B
+0x120 LastExceptionToRip : Uint8B
+0x128 LastExceptionFromRip : Uint8B
+0x108 LastBranchControl : Uint8B
+0x110 LastBranchMSR : Uint4B
+0x130 SegDs : Uint2B
+0x132 SegEs : Uint2B
+0x134 SegFs : Uint2B
+0x136 SegGs : Uint2B
+0x138 TrapFrame : Uint8B
+0x140 Rbx : Uint8B
+0x148 Rdi : Uint8B
+0x150 Rsi : Uint8B
+0x158 Rbp : Uint8B
+0x160 ErrorCode : Uint8B
+0x160 ExceptionFrame : Uint8B
+0x160 TimeStampKlog : Uint8B
+0x168 Rip : Uint8B
+0x170 SegCs : Uint2B
+0x172 Fill0 : UChar
+0x173 Logging : UChar
+0x174 Fill1 : [2] Uint2B
+0x178 EFlags : Uint4B
+0x17c Fill2 : Uint4B
+0x180 Rsp : Uint8B
+0x188 SegSs : Uint2B
+0x18a Fill3 : Uint2B
+0x18c CodePatchCycle : Int4B
大家可以看到 第一个值 是push 0x2B 那么也就是 在0x188的位置 压入,当前Rsp指向了0x188 这是线程堆栈被分配的时候 就指向在申请堆栈空间的-4位置
然后连续几个PUSH 也就是在压入到这个结构中,push rcx是值得注意的。调用syscall会把 调用指令的这个函数的下一行地址 保存在RCX 所以 rcx等于返回地址。
然后Sub rsp,158 瞬间 就到了这个结构的起始地址,这个结构就是这样被创建出来了
值得注意的是 rbp保存是这个结构的腰,到时候会用rbp来计算这个结构的
这一段内容主要是判断 当前是否处于调试,是都的话 要保存当前调试的寄存器以及状态 我们默认不是调试, 用 jz loc_14007f750跳转到下一个逻辑处
在这里 首先会保存 rcx 也就是第一个参数,然后还有函数的编号,也就是 eax(这里没法说清楚,rcx为什么是第一个参数,eax为什么是函数编号,要想弄明白得去分析分析R3 参数 传递,我这里就不做阐述,如果有需要,我下次在发帖……………………)
然后RSP 是什么? 是不是我们的trap_frame结构啊,把他保存到当前线程中,rbx为什么是当前线程?注意我的第一个图,最后一句,GS:188就是当前线程了。
想弄清楚里面所有的东西,需要有积累,可以加我的群,大家一起交流。
下面就是我们的重点了。
首先把函数编号,存到edi 然后做运算,首先逻辑右移7位,然后逻辑与0x20 为什么是0x20? 因为我们的SSDT表 是一个结构数组,这个数组在x64下的每个元素是0x20个字节,所以在这里 是在求 当前 函数 在那个结构中,也就是计算结构的偏移
也就是说现在 edi 存放是结构的偏移,eax逻辑与上0xFFF,为什么是0xFFF? 因为在windows中 函数编号 大于0x1000就代表 是GUI SSDT的寻找,所以0xfff往上的编号 都是额外的作用,不能作用于函数的索引上。计算过后 eax中才是真正的函数编号 也可以认为是索引,我们在来看下一段
首先把SSDT和SSDT_Shadow分别存放到 r10,r11寄存中,然后判断线程是否是GUI线程,如果是GUI线程,那么就要在
SSDT_Shadow中查找,否则在SSDT中查找,
取结构中的函数最大个数 与 当前的函数编号去比较,如果大于(这边还会有一些条件,大家感兴趣的话自己去分析) 这里就会报错了
先获得函数表,在获得函数偏移(我这里为什么说是偏移?继续)
把R11存到RAX,备份一份 等下要用
然后把r11算术右移 4位(什么是算术右移?请百度)
R10我们都分析过了。他就是函数表,用函数表+函数偏移 等于真正的函数,此时的R10就是函数了
从上面我们可以得出算法
ULONG funcOffset = SSDT[结构索引][函数索引];
funcOffset = funcOffset & 0x80000000 ?
funcOffset >> 4 : (funcOffset >> 4) | 0xF0000000;
ULONG64 funcAddress = SSDT[结构索引].函数表 +
funcOffset;
判断一下 是第一个结构还是第二个结构,如果是第一个结构 就跳走了,如果是第二个结构 就代表是GUI结构,那么要做相对应的刷新,统计 等操作
做完之后也会调用到这里来
我们之前说 eax要备份过,也就是函数的原始偏移,那么大家看第一句话, 就是拿原始偏移的 &0xf 取低4位
如果获取到值为空 就直接调用函数,否则 就往下走
在把低4位 左移3位, 这些是什么意思勒, 其实就是求 当前这个函数的参数,我们都知道 x64下都是 fastcall调用约定,之后4个寄存器 rcx,rdx,r8,r9用来保存参数,那么如果参数大于了4个 都是放到堆栈中,其实这里就是 求 除了保存在4个寄存器中额外的参数,要把这些参数从R3的堆栈 copy到R0的堆栈中,相信我这么一说大家就明白了,那么这下面几句就是 为了 复制参数做的操作,另外还判断了 是R0调用 还是R3调用
这里大家可以自行的分析下上面的代码 就会明白的我说的了
最后这一句就是call R10了。这就是一个调用过程。
我在最后 没有和大家说 是如何返回到R3的。我不想篇幅太长,如果网友们很热心,或者觉得火哥分析的还不错,都支持的话。我在出下一章吧。
以上内容是原创分析,目前分析有我这么详细的文章没有看到。希望大家来朵鲜花
最后在啰嗦下哈。
火哥,交流QQ群:1026716399(新群),希望多支持哈
[课程]FART 脱壳王!加量不加价!FART作者讲授!