瞄一眼

 找回密码
 立即注册
查看: 275|回复: 7

[r3过检测] 数据代码隔离绕过crc

[复制链接]

320

主题

340

帖子

47

积分

管理员

Rank: 12Rank: 12Rank: 12

积分
47
发表于 2019-11-9 14:00:04 | 显示全部楼层 |阅读模式

翻译来自原文

随着Intel Nehalem引入sTLB,TLB隔离(曾经是一种可靠的技术)已成为历史。那些不得不HOOK用户模式的人开始研究虚拟机管理程序。特别是EPT违规。但是,实现虚拟机监控程序意味着实现庞大的,依赖于平台的代码,这并不是您尝试发布软件时的最佳方法-尤其是在您试图隐身的情况下,因为虚拟化很容易检测并且很难隐藏。


这就是分段发挥作用的地方。尽管我们长期以来一直使用CS == SS == DS模型,但自1978年以来,分段一直处于无效状态,但是可以起作用。CS的值指示如何执行指令,DS的值指示如何读取内存。实际上,我们希望从TLB拆分中获得什么。


尽管我们必须禁用Patchguard才能使用此技术(相对简单),但该技术将使我们做很多有趣的事情,例如欺骗返回指针,挂钩函数,而无需更改.text和大量的call指令。


 我们将必须钩住一堆内核函数并创建其他的段。但是在此之前,让我们先讨论一下其基本工作方式:


这项技术基本上是通过创建镜像模块(而不是原始模块)来工作的。我们分配一个与原始模块大小相等的内存,然后按1:1复制其内容。尽管代码数据位于不同的内存地址中,但由于代码引用的IP不会有所不同,因此我们无需进行任何重定位。然后,我们将克隆原始CS的GDT条目(无论是0x23还是0x33),并将GDT的Base设置为新分配的内存减去原始模块基地址(进程内各种dll模块 每个模块都有自己的数据段 代码段 我们镜像了整个dll  镜像dll的数据段减去原始dll的数据段的值正好是镜像dll和原始dll的基地址偏移),这很简单:



  1. typedef struct _KGDTENTRY
  2. {
  3. uint8_t Limit0;
  4. uint8_t Limit1;
  5. uint8_t Base0;
  6. uint8_t Base1;
  7. uint8_t Base2;
  8. uint8_t Access;
  9. uint8_t Limit2 : 4;
  10. uint8_t Unk : 1;
  11. uint8_t L : 1;
  12. uint8_t Db : 1;
  13. uint8_t Granularity : 1;
  14. uint8_t Base3;
  15. } KGDTENTRY;
  16. typedef struct _SET_ENTRY_DPC_ARGS
  17. {
  18. uint16_t EntryId;
  19. uint64_t Entry;
  20. NTSTATUS Status;
  21. uint64_t Error_Trgt;
  22. uint64_t Error_Base;
  23. uint64_t Error_Lmt;
  24. } SET_ENTRY_DPC_ARGS;
  25. static void Gdt_SetEntryDpc( KDPC *Dpc, SET_ENTRY_DPC_ARGS* Args, PVOID SystemArgument1, PVOID SystemArgument2 )
  26. {
  27. uint64_t Backup = DisableWP();
  28. GDTR Gdtr;
  29. _sgdt( &Gdtr );
  30. uint64_t* Limit = Gdtr.Base + Gdtr.Limit + 1 - 8;
  31. uint64_t* Target = Gdtr.Base + Args->EntryId * 8;
  32. if ( Target > Limit )
  33. {
  34. Args->Error_Trgt = Target;
  35. Args->Error_Base = Gdtr.Base;
  36. Args->Error_Lmt = Gdtr.Limit;
  37. Args->Status = GDT_SEG_NOT_PRES;
  38. Log( "Target (%x) > Limit (%x) [%d]\n", Target, Limit, KeGetCurrentProcessorNumber() );
  39. }
  40. else
  41. {
  42. *Target = Args->Entry;
  43. Log( "Target (%x) <= Limit (%x) [%d]\n", Target, Limit, KeGetCurrentProcessorNumber() );
  44. }
  45. KeSignalCallDpcSynchronize( SystemArgument2 );
  46. ResetWP( Backup );
  47. KeSignalCallDpcDone( SystemArgument1 );
  48. }
  49. static NTSTATUS Gdt_SetEntry( uint16_t EntryId, uint64_t Entry )
  50. {
  51. static SET_ENTRY_DPC_ARGS Args;
  52. Args.EntryId = EntryId;
  53. Args.Entry = Entry;
  54. Args.Status = STATUS_SUCCESS;
  55. KeGenericCallDpc( Gdt_SetEntryDpc, &Args );
  56. return Args.Status;
  57. }
  58. static NTSTATUS Gdt_SetupSeg( uint32_t Seg, uint8_t Wow64, uint32_t Base, uint32_t Limit )
  59. {
  60. BOOLEAN Granularity = Limit > 0xFFFFF;
  61. if ( Granularity )
  62. Limit /= 0x1000; // 4 kb
  63. if ( Limit > 0xFFFFF )
  64. return GDT_LIM_TOO_BIG;
  65. uint64_t SegBaseVal = Wow64 ? Gdt_GetEntry( GDT_ENTRY( 0x23 ) ) : Gdt_GetEntry( GDT_ENTRY( 0x33 ) );
  66. KGDTENTRY* SegBase = &SegBaseVal;
  67. SegBase->Base0 = ( Base >> 8 * 0 ) & 0xFF;
  68. SegBase->Base1 = ( Base >> 8 * 1 ) & 0xFF;
  69. SegBase->Base2 = ( Base >> 8 * 2 ) & 0xFF;
  70. SegBase->Base3 = ( Base >> 8 * 3 ) & 0xFF;
  71. SegBase->Limit0 = ( Limit >> 8 * 0 ) & 0xFF;
  72. SegBase->Limit1 = ( Limit >> 8 * 1 ) & 0xFF;
  73. SegBase->Limit2 = ( Limit >> 8 * 2 ) & 0xF;
  74. SegBase->Granularity = Granularity;
  75. return Gdt_SetEntry( GDT_ENTRY( Seg ), SegBaseVal );
  76. }

