import gmpy2 n = 29331922499794985782735976045591164936683059380558950386560160105740343201513369939006307531165922708949619162698623675349030430859547825708994708321803705309459438099340427770580064400911431856656901982789948285309956111848686906152664473350940486507451771223435835260168971210087470894448460745593956840586530527915802541450092946574694809584880896601317519794442862977471129319781313161842056501715040555964011899589002863730868679527184420789010551475067862907739054966183120621407246398518098981106431219207697870293412176440482900183550467375190239898455201170831410460483829448603477361305838743852756938687673 c = 2205316413931134031074603746928247799030155221252519872649649212867614751848436763801274360463406171277838056821437115883619169702963504606017565783537203207707757768473109845162808575425972525116337319108047893250549462147185741761825125
for k in range(100000): [r, exact] = gmpy2.iroot(c+n*k, 3) if exact: print(k) print(bytes.fromhex(hex(r)[2:]))
因為數字很大的緣故所以沒辦法用 pow 來驗證是不是整數的三次方,就用了 gmpy2
另一個方法是用 RsaCtfTool 的 cube_root Attack,它用了二分搜
b00tl3gRSA2
我們得到的 e 很大,但實際應用上來說 e 並不大,所以那個 e 實際上大概是 d。
再來 RSA 就數學上來說用 e 或 d 來加密是沒有差的,不過因為 e 和 d 一般都有一定的大小差距,所以有些特殊的方法可以在特定情況下幫你破解 RSA。像是 Google 搜尋一下 RSA small d 就告訴我們有個 Wiener's attack 可以應用在這種情況下。
with open('body.enc.ppm', 'rb') as f: header = f.readline()+f.readline()+f.readline() cur = f.tell() sz = f.seek(0, 2)-cur f.seek(cur) blocks = [] for i in range(sz//16): blocks.append(int.from_bytes(f.read(16), byteorder='big'))
UMAX = 256**16
blocks_ebc = [] for i in range(len(blocks)-1): blocks_ebc.append((blocks[i+1]-blocks[i]) % UMAX)
data = header+b''.join([blk.to_bytes(16, byteorder='big') for blk in blocks_ebc]) with open('body.ppm', 'wb') as f: f.write(data)
想用 z3 解也是可以,不過需要多花點時間(約一分鐘),因為資料量比較大
b00tl3gRSA3
這題的關鍵在於它用了多個質數,而多個質數所相乘的比起雙質數的要好分解很多,所以就直接給他分解,算出 然後就能求 d 解密了。
chs = list('jU5t_a_sna_3lpm18g947_u_4_m9r54f') for i in range(0, 8): s.add(p[i] == ord(chs[i])) for i in range(8, 16): s.add(p[i] == ord(chs[23-i])) for i in range(16, 32, 2): s.add(p[i] == ord(chs[46-i])) for i in range(31, 16, -2): s.add(p[i] == ord(chs[i]))
if s.check() == sat: print(s.model()) else: print(s.unsat_core())
vault-door-4
想辦法把它的各種格式轉換一下就好了,像是轉成 python 能接受的 input 直接貼進去。
vault-door-5
Base64 + URL encoding
vault-door-6
丟進 python 做 xor
vault-door-7
簡單的 Shifting:
1 2 3 4 5 6 7
x = [1096770097, 1952395366, 1600270708, 1601398833, 1716808014, 1734304867, 942695730, 942748212]
flag = b'' for y in x: flag += bytes.fromhex(hex(y)[2:]) print(flag)
x = [ord(c)-97for c in"lfmhjmnahapkechbanheabbfjladhbplbnfaijdajpnljecghmoafbljlaamhpaheonlmnpmaddhngbgbhobgnofjgeaomadbidl"] key = String('key') s = Solver() s.add(Length(key) == len(x))
for i in range(len(x)): c = key[i] s.add(Or(And(47 < c, c <= 57), And(96 < c, c <= 102)))
s.add(fn(key[0]) % 16 == x[0]) for i in range(1, len(x)): v4 = fn(key[i]) v5 = x[i-1]+v4 v6 = (((x[i-1]+v4) >> 31) & 0xffffffff) >> 28 s.add(x[i] == (((v6+v5) & 0xF)-v6))
assert(s.check() == sat) m = s.model() key = m[key].as_string() print(key) k = int(key, 16) flag = int( "a5d47ae6ffa911de9d2b1b7611c47a1c43202a32f0042246f822c82345328becd5b8ec4118660f9b8cdc98bd1a41141943a9", 16) flag ^= k print(''.join([chr(c) for c in bytes.fromhex(hex(flag)[2::])]))
reverse_cipher
一樣是 z3 下去解決:
1 2 3 4 5 6 7 8 9 10 11 12 13
from z3 import *
target = [ord(c) for c in"w1{1wq84fb<1>49"] key = String('key') s = Solver()
s.add(Length(key) == len(target)) for i in range(len(target)): c = key[i] s.add(If(i & 1 != 0, c-2, c+5) == target[i])
if len(sys.argv) < 3: # python asm.py $FILE $PREFIX sys.exit()
with open(sys.argv[1], 'r') as f: lines = f.readlines()
prog = '' lst_lbl = None for line in lines[1:]: line = re.sub(r'<\+(\d+)>', r'L\1', line) line = re.sub(r'0x.*?<.*?\+(\d+)>', r'L\1', line) prog += line lbl = re.search(r'L\d+', line) if lbl: lsl_lbl = lbl[0]
if len(sys.argv) < 3: print(f'python {sys.argv[0]} $ASM_FILE $C_FILE') sys.exit()
asm_file = sys.argv[1] c_file = sys.argv[2]
with open(asm_file, 'r') as f: lines = f.readlines()
prog = '' lst_lbl = None for line in lines: line = re.sub(r'<\+(\d+)>:', r'', line) line = re.sub(r'0x.*?<(.*?\+\d+)>', r'\1', line) prog += line lbl = re.search(r'L\d+', line) if lbl: lsl_lbl = lbl[0]
s = Solver() arr = Array('arr', IntSort(), IntSort()) for i in range(len(data)): s.add(arr[i] == data[i]) # arr = Store(arr, i, data[i]) is very slow shifters = [Int(f's_{i}') for i in range(16)] for x in shifters: s.add(0 <= x) s.add(x <= 9) result = [0]*len(data) for i in range(16): shifter = shifters[i] for j in range(len(data)//16): result[(j*16)+i] = arr[(((j+shifter)*16) % len(data))+i]
header = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a,0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52] # Header + IHDR for i in range(len(header)): s.add(result[i] == header[i])
while s.check() == sat: m = s.model() shs = [m[x].as_long() for x in shifters] result=[0]*len(data) for i in range(16): sh=shs[i] for j in range(len(data)//16): result[(j*16)+i] = data[(((j+sh)*16) % len(data))+i] if'IEND'in''.join([chr(c) for c in result]): # Checks for IEND as there are multiple satisifying solutions print(''.join([str(s) for s in shs])) s.add(reduce(Or, [x != m[x].as_long() for x in shifters]))
s = Solver() arr = Array('arr', IntSort(), IntSort()) for i in range(len(data)): s.add(arr[i] == data[i]) # arr = Store(arr, i, data[i]) is very slow shifters = [Int(f's_{i}') for i in range(16)] for x in shifters: s.add(0 <= x) s.add(x <= 9) result = [0]*len(data) for i in range(16): shifter = shifters[i] for j in range(len(data)//16): result[(j*16)+i] = arr[(((j+shifter)*16) % len(data))+i]
header = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52] # Header + IHDR for i in range(len(header)): s.add(result[i] == header[i])
while s.check() == sat: m = s.model() shs = [m[x].as_long() for x in shifters] result = [0]*len(data) for i in range(16): sh = shs[i] for j in range(len(data)//16): result[(j*16)+i] = data[(((j+sh)*16) % len(data))+i] if''.join([chr(c) for c in [0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82]]) in''.join([chr(c) for c in result]): # Full IEND print(''.join([str(s)+'0'for s in shs])) s.add(reduce(Or, [x != m[x].as_long() for x in shifters]))
> /usr/bin/checksec --file=vuln RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH 1844) Symbols No 0 0 vuln
p = remote('jupiter.challenges.picoctf.org', 51462) # process('./vuln') whileTrue: line = p.readline() ifb"like to guess"in line: p.sendline("2") elifb"Congrats"in line: p.sendline(payload) break p.interactive()
Guessing Game 2
首先一樣是要你猜數字,只是注意到 get_random 裡面回傳的是函數 rand 的位置,所以用 gdb 理論上是可以弄到的,但因為是在對方的 server 上執行,libc 的版本應該都是不同的,所以只能暴力找:
1 2 3 4 5 6 7 8
from pwn import * p = remote('jupiter.challenges.picoctf.org', 13775) for i in range(-4100, 4100): # Note: -5 % 3 == -1 in C p.recvuntil('What number') p.sendline(str(i)) ifb'Congrats'in p.recvline(): print(i) break
給不想花時間在這上面的人,答案是 -31
再來是到 win() 函數,有明顯的 buffer overflow 和 format string 兩個問題,所以先來做些檢查看看要用什麼方法。checksec 告訴你有 NX, Canary, RELRO,只有 PIE 沒有,然後再用 ROPgadget 看一下有沒有 syscall 或是 int 0x80,只是也找不到,最後找找看 /bin/sh 的 string 也是沒有,所以大概只剩下 return to libc 能用了。
不過它沒有提供給我們 libc,所以只能想辦法找,就先在 local 這邊用 gdb 找一下各個重要 address 和 format string 的 offset 是多少,然後再用 LibcSearcher 去找 libc 版本,然後就能很方便的算出 address 了。