(Last modified: )

動的基本ブロックと静的基本ブロックの違いを考える

動的基本ブロック(Dynamic Basic Block)と静的基本ブロック(Static Basic Block)の違いを自分なりに考えた記事です。

DynamoRIO System Overviewによると、コードキャッシュへコピーされるコードは動的基本ブロック(Dynamic Basic Block)と記されています。 この動的基本ブロックについて、DynamoRIOのinstrlist_disassembleを用いながら静的基本ブロックとの違いを確認していきます。

DynamoRIOについては、DynamoRIOでシンプルな基本ブロックトレーサーを実装してみるを参照してください。

call-pop trick

32bitのシェルコードは、文字列のアドレスを取得するためによくcallとpopが用いられます。

次のコードは、call命令の後に文字列を置くことで、リターンアドレスが文字列ポインタとなります。 したがって、文字列の取得にpopを使っています。この場合、retによって呼び出し元に戻ることは考慮されません。

 1SECTION .text
 2global _start
 3
 4_print:
 5    ; write(STDOUT, ...)
 6    mov eax, 4
 7    mov ebx, 1
 8    pop ecx ; mov ecx, DWORD [esp]
 9    mov edx, 13
10    int 0x80
11
12    ; exit(0)
13    mov eax, 1
14    mov ebx, 0
15    int 0x80
16
17_start:
18    call _print
19    db "Hello world!", 10

上のコードのファイル名をmain.asmとする場合、次のようにビルドします。 以降、出力したa.outを見ていきます。

nasm -f elf main.asm -o a.o
ld -m elf_i386 a.o -o a.out

静的基本ブロック

個人的には、静的基本ブロックとはIDAやRadare2などで見られる基本ブロックだと考えています。 生成された実行ファイルの_startr2で逆アセンブルしてみましょう。 0x080490230x0804902bより、Hello world!が逆アセンブルされていることが分かります。

このような基本ブロックは、終点が分岐命令やジャンプ命令です。本来、call命令は元の基本ブロックに戻るため、終点となりません。 しかし、今回のように、リターンを意図しないcall命令もあります。そのような場合、静的基本ブロックでは誤った逆アセンブル結果を返します。

$ r2 ./a.out
[0x0804901e]> aa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze all functions arguments/locals
[0x0804901e]> pdb @ loc._start
            ;-- _start:
            ;-- eip:
┌ 18: entry0 ();
│           0x0804901e      e8ddffffff     call loc._print
│           0x08049023      48             dec eax
│           0x08049024      656c           insb byte es:[edi], dx
│           0x08049026      6c             insb byte es:[edi], dx
│           0x08049027      6f             outsd dx, dword [esi]
│           0x08049028      20776f         and byte [edi + 0x6f], dh
│       ┌─< 0x0804902b      726c           jb 0x8049099

0x08049023以降のコードが実行されるか否かは、実際に実行してみない限り確認の仕様がありません。

動的基本ブロック

DynamoRIO System Overviewによると、動的基本ブロックをコードキャッシュにコピーすると書いています。 そこで、コードキャッシュをinstrlist_disassembleで確認してみます。

このコードの詳細は、DynamoRIOでシンプルな基本ブロックトレーサーを実装してみるを参照してください。

 1static module_data_t *app;
 2static dr_emit_flags_t disassemble_codecache(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, bool translating)
 3{
 4    app_pc pc = dr_fragment_app_pc(tag);
 5    if (dr_module_contains_addr(app, pc))
 6    {
 7        instrlist_disassemble(drcontext, pc, bb, STDERR);
 8    }
 9    return DR_EMIT_DEFAULT;
10}

./a.outelf_i386なので32bitでビルドします。

CXXFLAGS=-m32 CFLAGS=-m32 cmake ..
cmake --build .

実行した結果が次のとおりです。静的基本ブロックと異なり、call命令が基本ブロックの終端です。 これにより、コードとして実行可能なアセンブリのみを出力できました。

$ drrun -syntax_intel -c ./show_codecache.so -- a.out
TAG  0x0804901e
 +0    L3 @0x3ff07d7c  e8 dd ff ff ff       call   0x08049000
END 0x0804901e

TAG  0x08049000
 +0    L3 @0x3ff0755c  b8 04 00 00 00       mov    eax, 0x00000004
 +5    L3 @0x3ff078d8  bb 01 00 00 00       mov    ebx, 0x00000001
 +10   L3 @0x3ff07d7c  59                   pop    ecx
 +11   L3 @0x3ff07f38  ba 0d 00 00 00       mov    edx, 0x0000000d
 +16   L3 @0x3ff07760  cd 80                int    0x80
END 0x08049000

Hello world!
TAG  0x08049012
 +0    L3 @0x3ff074c8  b8 01 00 00 00       mov    eax, 0x00000001
 +5    L3 @0x3ff07760  bb 00 00 00 00       mov    ebx, 0x00000000
 +10   L3 @0x3ff07a18  cd 80                int    0x80
END 0x08049012

これをCFGで示そうとすると冗長です。特に、間接ジャンプ(Indirect Jump)をCALLする場合、そこでCFGが途切れてしまいます。 静的基本ブロックの場合は、同じ間接ジャンプでもJMPでなければ(今回のように正しくない結果となる場合もありますが)CFGを生成できます。

Powered by Hugo & Kiss.