只需复制原始段的值,相应地设置Base,Limit和granularity ,然后创建一个DPC以使用sgdt获取每个处理器的GDT 基地址 ,然后写入指定的索引。 (您可能会注意到我没有分配新的GDT,这是因为在替换GDT指针后,我的用户很少会遇到奇怪的系统冻结)


现在我们已经成功设置了新的段,这是我们遇到的第一个问题,即IP范围之外的问题。


让我们看下面的例子:

  1. Module base (0x400000) /---------------------\ /---------------------\ (CS Base + 0x400000) 我们的镜像模块基地址
  2. | ... | | ... |
  3. | CALL 0x600214 | | CALL 0x600214 |
  4. | MOV [0x312321], EAX | | MOV [0x312321], EAX |
  5. | CALL 0x333333 | | CALL 0x333333 |
  6. | ... | | ... |
  7. | ... | | ... |
  8. | ... | | ... |
  9. | ... | | ... |
  10. Module end (0x500000) \---------------------/ \---------------------/ (CS Base + 0x500000) 镜像模块结束地址


当该目标IP比模块基地址小(低级CALL 0x333333 ),或者当目标IP比模块基座更高(CALL 0x600214 ),这回产生问题,因为他们将分别执行CS 基地址+ 0x333333和  CS 基地址+ 0x600214,不在我们的镜像模块范围 可能是其它模块。


