最近のお話
最近、リバースエンジニアリングにハマり、Ghidraとかで解析することが増えてきましたが、自分がghidraから得られる情報は大きく2点
- 逆コンパイル結果(簡単なコードのみ)
- 関数呼び出し時の引数
そこで!!!
アセンブリともっと仲良くなれば、もっとたくさんの情報を抜き出すことができるようになるはず。
今日は手始めに、HelloWorldをsyscallを用いて出力してみようと思う。
アセンブリコードの作成
文字列格納部分の.dataセクション
section .data
msg db "Hello_World", 0x0a
section .data
初期値ありデータ置き場(.data)はここです!と宣言
msg db "Hello_World", 0x0a
Cで書くと、以下のような形。
char msg[] = "Hello_World\n";
msgが格納される先頭のアドレス
dbはdefine byteの略であり、1バイトずつ定義するということ。
0x0aは改行コード(LF)
実行される.textセクション
宣言
section .text
global _start
section .text
実行コードが格納場所(.text)はここですよ!と宣言
global _start
_startというら別は外から見えるようにするよということ。_startはプログラムのエントリーポイントを表す。
つまり、実行開始の場所を教えてあげている。
コード部分(HelloWorldを出力)
_start:
mov rax, 1
mov rdi, 1
mov rsi, msg
mov rdx, 12
syscall
_start:
ここが実行開始地点
コード部分に入る前にsyscallの準備について
Linux x86_64では以下のようになっている
| レジスタ | 意味 |
|---|---|
| rax | syscall番号 |
| rdi | 第1引数 |
| rsi | 第2引数 |
| rdx | 第3引数 |
mov rax, 1
writeのsyscall番号が1なのでraxレジスタに1を格納する
mov rdi, 1
writeの第1引数はファイルディスクリプタを表す
stdout(標準出力)=1
つまり画面に表示させる
mov rsi, msg
第2引数はデータが格納されているアドレス。
これは、今回の場合.dataセクションでmsgと宣言している。
mov rdx, 12
第3引数はバイト数
今回出力する「Hello_World」は11バイト
最後の改行コード「0x0a」は1バイト
足して12バイトになる。
syscall
カーネルに処理を依頼している。
内部的には以下と同じ
write(1, msg, 12)
syscall後の戻り値はraxレジスタに格納される。
今回の場合は以下のようになる
成功 → 書いたバイト数
失敗 → 負の値(エラー)
コード部分(終了処理)
mov rax, 60
xor rdi, rdi
syscall
mov rax, 60
exitのsyscall = 60
xor rdi, rdi
rdiを0 にする
exitの第一引数は終了コード
なぜmov rdi, 0ではないのか?
3つの利点がある。
- 速い(CPU的に最適化される)
- バイト数が短い
- レジスタを確実に0にできる
syscall
実際に終了させる
コード全体
section .data
msg db "Hello_World", 0x0a
section .text
global _start
_start:
mov rax, 1
mov rdi, 1
mov rsi, msg
mov rdx, 12
syscall
mov rax, 60
xor rdi, rdi
syscall
アセンブル及びリンク
実際に今書いたコードを実行できる形にする。
アセンブル(.asm → .o)
nasm -f elf64 hello.asm -o hello.o
-f elf64 = Linux 64bit形式
出力は .o(オブジェクトファイル)
リンク
ld hello.o -o hello
このあたりについては以下の記事に詳しく書いてある
実行
$./hello
Hello_World
最後に
アセンブリと友達になるぞ!!!