x86-64で簡単なシェルコードを作ってみる

最近portswiggerにハマっててVulnhubの勉強が滞りがちだったのでたまには低レイヤーをやります。Web系楽しいんじゃあ^〜

とはいってもあまりにも多くの人がチャレンジしてるような内容ですが。

基本的にはこちらの、ももいろテクノロジーさんの記事にある方法をなぞっているだけです。

https://inaz2.hatenablog.com/entry/2014/03/13/013056


┌─[parrot@parrot-tobefilledbyoem]─[~/Desktop/shellcode]
└──╼ $uname -a
Linux parrot-tobefilledbyoem 5.10.0-6parrot1-amd64 #1 SMP Debian 5.10.28-6parrot1 (2021-04-12) x86_64 GNU/Linux
​└──╼ $nasm --version
NASM version 2.15.05

環境はこんな感じ(Parrot OS推し)。

まずc言語を使って、execveで"/bin/sh"を呼び出すプログラムを作る。

// int execve(const char *pathname, char *const argv[], char *const envp[]);

#include <unistd.h>

void main() {
   char *argv[] = {"/bin/sh", NULL};
   execve(argv[0], argv, NULL);
}

これをgcc -static bash.cで実行ファイルにコンパイルし、gdbでコードを見てみる。

Dump of assembler code for function main:
  0x0000000000401c2d <+0>:	push   %rbp
  0x0000000000401c2e <+1>:	mov    %rsp,%rbp
=> 0x0000000000401c31 <+4>:	sub    $0x10,%rsp
  0x0000000000401c35 <+8>:	lea    0x7f3c8(%rip),%rax        # 0x481004
  0x0000000000401c3c <+15>:	mov    %rax,-0x10(%rbp)
  0x0000000000401c40 <+19>:	movq   $0x0,-0x8(%rbp)
  0x0000000000401c48 <+27>:	mov    -0x10(%rbp),%rax
  0x0000000000401c4c <+31>:	lea    -0x10(%rbp),%rcx
  0x0000000000401c50 <+35>:	mov    $0x0,%edx
  0x0000000000401c55 <+40>:	mov    %rcx,%rsi
  0x0000000000401c58 <+43>:	mov    %rax,%rdi
  0x0000000000401c5b <+46>:	call   0x43d440 <execve>


0x000000000043d440 in execve ()
(gdb) disas
Dump of assembler code for function execve:
=> 0x000000000043d440 <+0>:	mov    $0x3b,%eax
  0x000000000043d445 <+5>:	syscall 


この時のレジスタの状態を見てみる。

(gdb) i r
rax            0x3b                59
rbx            0x400488            4195464
rcx            0x7fffffffde80      140737488346752
rdx            0x0                 0
rsi            0x7fffffffde80      140737488346752
rdi            0x481004            4722692
rbp            0x7fffffffde90      0x7fffffffde90
rsp            0x7fffffffde78      0x7fffffffde78
r8             0x6                 6
r9             0x0                 0
(以下略)​
(rcx, rsiレジスタの中身)

0x7fffffffde80:	0x00481004	0x00000000	0x00000000	0x00000000
(rdiレジスタの中身)

(gdb) x/10x $rdi
0x481004:	0x6e69622f	0x0068732f	0x6f657800	0x68705f6e

(gdb) x/10s $rdi
0x481004:	"/bin/sh"

以上より

rax: 0x3b(システムコール番号)

rdi: execve関数のchar *pathname に対応("/bin/sh"へのポインタ)

rcx, rsi: execve関数の char *argv[]に対応("/bin/sh"へのポインタのポインタ)

rdx: 0 つまりNULL

という対応関係がわかる。問題はrcx,rsiレジスタのどちらが引数渡しに使われるかということだが、以下のページの通りレジスタは rdi, rsi, rdx,...の順に使われる。よってrsiレジスタがchar *argv[]に対応している。


あとは以上の通りになるようレジスタをセットして、システムコールを呼び出すようなアセンブラコードを書けば良い。

section .text
   global _start
_start:
   xor     rdx, rdx    ; *envp[]
   push    rdx
   mov     rax, 0x68732f6e69622f  ; /bin/sh
   push    rax
   mov     rdi, rsp    ; *pathname

   push    rdx
   push    rdi
   mov     rsi, rsp    ; *argv[]

   xor     rax, rax    
   mov     al, 0x3b    ; syscall 0x3b
   syscall

上のコードでは一旦raxレジスタに0x68732f 6e69622fを代入してスタックにpushしている。リトルエンディアンなので"/sh /bin"の順序で代入している。

あとはこのアセンブリファイル(bash.s)を

nasm -f elf64 bash.s
ld -o bas bash.o

で実行ファイルにするとシェルを起動することができた。

objdumpを使えばこの実行ファイルをアセンブリに戻すことができ、ももいろテクノロジーさんのサイトにあるワンライナーを使うことで以下のようなシェルコードを作成できる。

└──╼ $objdump -M intel -d ./bas | grep '^ ' | cut -f2 | perl -pe 's/(\w{2})\s+/\\x\1/g'
\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05

このシェルコードを呼び出す、テスト用のプログラムを作って動かしてみる。

void main() {
   char const shellcode[] = "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05";
   (*(void (*)())shellcode)();
}

あとはこれをコンパイルすると、

┌─[✗]─[parrot@parrot-tobefilledbyoem]─[~/Desktop/shellcode]
└──╼ $gcc -fno-stack-protector -z execstack ./bas.c
┌─[parrot@parrot-tobefilledbyoem]─[~/Desktop/shellcode]
└──╼ $./a.out 
$ exit

無事にシェルコードの動作を確認できた。

ちなみに上記のテストプログラムで、shellcode[]をmain関数の外側に書くとSIGSEGVでセグメントエラーになる。なぜだろう…

【追記】

main関数の外側にshellcodeを置くと、shellcodeはスタックではなく.rodataセクションに配置される。この.rodataには実行の権限は無いのでセグメンテーションフォルトになる。


【反省】

初歩の初歩といった内容でしたが自分の頭で考えてシェルコードを作ることができました。実はテストプログラムの (*(void (*)())shellcode)(); の意味がよく理解できていないので、勉強しておきたいです。

精進します。





いいなと思ったら応援しよう!