这第一个问题很容易处理。只需将GDT条目的Limit设置为模块大小,并在发生GPF(页面异常)时恢复CS为原始的cs  并设置IP = IP + Shadow Base-Real Base(此时ip指向镜像模块当前执行的地方 继续执行  并冒着泄漏shadow base的风险,因为需要push指向镜像模块的返回指针到堆栈,然后回到0x23:镜像模块处继续执行 执行完毕返回的时候 会回到镜像模块继续执行  执行的时候 一旦遇到call jmp等 目标地址不在镜像段范围内的  又会异常 此时我们恢复cs为镜像cs 继续在镜像原始地址下一条执行),或者自己解决调用,如下所示:

  1. static BOOLEAN ResolveCall( ITRAP_FRAME* Frame, UCHAR* Instruction, uint32_t* Target, uint8_t* InstructionSize )
  2. {
  3. if ( Instruction[ 0 ] != 0xE8 && Instruction[ 0 ] != 0xFF )
  4. return FALSE;
  5. hde32s s = { 0 };
  6. *InstructionSize = hde32_disasm( Instruction, &s );
  7. if ( Instruction[ 0 ] == 0xFF && s.modrm_reg == 2 )
  8. {
  9. if ( s.sib )
  10. {
  11. if ( s.modrm_mod == 0 )
  12. *Target = *( uint32_t* ) ( ResolveRegisterById( Frame, s.sib_index ) * ( 1 << s.sib_scale ) + s.disp.disp32 );
  13. else if ( s.modrm_mod == 1 )
  14. *Target = *( uint32_t* ) ( ResolveRegisterById( Frame, s.sib_base ) + s.disp.disp32 );
  15. else
  16. *Target = *( uint32_t* ) ( ResolveRegisterById( Frame, s.sib_base ) + ResolveRegisterById( Frame, s.sib_index ) * ( 1 << s.sib_scale ) + s.disp.disp32 );
  17. }
  18. else
  19. {
  20. if ( s.modrm_mod == 0 )
  21. *Target = *( uint32_t* ) ( s.disp.disp32 );
  22. else if ( s.modrm_mod == 3 )
  23. *Target = ResolveRegisterById( Frame, s.modrm_rm );
  24. else if ( s.modrm_mod == 2 || s.modrm_mod == 1 )
  25. *Target = *( uint32_t* ) ( ResolveRegisterById( Frame, s.modrm_rm ) + s.disp.disp32 );
  26. }
  27. return TRUE;
  28. }
  29. else if ( Instruction[ 0 ] == 0xE8 )
  30. {
  31. *Target = Frame->Rip + s.imm.imm32 + 5;
  32. return TRUE;
  33. }
  34. return FALSE;
  35. }
  36. hook处理GPF页面异常的中断
  37. static BOOLEAN NTAPI HkOnGpf( ITRAP_FRAME* TrapFrame )
  38. {
  39. if ( TrapFrame->Cs == SHADOW_HOOK_SEG )
  40. {
  41. // CALL | JMP | RET 目标地址不在本段范围内?
  42. __swapgs();//切换gs到内核环境
  43. SHADOW_MODULE_ENTRY Sme = GetShadowModuleFromRip( PsGetCurrentProcessId(), TrapFrame->Rip );//通过异常ip获取在镜像段的相关信息结构
  44. if ( Sme.ModuleReal )//原始模块基地址
  45. {
  46. uint64_t RspBackup = TrapFrame->Rsp;//异常时候的堆栈
  47. _enable();
  48. //Log( "Handling call to the outside of shadow module @ %llx\n", TrapFrame->Rip );
  49. __try
  50. {
  51. uint32_t Destination = 0;
  52. uint8_t InstructionSize = 0;
  53. //解析 获取要跳转的目标地址
  54. if ( ResolveCall( TrapFrame, TrapFrame->Rip - Sme.ModuleReal + Sme.ModuleShadow, &Destination, &InstructionSize ) )
  55. {
  56. uint32_t IsPageMapped = FALSE;
  57. KIRQL Irql = RsAcquireSpinLockRaiseToDpc( &Rs_ProcessRecordSpinLock );
  58. DWORD Pid = PsGetCurrentProcessId();
  59. if ( Rs_ProcessRecordsMaxPid > Pid && Rs_ProcessRecords[ Pid ] )
  60. {
  61. for ( int i = 0; i < ARRAYSIZE( Rs_ProcessRecords[ Pid ]->SpoofedProtect ); i++ )
  62. {
  63. if ( !Rs_ProcessRecords[ Pid ]->SpoofedProtect[ i ].PageBase )
  64. break;
  65. if ( ( Rs_ProcessRecords[ Pid ]->SpoofedProtect[ i ].PageBase & ( ~0xFFF ) ) == ( TrapFrame->Rip & ( ~0xFFF ) ) )
  66. {
  67. IsPageMapped = TRUE;//已经存在镜像
  68. break;
  69. }
  70. }
  71. }
  72. RsReleaseSpinLock( &Rs_ProcessRecordSpinLock, Irql );
  73. TrapFrame->Rsp -= 0x4;
  74. *( uint32_t* ) TrapFrame->Rsp =
  75. IsPageMapped
  76. ? ( TrapFrame->Rip + InstructionSize - Sme.ModuleReal + Sme.ModuleShadow )//如果目标内存已存在镜像 直接堆栈塞入在镜像的 指令返回地址 比如第一个CALL 0x600214
  77. : ( TrapFrame->Rip + InstructionSize );//否则塞入原始模块的地址 指令返回地址
  78. TrapFrame->Rip = Destination;//塞入目标地址
  79. TrapFrame->Cs = WOW64_SEG;//指向原始的cs
  80. //Log( " --> %llx (%d)\n", TrapFrame->Rip, IsPageMapped );
  81. _disable();
  82. __swapgs();
  83. return TRUE;
  84. }
  85. }
  86. __except ( 1 )
  87. {
  88. }
  89. _disable();
  90. __swapgs();
  91. TrapFrame->Rsp = RspBackup;//解析目标地址失败 可能是数据的访问 就直接恢复堆栈 比如第二个MOV [0x312321], EAX
  92. TrapFrame->Rip -= Sme.ModuleReal;//#1但是第三条指令是 CALL 0x333333 那么执行完第二条后 因为用的cs是原始的 原始limit是整个进程范围 那么0x333333也可以执行 而不是镜像的 关键call的目标地址和下一条指令地址+偏移有关系 这里的0x333333只是打比喻 真实情况下 镜像的0x333333代表一个只有镜像才存在的目标指令地址 但是当cs是原始cs的时候 这个0x333333可能是另外一个地址 那里没有指令 可能全是nop 或者c3 导致直接异常
  93. TrapFrame->Rip += Sme.ModuleShadow;//设置镜像模块的地址
  94. TrapFrame->Cs = WOW64_SEG;//原始模块的cs
  95. return TRUE;
  96. }
  97. else
  98. {
  99. // 从原始模块执行完毕 返回到镜像模块 发生异常的时候 切换cs到镜像cs
  100. }
  101. __swapgs();
  102. }
  103. return FALSE;
  104. }

