アセンブリ言語について少し学ぶ
書き出し
アセンブリ言語について少し学ぶ。 メモ程度のもの。
本文
コマンド
# nasmは Netwide Assemblerの略称 nasm -f<format> <in> -o <out> #optin #-f : format # リンク用のGNUコマンド ld -o <out> <in>
メモ
hello.asm
global _start section .data message:db 'hello, world!',10 section .text _start: mov rax, 1 mov rdi, 1 mov rsi, message mov rdx, 15 syscall mov rax, 60 xor rdi, rdi syscall
この場合、.data(global 変数)
にdb(data byte)
の変数mesageを配置している。
また命令セクションである.text
に_start命令を配置し、syscallの呼び出し準備を行う。
syscall呼び出し時のレジスタ内部
今回自分の中で確認するために、はじめのwriteのコール時のシステムコールを元に解釈していく。
レジスタ | 値 | 意味 | 内容 |
---|---|---|---|
rax | 1 | system call | system call number |
rdi | 1 | 第一引数 | discription(書き込み先) |
rsi | message | 第二引数 | 文字列の先頭アドレス |
rdx | 14 | 第三引数 | 書き込むバイト数 |
C言語で表すとこのような表示になる。
#include <unistd.h> ssize_t write(int fd , const void * buf , size_t count );
syscall実行時にraxからwriteが実行され、引数として先頭からrdi,rsi,rdxが配置される。
変数の定義
表記 | 意味 |
---|---|
db | data byte|1byte |
dw | data word|2byte |
dd | data double word|4byte |
dq | data quad word|8byte |
例
section .data example1:db 1, 1, 2, 3, 5, 7 example2:times 999 db 1 example3:dw 999
times
変数(data)は、どのセクションの内側でも作ることができる。 CPUから見ればデータも一つの命令となるので解釈される。
実際にelfファイルにし、確認してみようと思う。
次のような内容になっており。しっかりと.data
や.text
にセクションが割り当てられていることがわかる。
$ objdump -D hello
hello: ファイル形式 elf64-x86-64 セクション .text の逆アセンブル: 00000000004000b0 <_start>: 4000b0: b8 01 00 00 00 mov $0x1,%eax 4000b5: bf 01 00 00 00 mov $0x1,%edi 4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi 4000c1: 00 00 00 4000c4: ba 0f 00 00 00 mov $0xf,%edx 4000c9: 0f 05 syscall 4000cb: b8 3c 00 00 00 mov $0x3c,%eax 4000d0: 48 31 ff xor %rdi,%rdi 4000d3: 0f 05 syscall セクション .data の逆アセンブル: 00000000006000d8 <message>: 6000d8: 68 65 6c 6c 6f pushq $0x6f6c6c65 6000dd: 2c 20 sub $0x20,%al 6000df: 77 6f ja 600150 <_end+0x68> 6000e1: 72 6c jb 60014f <_end+0x67> 6000e3: 64 21 0a and %ecx,%fs:(%rdx)
局所ラベルの利用とレジスタの出力
section .data codes: db '0123456789ABCDEF' section .text global _start _start: mov rax, 0x1122334455667788 mov rdi, 1 mov rdx, 1 mov rcx, 64 jmp .loop ;このstartで、rax内に値が存在する状況を生成。 .loop: ; 16文字の出力を行うためにこのloopで出力を行う。 ; 先の_startで設定したカウンタレジスタ内に16バイト分の数値を設定し、それを減算することにより文字列を出力する。 push rax ; raxの値を退避させることにより、値を保持。 sub rcx, 4 sar rax, cl and rax, 0xf lea rsi, [codes + rax] ;[codes + rax]は相対アドレスを意味する。 mov rax, 1 push rcx syscall pop rcx pop rax test rcx, rcx jnz .loop ; testで0でないことを確認してから、.loopへ飛ばす。 ; 文字数がなくなった時点でloop処理を終了し、exitする。 mov rax, 60 xor rdi, rdi syscall
.loop
のように先頭にピリオドを配置することにより局所的なラベルとして機能する。
今回の場合_start.loop
という扱いになる。
$ objdump -D ./print_rax ./print_rax: ファイル形式 elf64-x86-64 セクション .text の逆アセンブル: 00000000004000b0 <_start>: 4000b0: 48 b8 88 77 66 55 44 movabs $0x1122334455667788,%rax 4000b7: 33 22 11 4000ba: bf 01 00 00 00 mov $0x1,%edi 4000bf: ba 01 00 00 00 mov $0x1,%edx 4000c4: b9 40 00 00 00 mov $0x40,%ecx 4000c9: eb 00 jmp 4000cb <_start.loop> 00000000004000cb <_start.loop>: 4000cb: 50 push %rax 4000cc: 48 83 e9 04 sub $0x4,%rcx 4000d0: 48 d3 f8 sar %cl,%rax 4000d3: 48 83 e0 0f and $0xf,%rax 4000d7: 48 8d b0 f8 00 60 00 lea 0x6000f8(%rax),%rsi 4000de: b8 01 00 00 00 mov $0x1,%eax 4000e3: 51 push %rcx 4000e4: 0f 05 syscall 4000e6: 59 pop %rcx 4000e7: 58 pop %rax 4000e8: 48 85 c9 test %rcx,%rcx 4000eb: 75 de jne 4000cb <_start.loop> 4000ed: b8 3c 00 00 00 mov $0x3c,%eax 4000f2: 48 31 ff xor %rdi,%rdi 4000f5: 0f 05 syscall セクション .data の逆アセンブル: 00000000006000f8 <codes>: 6000f8: 30 31 xor %dh,(%rcx) 6000fa: 32 33 xor (%rbx),%dh 6000fc: 34 35 xor $0x35,%al 6000fe: 36 ss 6000ff: 37 (bad) 600100: 38 39 cmp %bh,(%rcx) 600102: 41 rex.B 600103: 42 rex.X 600104: 43 rex.XB 600105: 44 rex.R 600106: 45 rex.RB 600107: 46 rex.RX
関数のcall
いままではラベルを利用して、jampしそこで実行されるなどを行ってきたが、実際に関数をコールしたいと思う。
push rip jmp <address>
このようにコールする際は、ripをstack内に保存し、ジャンプする。
関数の利用
実際に関数を利用し、printを実装する。
アセンブラで関数を実装するのには特にラベルとの違うところはない。
主に、異なる箇所は、retやpushによる値の保存を行っているところであろう。
section .data demo1: dq 0x1122334455667788 demo2: db 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 demo3: db '0123456789abcdef' newline_char: db 10 section .text global _start print_newline: ; いつもの初期設定 mov rax, 1 mov rdi, 1 mov rsi, newline_char mov rdx, 1 syscall ret print_hex: mov rax, rdi mov rdi, 1 mov rdx, 1 mov rcx, 64 ; rax をシフトするビット数 iterate: push rax sub rcx, 4 sar rax, cl ; 右にシフト回転させる and rax, 0xf ; 下位4ビット以外を初期化 lea rsi, [codes + rax] ; 16進数の文字コードを取得 mov rax, 1 push rcx ; write自体がrcxを破壊するので退避する。 syscall pop rcx pop rax test rcx, rcx jnz iterate ret _start: mov rdi, [demo1] ; [demo1]がdemo1の先頭アドレスをを与える。 call print_hex call print_newline mov rdi, [demo2] call print_hex call print_newline mov rdi, [demo3] call print_hex call print_newline mov rax, 60 xor rdi, rdi syscall
何かあれば追記する。