バイナリの静的解析を困難にするための難読化手法(jmp、パッカー)
対象読者
基本的なx64アセンブリがわかる。
想定環境
x64GNU/Linux
序
この記事では代表的な難読化手法を解説していきます。
1, jmpを用いた難読化
例えば下記のようなx64アセンブリコード(nasm)があったとします。
section .data
str: db "Hello, World!"
section .text
global _start
_start:
; sys_write
mov rax, 1
mov rdi, 1
mov rsi, str
mov rdx, 13
syscall
loop:
jmp loop
このプログラムはシステムコールでHello, World!を出力したあと無限ループに入ります。
とりあえず普通にビルドしてみましょう。
nasm -O0 -f elf64 hello.asm
ld hello.o -o hello
次にstripしましょう。
strip --strip-all hello
次にobjdumpで逆アセンブルしてみましょう。
objdump -M x86-64 -M intel -D hello
hello: file format elf64-x86-64
Disassembly of section .text:
0000000000401000 <.text>:
401000: 48 b8 01 00 00 00 00 movabs rax,0x1
401007: 00 00 00
40100a: 48 bf 01 00 00 00 00 movabs rdi,0x1
401011: 00 00 00
401014: 48 be 00 20 40 00 00 movabs rsi,0x402000
40101b: 00 00 00
40101e: 48 ba 0d 00 00 00 00 movabs rdx,0xd
401025: 00 00 00
401028: 0f 05 syscall
40102a: e9 fb ff ff ff jmp 0x40102a
Disassembly of section .data:
0000000000402000 <.data>:
402000: 48 rex.W
402001: 65 6c gs ins BYTE PTR es:[rdi],dx
402003: 6c ins BYTE PTR es:[rdi],dx
402004: 6f outs dx,DWORD PTR ds:[rsi]
402005: 2c 20 sub al,0x20
402007: 57 push rdi
402008: 6f outs dx,DWORD PTR ds:[rsi]
402009: 72 6c jb 0x402077
40200b: 64 fs
40200c: 21 .byte 0x21
当たり前ですが難読化されていませんね。
システムコールでHello, World!を出力したあと無限ループに入っていることが読み取れます。
今回はsyscall命令を難読化してみたいとおもいます。
ではソースコードを下記のように書き換えてみましょう。
section .data
str: db "Hello, World!"
section .text
global _start
_start:
; sys_write
mov rax, 1
mov rdi, 1
mov rsi, str
mov rdx, 13
label:
db 0x81, 0xc1, 0x0f, 0x05, 0x74, 0x33
jmp label + 2
loop:
jmp loop
では先ほどと同じようにビルドしましょう。
nasm -O0 -f elf64 newhello.asm
ld newhello.o -o newhello
strip --strip-all newhello
実行できるか確認しましょう。
先程と同じように、Hello, World!出力後無限ループに入りました。
objdumpで逆アセンブルしてみましょう。
objdump -M x86-64 -M intel -D newhello
newhello: file format elf64-x86-64
Disassembly of section .text:
0000000000401000 <.text>:
401000: 48 b8 01 00 00 00 00 movabs rax,0x1
401007: 00 00 00
40100a: 48 bf 01 00 00 00 00 movabs rdi,0x1
401011: 00 00 00
401014: 48 be 00 20 40 00 00 movabs rsi,0x402000
40101b: 00 00 00
40101e: 48 ba 0d 00 00 00 00 movabs rdx,0xd
401025: 00 00 00
401028: 81 c1 0f 05 74 33 add ecx,0x3374050f
40102e: e9 f7 ff ff ff jmp 0x40102a
401033: e9 fb ff ff ff jmp 0x401033
Disassembly of section .data:
0000000000402000 <.data>:
402000: 48 rex.W
402001: 65 6c gs ins BYTE PTR es:[rdi],dx
402003: 6c ins BYTE PTR es:[rdi],dx
402004: 6f outs dx,DWORD PTR ds:[rsi]
402005: 2c 20 sub al,0x20
402007: 57 push rdi
402008: 6f outs dx,DWORD PTR ds:[rsi]
402009: 72 6c jb 0x402077
40200b: 64 fs
40200c: 21 .byte 0x21
あれっsyscall命令のニーモニックが表示されていませんね。
これはどういうことなのでしょうか。
実は jmp 0x40102a という命令がカギになります。
この命令では0x40102aにジャンプします。
0x40102aには 0f 05 があります。これはアセンブリニーモニックでいうとsyscall命令です。
つまり、add ecx,0x3374050f という命令の中にsyscallが隠れていることになります。
以上がjmpを用いた難読化です。
2, パッカーを用いた難読化
続いてはパッカーを用いた難読化手法について解説します。
パッカーとは機械語を圧縮するプログラムです。
圧縮されるとそのままでは逆アセンブルできません。
代表的なパッカーとしては、UPXがあります。
では実際に使ってみましょう。
今回はnewhelloを難読化します。
upx newhello
objdumpで逆アセンブルしてみましょう。
objdump -M x86-64 -M intel -D newhello
うまく逆アセンブルされませんね。
以上がパッカーを用いた難読化です。
ちなみに
upx -d newhello
でもとに戻せます。
Discussion