ペイロードは次の手順に従って作成する.
主に次のような命令がある(instruction, x, y). これらを組み合わせて行いたい処理を記述する.
GASでは8ビットコンピュータおよび16ビットコンピュータとの互換性を保つため,レジスタおよびレジスタを操作する命令にはそれぞれ8ビット用命令,16ビット用命令,32ビット用命令の3種類が用意されている.
8ビット用命令なら32ビットコンピュータで動作しても下位8ビットのみを操作する.レジスタを参照する場合でも8ビット互換レジスタを指定すれば32ビットレジスタであっても下位8ビットしか参照しない.
mov %eax, %ebxのように互換命令部分を省略して記述することも可能.この場合は後ろのレジスタの種類によってl,w,bが補完される.
図1:互換レジスタの参照部分
実際には次のように記述する.
movl $0x1, %eax ;1をeaxに代入 eax: 0000 0001
incl %eax ;eaxに1加算 eax: 0000 0002
movb %ah, %al ;eaxをゼロクリア eax: 0000 0000
movl $0xFFFF0F0F, %eax ;eaxに値を代入 eax: FFFF 0F0F
movw $0xF0F0, %bx ;ebxにF0F0を代入 ebx: 0000 F0F0
xorw %ax, %bx ;axとbxのxor ax: 0F0F→0000111100001111
; bx: F0F0→1111000011110000
;eaxはFFFFFFFFになる
ハッキングは他者の管理するシステムへ行うが,通常システム情報が公開されていることはない.ゆえになんらかの方法で,どのOSを使っているのか詮索する必要がある.
メモをとっていない方,第0回その2に参加していない方は次のソースをご利用ください.
.code32
.data
msg: .ascii "Hello!"
.global main
main:
movl $4, %eax
movl $0, %ebx
movl $msg, %ecx
movl $6, %edx
int $0x80
ret
前回作ったプログラムはあまり行儀の良いプログラムとは言えない.プログラムがすべて終わったことを示し,OSに制御を移すべくexitシステムコールを呼ぶのが普通.
前回作成したHelloWorldプログラムにexitシステムコールを追加し,まっとうなプログラムに更生せよ.
実行ファイルを2進数や16進数で表したものをバイトコードと呼ぶ.ペイロードとして実行するプログラムは最終的にバイトコードに変換して利用する必要がある.
16進数エディタのobjdumpを使う.そのままでは不要な部分まで表示され見にくいため,次のようにタイプする.
defolos@glazheim:~$ objdump -d a.out|grep \<main\> -A 30
NULLバイト(0x00)はstrcpy()などでは文字列の終端を意味するため,ペイロードの途中にNULLが含まれると正常に動作しない.
int main(int argc, char *argv[]){
char str1[] = "Hello\0World";
char str2[12];
strcpy();
printf("%s\n", str2);
return 0;
}
前述の問題点を解決するために,ペイロードの作成スタイルには独自のテクニックを使用することが多い.
スタックセグメントのみを使用するようにする.特に問題になるのが文字列などのデータ.文字列を格納しているメモリの先頭アドレスを取得できれば良い.
main:
jmp ONE
TWO:
popl %ecx
;codes...
ONE:
call TWO
.string "STRINGS"
文字列を4バイトごとに区切ってスタックにプッシュすることで文字列を生成する.
pushl $0x0
pushl $0x ;!gni
pushl $0x ;nroM
movl %esp, %ecx
ただし,文字列が4の倍数でなければこのテクニックは利用できない.
NULLバイトの原因はコード中に出現する「0」が影響している.つまり,0を使わずにコードを書けば良い.
11010010 xor)01110111 ---------- 10100101
11010010 xor)11010010 ---------- 00000000
「movl $0x5, %eax」は4バイト(32ビット)の転送命令であるが,0x5は101であり8ビットでも表現可能.残の24ビットには埋め草として「0」が挿入され,コンパイル時にmovl $0x00000005, %eaxと解釈される.
movb $0x5, $al
ペイロードのテストには,プログラム内で意図的にペイロードを実行する次のプログラムを利用する.
unsigned char payload[]="SHELLCODE";
int main(void){
int *retadd;
retadd = (int *)&retadd + 2;
(*retadd) = (int)payload;
return 0;
}
プログラムの流れを変えてスタックセグメント上のペイロードを実行する点で,バッファオーバフローなどのExpliotと動作は変わらない.
これまで紹介したテクニックを参考にし,シェル(/bin/sh)を起動するペイロードを作成せよ