😸

バイナリの静的解析を困難にするための難読化手法(jmp、パッカー)

2023/02/06に公開

対象読者

基本的なx64アセンブリがわかる。

想定環境

x64GNU/Linux

この記事では代表的な難読化手法を解説していきます。

1, jmpを用いた難読化

例えば下記のようなx64アセンブリコード(nasm)があったとします。

hello.asm
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
objdumpの出力

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命令を難読化してみたいとおもいます。
ではソースコードを下記のように書き換えてみましょう。

newhello.asm
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
objdumpの出力

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があります。
https://github.com/upx/upx

では実際に使ってみましょう。

今回はnewhelloを難読化します。

upx newhello

objdumpで逆アセンブルしてみましょう。

objdump -M x86-64 -M intel -D newhello

うまく逆アセンブルされませんね。
以上がパッカーを用いた難読化です。

ちなみに

upx -d newhello

でもとに戻せます。

Discussion