游客,如果您要查看本帖隐藏内容请回复


现在,您可以使用jmp 0x23 :Trampoline HOOK 镜像模块, 并使用jmp 0xAA :RetPtr 返回, 或者根据需要更改指令。除了您需要使原始页面不执行之外,没有其他区别。

参考本站另外一篇文章


0

主题

2

帖子

0

积分

韶华一笑间

Rank: 1

积分
0
发表于 2019-11-10 03:41:46 | 显示全部楼层
学习一下谢谢了

0

主题

6

帖子

1

积分

韶华一笑间

Rank: 1

积分
1
发表于 2019-11-13 11:13:35 | 显示全部楼层
好东西,看一看

0

主题

36

帖子

6

积分

韶华一笑间

Rank: 1

积分
6
发表于 2019-11-15 08:18:33 | 显示全部楼层
学习学习

0

主题

4

帖子

1

积分

韶华一笑间

Rank: 1

积分
1
发表于 2020-3-15 11:48:34 | 显示全部楼层
学习一下谢谢了

0

主题

9

帖子

2

积分

韶华一笑间

Rank: 1

积分
2
发表于 2020-3-24 10:05:54 | 显示全部楼层
学习一下!!

0

主题

10

帖子

1

积分

韶华一笑间

Rank: 1

积分
1
发表于 2020-3-25 15:21:34 | 显示全部楼层

学习一下谢谢了

0

主题

17

帖子

4

积分

韶华一笑间

Rank: 1

积分
4
发表于 7 天前 | 显示全部楼层
学习一下谢谢了
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|瞄眼社区

GMT+8, 2020-4-2 22:53 , Processed in 0.363381 second(s), 28 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表