最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

焦点关注:[PingCTF2022] guess what - S1gMa

来源:博客园

前言

本题来自PingCTF2022 - guess what,早上12点被树木喊起来对超极长的代码审计和写 \(exp\) ,俩人直接干到下午 \(6\) 点,对着一个不存在的错误 \(debug\) 了 \(4\) 个小时,然后世一血就没了(悲),于是决定记录下来>_<

题面

本题是到交互题,题面给了 \(6\) 个脚本:\(main.py\)、\(common.py\)、\(part1.py\)、\(part2.py\)、\(part3.py\)、\(pow.py\).

代码按照顺序给出:


(资料图片)

from src.common import *from src.pow import NcPowserfrom src.part1 import part1_, part1from src.part2 import part2_, part2from src.part3 import part3_, part3def main():    part1_()    input("Press enter to continue...")    for i in range(2, 18):        part1(i)    part2_()    input("Press enter to continue...")    for i in range(2, 6):        part2(6)    part3_()    input("Press enter to continue...")    part3()if __name__ == "__main__":    if (SHOULD_USE_POW):        nc = NcPowser()        if nc.pow():            main()        else:            print("Wrong answer")            exit(0)    else:        main()
from time import timeimport itertoolsimport randomfrom Crypto.Util.number import bytes_to_longfrom progress.bar import ChargingBarfrom time import *import osSHOULD_USE_POW = TrueSHOULD_USE_ANNOYING_ANIMATIONS = Trueintro_dictionary = "AB"mid_dictionary = "ABCD"def brrr_the_strings(strings):    print("PRINTING...")    for i in range(len(strings)):        print(strings[i])    print("DONE PRINTING")
from src.common import *def part1_():    print("Hi, this is my game :)")    print("I will give you some sTrInGs, and you will have to tell me, which one is missing, seems easy, right? :D")    print("Let"s try it out!")def part1(l):    if (SHOULD_USE_ANNOYING_ANIMATIONS):        for i in ChargingBar("Loading sTrInGs", max=16, check_tty=False).iter(range(16)):            sleep(0.1)    strings = ["".join(x)               for x in itertools.product(intro_dictionary, repeat=l)]    indexToRemove = bytes_to_long(os.urandom(32)) % len(strings)    removedString = strings[indexToRemove]    strings.remove(removedString)    random.shuffle(strings)    brrr_the_strings(strings)    print("Which one is missing?")    guess = input("> ")    if guess == removedString:        print("Correct!")    else:        print("Wrong!!!!! Cmon, you can do it!")        exit(0)
from src.common import *def part2_():    print("You are doing great! Now, let"s try something harder!")    print("I will give you AGAIN some StRiNgS, and you will have to tell me, which one is missing, seems still doable, right? :D")    print("But I need you to hurry this time, so you will have to guess the missing string in 5 seconds.")    print("Let"s try it out!")def part2(l):    if (SHOULD_USE_ANNOYING_ANIMATIONS):        for i in ChargingBar("Loading StRiNgS", max=32, check_tty=False).iter(range(32)):            sleep(0.1)    strings = ["".join(x) for x in itertools.product(mid_dictionary, repeat=l)]    indexToRemove = bytes_to_long(os.urandom(32)) % len(strings)    removedString = strings[indexToRemove]    strings.remove(removedString)    random.shuffle(strings)    brrr_the_strings(strings)    print("Which one is missing?")    guess = input("> ")    if guess == removedString:        print("Correct!")    else:        print("Wrong!!!!! Cmon, you can do it!")        exit(0)
from src.common import *from flag import flagassert(len(flag) == 2**(2**2))def part3_():    print("Ok. This is kinda spooky. This time I will show you that I know everything, and you will have to prove me wrong in order to get the flag.")def part3():    real_flag = flag[5:][:-1]    if (SHOULD_USE_ANNOYING_ANIMATIONS):        for i in ChargingBar("Loading flags", max=64, check_tty=False).iter(range(64)):            sleep(0.1)    flags = ["".join(x) for x in itertools.permutations(real_flag)]    flags.remove(real_flag)    random.shuffle(flags)    brrr_the_strings(flags)    print("If you are so smart, then you should be able to give the flag in 15 seconds!")    start = time()    guess = input("> ")    end = time()    if guess == flag and end - start <= 15:        print("Correct! Here is your flag: " + flag)    else:        print("Well, at least I can rest. GL")        exit(0)
import hashlibimport secretsclass NcPowser:def __init__(self, difficulty=20, prefix_length=17):self.difficulty = difficultyself.prefix_length = prefix_lengthdef get_challenge(self):prefix = secrets.token_hex(self.prefix_length)rest = secrets.token_hex(self.difficulty - self.prefix_length)return prefix, restdef pow(self):prefix, rest = self.get_challenge()print(f"sha256(\"{prefix} + {"?"*(len(rest))}\") == \"{hashlib.sha256((prefix + rest).encode()).hexdigest()}\"")answer = input("> ")if hashlib.sha256((prefix + answer).encode()).hexdigest() == hashlib.sha256((prefix + rest).encode()).hexdigest():return Trueelse:return Falsedef solve_pow(self, prefix, result, unknown_count):from itertools import productpossibilities = product("0123456789abcdef", repeat=unknown_count)for ans in possibilities:answer = "".join(ans)if hashlib.sha256((prefix + answer).encode()).hexdigest() == result:return answerif __name__ == "__main__":print("Solving PoW...")nc = NcPowser()# sha256("dd32ded3ce6a9c864b5b2a0c364003b409 + ??????") == "e46f470c74eff9629dd828c0bfada1ff87bbeede19cdcd3fbcac8684a07b1384"prefix = "dd32ded3ce6a9c864b5b2a0c364003b409"result = "e46f470c74eff9629dd828c0bfada1ff87bbeede19cdcd3fbcac8684a07b1384"unknown_count = 6solution = nc.solve_pow(prefix, result, unknown_count)print(f"Solution: {solution}")exit(0)

审计代码:

main.py

def main():    part1_()    input("Press enter to continue...")    for i in range(2, 18):        part1(i)    part2_()    input("Press enter to continue...")    for i in range(2, 6):        part2(6)    part3_()    input("Press enter to continue...")    part3()

显然是按照顺序进行闯关,不过这里要注意循环中读进去的参数(对后面很重要)。

common.py

intro_dictionary = "AB"mid_dictionary = "ABCD"

这里给出的是后面两个 \(part\) 用来生成字符串的字典。

pow.py

sha256("dd32ded3ce6a9c864b5b2a0c364003b409 + ??????") =="e46f470c74eff9629dd828c0bfada1ff87bbeede19cdcd3fbcac8684a07b1384"prefix = "dd32ded3ce6a9c864b5b2a0c364003b409"

这里就可以看出,第一步是sha256的爆破,6位的掩码爆破,差不多要跑十分钟(后面错一次就要跑这一次出题人坏b)

EXP

p = remote("guess_what.ctf.knping.pl",20000)str=p.recvuntil(b"> ")key=str[8:-84].decode()h=str[58:-4].decode()print(key)print(h)for i in range(16777216):    tmp = key + hex(i).zfill(6)[2:8]    if hashlib.sha256(tmp.encode("utf-8")).hexdigest() == h:        print(tmp)        breakprint(tmp[-6:])p.sendline(tmp[-6:])p.recvuntil(b"Press enter to continue...")p.sendline()

part1.py

strings = ["".join(x) for x in itertools.product(intro_dictionary, repeat=l)]

这里是字符串的生成规则,\(itertools.product\) 指的是对字典里的字符串进行排列组合取数,取数规则就是 \(repert\)。

removedString = strings[indexToRemove]strings.remove(removedString)random.shuffle(strings)

然后将其获取到的字符串数组打乱,随机删去一个数,然后猜删了什么。

EXP

EXP在分析完题目后就非常好写了,只需要讲排列组合列出来对生成的字符串进行比对然后 \(sendline\) 即可。

for l in range(2,18):    p.recvuntil(b"PRINTING...\n")    keyword=p.recvuntil(b"DONE PRINTING\n",drop=True)    keyword=keyword[:-1].decode()    keywordlist=keyword.split("\n")    #print(keyword)    #print(keywordlist)    for e in itertools.product("AB", repeat=len(keywordlist[0])):        s = "".join(e)        if s not in keywordlist:            p.sendline(s)p.recvuntil(b"Press enter to continue...")p.sendline()

对输出字符串进行切割利用 split("\n")进行转为数组(群里大佬教的,长见识了),然后根据题意利用循坏改变 \(repert\) 的值一层一层爆破即可

part2.py

strings = ["".join(x) for x in itertools.product(mid_dictionary, repeat=l)]

思路和 \(part1\) 同理,惟独改变的是字典

EXP

for l in range(2,6):    p.recvuntil(b"PRINTING...\n")    keyword=p.recvuntil(b"DONE PRINTING\n",drop=True)    keyword=keyword[:-1].decode()    keywordlist=keyword.split("\n")    #print(keyword)    #print(keywordlist)    for e in itertools.product("ABCD", repeat=len(keywordlist[0])):        s = "".join(e)        if s not in keywordlist:            p.sendline(s)p.recvuntil(b"Press enter to continue...")p.sendline()

part3.py

assert(len(flag) == 2**(2**2))real_flag = flag[5:][:-1]flags = ["".join(x) for x in itertools.permutations(real_flag)]flags.remove(real_flag)random.shuffle(flags)brrr_the_strings(flags)if guess == flag and end- start <= 15:

以上是关键代码,意思分别是告诉 \(flag\) 为 \(16\) 位字符串,同时在交互时只保留中心 \(flag\),然后对其进行全排列、乱序、删数,然后必须在15秒内猜到。

EXP

看到全排列就知道有 \(10^{10}\) 个可能性,而且 \(15s\) 的限制,不得不对代码进行优化,因此考虑现生成完整的全排列数列,然后利用 \(sort()\) 同时对两个数组进行排序,然后一一比对遇到不一样的既找到答案,如果不这样干的话将会最多比对 \(C_{10^{10}}^{2}\) 次,时间必然会超时。

p.recvuntil(b"PRINTING...\n")keyword=p.recvuntil(b"DONE PRINTING\n",drop=True)keyword=keyword[:-1].decode()keywordlist=keyword.split("\n")flags = ["".join(x) for x in itertools.permutations("i8a9eF2d4n")]keywordlist.sort()flags.sort()p.recvuntil(b"> \n")for i in range(0,10**10):    if keywordlist[i] != flags[i]:        p.sendline("ping{"+flags[i]+"}")        breakp.interactive()

完整EXP

import itertoolsfrom pwn import *import hashlibcontext.log_level="debug"p = remote("guess_what.ctf.knping.pl",20000)str=p.recvuntil(b"> ")key=str[8:-84].decode()h=str[58:-4].decode()print(key)print(h)for i in range(16777216):    tmp = key + hex(i).zfill(6)[2:8]    if hashlib.sha256(tmp.encode("utf-8")).hexdigest() == h:        print(tmp)        breakprint(tmp[-6:])p.sendline(tmp[-6:])p.recvuntil(b"Press enter to continue...")p.sendline()#part1for l in range(2,18):    p.recvuntil(b"PRINTING...\n")    keyword=p.recvuntil(b"DONE PRINTING\n",drop=True)    keyword=keyword[:-1].decode()    keywordlist=keyword.split("\n")    #print(keyword)    #print(keywordlist)    for e in itertools.product("AB", repeat=len(keywordlist[0])):        s = "".join(e)        if s not in keywordlist:            p.sendline(s)p.recvuntil(b"Press enter to continue...")p.sendline()#part2for l in range(2,6):    p.recvuntil(b"PRINTING...\n")    keyword=p.recvuntil(b"DONE PRINTING\n",drop=True)    keyword=keyword[:-1].decode()    keywordlist=keyword.split("\n")    #print(keyword)    #print(keywordlist)    for e in itertools.product("ABCD", repeat=len(keywordlist[0])):        s = "".join(e)        if s not in keywordlist:            p.sendline(s)p.recvuntil(b"Press enter to continue...")p.sendline()#getflagp.recvuntil(b"PRINTING...\n")keyword=p.recvuntil(b"DONE PRINTING\n",drop=True)keyword=keyword[:-1].decode()keywordlist=keyword.split("\n")flags = ["".join(x) for x in itertools.permutations("i8a9eF2d4n")]keywordlist.sort()flags.sort()p.recvuntil(b"> \n")for i in range(0,10**10):    if keywordlist[i] != flags[i]:        p.sendline("ping{"+flags[i]+"}")        breakp.interactive()

结语

以上便是本题的所有内容了,总体来说不是很难,主要考验代码审计能力和优化能力,是挺有意思的一道题~

感谢大家阅读,求求点点关注哦~

会不断更新各个比赛的wp、题目分享、出题笔记、学习笔记等。

碎碎念

阳了真的真的真的真的好难受呜呜呜呜呜呜。。。。。。咩。。。。。。。。。

关键词: 代码审计 输出字符串 进行排列