# Bronco CTF Write Up
[TOC]
# Beginner
# Break the Battalion
这道题我们会拿到一份 ELF 文件,我们用 IDA 打开它会看到
可以发现,这个程序的核心内容是 encrypt,所以我们查看一下它的内容:
写一段 python 便可以简单得知输入什么内容最后会输出 “brigade”:
def decrypt(encrypted): | |
return ''.join(chr(ord(c) ^ 0x50) for c in encrypted) | |
encrypted = "brigade" | |
original_input = decrypt(encrypted) | |
print(f"Original input: {original_input}") | |
# Original input: 2"97145 |
所以 flag 为:
bronco{2"97145}
(吐槽一下,这个 flag 的内容真的非常奇怪,一般都是会带点正常单词的。)
# Simon Says
这道题我们会拿到这样一张图片:
并没有任何有用的内容。根据题目描述我们猜测这道题用了 LSB 隐写,所以用 Stegsolve 打开图片便可得到:
flag 为:
bronco{simon_says_submit_this_flag}
# Too Many Emojis
这道题我们会得到一串 emoji 内容:
因为知道 flag 的格式为 bronco {},所以可以确定这个应该是单表加密,并且知道前 6 个 emoji 对应的明文。
经过一系列搜索与排查可以发现每一个 emoji 对应的字母为这个 emoji 的官方英文名的首字母,根据这个线索我们可以通过找到的这些信息来解密内容:
(用到的网站:https://unicode.org/emoji/charts/full-emoji-list.html)
这里有一个小技巧:如果找不到想要的 emoji,可以描述给 ChatGPT 并询问其官方名称,再到网站上用名称(或部分名称)搜索,确认是否是我们需要的。
最后得到 flag:
bronco{emojis_express_my_emotions}
# Straight Up Circular
这道题给出的加密字符串如下:
dvlby_otspnr{cobrnot450i1nm_e03}
首先,通过 {}
的位置,我们可以判断这并不是替换加密。其次,我们发现 bronco{}
(该比赛的 flag 统一格式)中的每个字母和符号都出现在了这串字符串中,因此很可能是某种乱序加密。
先确定 b
、 r
、 o
等字母在加密字符串中的具体位置,再根据题目名字(Straight Up Circular)不难发现这个用这个规律可以得到的 flag 开头:
- 从字符串正中间的
b
出发 - 先向右移动 1 位
- 再向左移动 2 位
- 接着向右移动 3 位
- 依此类推……
继续这个流程便可以成功获得 flag:
bronco{tr4n5p0sit1on_my_bel0v3d}
# Crypto
# Across the Tracks
我们会得到一段内容:
Samddre··ath·dhf@_oesoere·ebun·yhot·no··oso·i·a·lr1rcm·iS·aruf·toibadhn·nadpikudynea{l_oeee·ch·oide·f·n·aoe·sae·aonbdhgo_so·rr.i·tYnl·s·tdot·xs·hdtyy'·.t·cfrlca·epeo·iufiyi.t·yaaf·.a.·ts··tn33}i·tvhr·.tooho···rlmwuI·h·e·iHshonppsoleaseecrtudIdet.·n·BtIpdheiorcihr·or·ovl·c··i·acn·t·su··ootr·:b3cesslyedheIath·e·_
根据题目描述我们猜测这段内容使用了栅栏密码,并且 key 为题目描述中提到的 “tenth”(10)。解密即可得到 flag:
bronco{r@1l_f3nc3_cip3rs_r_cool}
# Rahhh-SA
这道题我们会得到以下内容:
e = 65537 | |
n = 3429719 | |
c = [-53102, -3390264, -2864697, -3111409, -2002688, -2864697, -1695722, -1957072, -1821648, -1268305, -3362005, -712024, -1957072, -1821648, -1268305, -732380, -2002688, -967579, -271768, -3390264, -712024, -1821648, -3069724, -732380, -892709, -271768, -732380, -2062187, -271768, -292609, -1599740, -732380, -1268305, -712024, -271768, -1957072, -1821648, -3418677, -732380, -2002688, -1821648, -3069724, -271768, -3390264, -1847282, -2267004, -3362005, -1764589, -293906, -1607693] | |
p = -811 |
首先注意到 c 的所有内容都是负数,但是其绝对值都小于等于 n,所有猜测将其直接放进 进行计算即可。但因为发现 并不是 n 的因数,所以尝试 ,发现结果为整数。
所以写一段 python 代码来尝试 RSA 解码即可:
#!/usr/bin/env python3 | |
e = 65537 | |
n = 3429719 | |
p = 811 # 题中写的是 -811,这里只取绝对值 | |
q = n // p # 4229 | |
# 计算 phi (n) | |
phi = (p - 1) * (q - 1) # (811 - 1)*(4229 - 1) = 810*4228 = 3424680 | |
# 求 d = e^-1 mod phi (n) | |
# Python 3.8+ 可以直接用 pow (e, -1, phi) 得到模逆 | |
d = pow(e, -1, phi) | |
# 给出的负数密文 | |
c_list = [ | |
-53102, -3390264, -2864697, -3111409, -2002688, -2864697, -1695722, -1957072, | |
-1821648, -1268305, -3362005, -712024, -1957072, -1821648, -1268305, -732380, | |
-2002688, -967579, -271768, -3390264, -712024, -1821648, -3069724, -732380, | |
-892709, -271768, -732380, -2062187, -271768, -292609, -1599740, -732380, | |
-1268305, -712024, -271768, -1957072, -1821648, -3418677, -732380, -2002688, | |
-1821648, -3069724, -271768, -3390264, -1847282, -2267004, -3362005, -1764589, | |
-293906, -1607693 | |
] | |
# 解密 | |
plaintext_nums = [] | |
for c in c_list: | |
# 先把负数转为 mod n 内的非负代表元 | |
c_mod = c % n | |
m = pow(c_mod, d, n) | |
plaintext_nums.append(m) | |
message = ''.join(chr(m) for m in plaintext_nums) | |
print("解密后得到的数值:", plaintext_nums) | |
print("尝试映射到字符后的结果:") | |
print(message) | |
# bronco{m4th3m4t1c5_r34l1y_1s_qu1t3_m4g1c4l_raAhH!} |
# Web
# Grandma's Secret Recipe
(因为这份食谱离婚了实在是有点抽象)
点击网站可以看到:
点开 Cookie 可以发现有 2 条内容:
checksum: a223befb6660a23f9c3491f74ef84e43
role: "kitchen helper"
结果检查发现 checksum 为 role 的 md5 结果:
所以我们将 role 改为:"grandma",并且将 checksum 改为 a5d19cdd5fd1a8f664c0ee2b5e293167(=md5 (grandma))。点击 “Grandma's Pantry“便可以看到:
得到 flag:
bronco{grandma-makes-b3tter-cookies-than-girl-scouts-and-i-w1ll-fight-you-over-th@t-fact}
# Reverse
# Reversing for Ophidiophiles
这道题我们会得到以下内容:
23a326c27bee9b40885df97007aa4dbe410e93
flag = input() | |
carry = 0 | |
key = "Awesome!" | |
output = [] | |
for i,c in enumerate(flag): | |
val = ord(c) | |
val += carry | |
val %= 256 | |
val ^= ord(key[i % len(key)]) | |
output.append(val) | |
carry += ord(c) | |
carry %= 256 | |
print(bytes(output).hex()) |
直接用 python 写一段逆向的算法便可以得到 flag:
encrypted_hex = "23a326c27bee9b40885df97007aa4dbe410e93" | |
encrypted_bytes = bytes.fromhex(encrypted_hex) | |
carry = 0 | |
key = "Awesome!" | |
flag = [] | |
for i, val in enumerate(encrypted_bytes): | |
val ^= ord(key[i % len(key)]) # 逆向 XOR 操作 | |
val = (val - carry + 256) % 256 # 逆向 carry 计算 | |
flag.append(chr(val)) | |
carry = (carry + val) % 256 # 重新计算 carry 值 | |
print("".join(flag)) | |
# bronco{charge_away} |
# theflagishere!
这道题我们会得到一份 Python 编译后的字节码文件 “theflagishere.pyc”,我们首先用这个网站将其反汇编:
https://www.lddgo.net/string/pyc-compile-decompile
# Visit https://www.lddgo.net/string/pyc-compile-decompile for more information | |
# Version : Python 3.9 | |
def what_do_i_do(whoKnows): | |
a_st = { } | |
for a in whoKnows: | |
if a_st.get(a) == None: | |
a_st[a] = 1 | |
continue | |
a_st[a] += 1 | |
variable_name = 0 | |
not_a_variable_name = 'None' | |
for a in a_st: | |
if a_st[a] > variable_name: | |
not_a_variable_name = a | |
variable_name = a_st[a] | |
continue | |
return (not_a_variable_name, variable_name) | |
def char_3(): | |
return 'm' | |
def i_definitely_return_the_flag(): | |
def notReal(): | |
def actually_real(): | |
return 'actuallyaflag' | |
return actually_real | |
def realFlag(): | |
return 'xXx___this__is_the__flag___xXx' | |
return (realFlag, notReal) | |
def i_am_a_function_maybe(param): | |
variableName = (param + 102) * 47 | |
for i in range(0, 100): | |
variableName *= i + 1 | |
variableName /= i + 1 | |
newVariable = variableName * i | |
newVariable += 100 | |
return chr(ord(chr(int(variableName) + 1))) | |
def i_do_not_know(): | |
realFlagHere = 'br0nc0s3c_fl4g5_4r3_345y' | |
return 'long_live_long_flags' | |
def unrelated_statement(): | |
return 'eggs_go_great_with_eggs' | |
def i_am_a_function(param): | |
variableName = (param + 102) * 47 | |
for i in range(0, 100): | |
variableName *= i + 1 | |
newVariable = variableName * i | |
newVariable += 100 | |
variableName /= i + 1 | |
return chr(ord(chr(int(variableName)))) | |
def i_return_a_helpful_function(): | |
def i_do_something(char): | |
var = [] | |
for i in range(54, 2000): | |
var.append(ord(char) / 47 - 102) | |
var.reverse() | |
return var.pop() | |
return i_do_something | |
def i_return_the_flag(): | |
return 'thisisdefinitelytheflag!' | |
def i(): | |
return 'free_flag_f' | |
def char_0(): | |
return i_am_a_function_maybe(i_return_a_helpful_function()(what_do_i_do(i_return_the_flag())[0])) | |
def char_1_4_6(): | |
return i_am_a_function_maybe(i_return_a_helpful_function()(what_do_i_do(i_definitely_return_the_flag()[0]())[0])) | |
def char_2_5_9(): | |
return i_am_a_function_maybe(i_return_a_helpful_function()(what_do_i_do(i_definitely_return_the_flag()[1]()())[0])) | |
def char_7(): | |
return i_am_a_function_maybe(i_return_a_helpful_function()(what_do_i_do(interesting()()()()())[0])) | |
def char_8(): | |
return i_am_a_function_maybe(i_return_a_helpful_function()(what_do_i_do(i_do_not_know())[0])) | |
def char_10(): | |
return i_am_a_function_maybe(i_return_a_helpful_function()(what_do_i_do(unrelated_statement())[0])) | |
def interesting(): | |
def notinteresting(): | |
def veryuninteresting(): | |
def interesting_call(): | |
return i | |
return interesting_call | |
return veryuninteresting | |
return notinteresting |
然后用 python 写一段逆向的脚本即可得到 flag (主要内容其实就是复制粘贴):
def what_do_i_do(whoKnows): | |
a_st = {} | |
for a in whoKnows: | |
if a_st.get(a) is None: | |
a_st[a] = 1 | |
continue | |
a_st[a] += 1 | |
variable_name = 0 | |
not_a_variable_name = 'None' | |
for a in a_st: | |
if a_st[a] > variable_name: | |
not_a_variable_name = a | |
variable_name = a_st[a] | |
return (not_a_variable_name, variable_name) | |
def i_definitely_return_the_flag(): | |
def notReal(): | |
def actually_real(): | |
return 'actuallyaflag' | |
return actually_real | |
def realFlag(): | |
return 'xXx___this__is_the__flag___xXx' | |
return (realFlag, notReal) | |
def i_do_not_know(): | |
realFlagHere = 'br0nc0s3c_fl4g5_4r3_345y' | |
return 'long_live_long_flags' | |
def unrelated_statement(): | |
return 'eggs_go_great_with_eggs' | |
def interesting(): | |
def notinteresting(): | |
def veryuninteresting(): | |
def interesting_call(): | |
return i | |
return interesting_call | |
return veryuninteresting | |
return notinteresting | |
def i(): | |
return 'free_flag_f' | |
def i_return_a_helpful_function(): | |
def i_do_something(char): | |
var = [] | |
for i in range(54, 2000): | |
var.append(ord(char) / 47 - 102) | |
var.reverse() | |
return var.pop() | |
return i_do_something | |
def i_am_a_function_maybe(param): | |
variableName = (param + 102) * 47 | |
for i in range(0, 100): | |
variableName *= i + 1 | |
variableName /= i + 1 | |
newVariable = variableName * i | |
newVariable += 100 | |
return chr(ord(chr(int(variableName) + 1))) | |
def char_0(): | |
return i_am_a_function_maybe(i_return_a_helpful_function()(what_do_i_do(i_return_the_flag())[0])) | |
def char_1_4_6(): | |
return i_am_a_function_maybe(i_return_a_helpful_function()(what_do_i_do(i_definitely_return_the_flag()[0]())[0])) | |
def char_2_5_9(): | |
return i_am_a_function_maybe(i_return_a_helpful_function()(what_do_i_do(i_definitely_return_the_flag()[1]()())[0])) | |
def char_3(): | |
return 'm' | |
def char_7(): | |
return i_am_a_function_maybe(i_return_a_helpful_function()(what_do_i_do(interesting()()()()())[0])) | |
def char_8(): | |
return i_am_a_function_maybe(i_return_a_helpful_function()(what_do_i_do(i_do_not_know())[0])) | |
def char_10(): | |
return i_am_a_function_maybe(i_return_a_helpful_function()(what_do_i_do(unrelated_statement())[0])) | |
def i_return_the_flag(): | |
return 'thisisdefinitelytheflag!' | |
# 拼接 flag | |
flag = ( | |
char_0() + | |
char_1_4_6() + | |
char_2_5_9() + | |
char_3() + | |
char_1_4_6() + | |
char_2_5_9() + | |
char_1_4_6() + | |
char_7() + | |
char_8() + | |
char_2_5_9() + | |
char_10() | |
) | |
print("Recovered flag:", flag) | |
# i_am_a_flag | |
# bronco{i_am_a_flag} |
# Forensics
# QR Coded
这道题我们会得到一张二维码:
直接扫描(https://scanqr.org/)会得到一个 fake flag:
用 Stegsolve 打开并调整到 Gray bits 会得到另外一张二维码:
扫描后会得到真正的 flag:
bronco{th1s_0n3_i5}
# Uno
这道题我们会得到这样一张图片:
根据题目描述(”a significant bit of the cards were left on the plane I was on.“)我们猜测这道题用的是 LSB 隐写了 ASCII 码,所以我们用 StegSolve 打开图片,利用其 Data Extract 模块进行查看。这个模块可以查看 RGB 三种颜色的每一个通道,并且按照(自选的)一定的排列顺序显示每个通道的 Hex 和 ASCII 码字符:
最后,根据题目描述中的 “the numbers really speak to me...” 这一句,尝试各种由 2、3、4、5 组成的组合,便可以得到 flag:
bronco{no_un0_y3t}
# Wordlands
我们会得到这张图片:
经过一番尝试后,当用 StegSolve 打开图片,利用其 Data Extract 模块进行查看时可以发现:
8BPS 是标准的 Photoshop 的.psd 文件有固定的文件头,所以我们点击 “Save Bin” 将其存为 wordlands.psd,并用这个网站打开它:
可以发现这里有所有图片创作的信息(图层之类的)。最后根据 line 的图层的顺序进行拼接便可以得到 flag:
比如说 Shape1 这个图层里的线连接了 b 和 r,表示开头为 br
然后是 (b) ro,以此类推...
bronco{i_love_admiring_beautiful_winter_landscapes}
# Misc
# Tick Tock
这道题我们首先会得到这张图片:
经过多次尝试可以在 StegSolve 的 Data Extract 模块里发现有一长串由 “tick” 和 “tock” 组成的内容:
ticktocktocktockticktickticktock ticktocktocktickticktocktocktock ticktocktocktickticktockticktick ticktocktockticktickticktocktock ticktocktocktocktickticktocktick ticktocktocktickticktockticktick ticktocktocktocktockticktocktock ticktocktocktockticktockticktock ticktocktocktocktocktickticktick ticktocktockticktockticktocktock ticktocktocktockticktockticktick ticktockticktocktocktocktocktock ticktocktockticktickticktocktick ticktocktocktocktocktickticktick ticktocktockticktickticktocktock ticktocktockticktockticktocktick ticktocktockticktocktickticktock ticktocktocktockticktockticktick ticktocktockticktocktickticktick ticktockticktocktocktocktocktock ticktocktockticktockticktocktick ticktocktockticktickticktocktock ticktocktockticktocktickticktock ticktocktocktocktocktickticktick ticktocktocktickticktickticktock ticktockticktocktocktocktocktock ticktocktockticktickticktocktick ticktocktocktocktocktickticktick ticktocktocktocktickticktocktock ticktocktockticktickticktocktock ticktocktocktocktocktickticktick ticktocktocktockticktocktocktick ticktocktocktockticktocktocktock ticktocktockticktocktickticktock ticktocktocktocktocktockticktock
写一段 python 将 tick 替换成 0,tock 替换成 1 然后当成二进制内容进行解码会得到:
def ticktock_to_binary(text): | |
return text.replace("tick", "0").replace("tock", "1") | |
text = "ticktocktocktockticktickticktock ticktocktocktickticktocktocktock ticktocktocktickticktockticktick ticktocktockticktickticktocktock ticktocktocktocktickticktocktick ticktocktocktickticktockticktick ticktocktocktocktockticktocktock ticktocktocktockticktockticktock ticktocktocktocktocktickticktick ticktocktockticktockticktocktock ticktocktocktockticktockticktick ticktockticktocktocktocktocktock ticktocktockticktickticktocktick ticktocktocktocktocktickticktick ticktocktockticktickticktocktock ticktocktockticktockticktocktick ticktocktockticktocktickticktock ticktocktocktockticktockticktick ticktocktockticktocktickticktick ticktockticktocktocktocktocktock ticktocktockticktockticktocktick ticktocktockticktickticktocktock ticktocktockticktocktickticktock ticktocktocktocktocktickticktick ticktocktocktickticktickticktock ticktockticktocktocktocktocktock ticktocktockticktickticktocktick ticktocktocktocktocktickticktick ticktocktocktocktickticktocktock ticktocktockticktickticktocktock ticktocktocktocktocktickticktick ticktocktocktockticktocktocktick ticktocktocktockticktocktocktock ticktocktockticktocktickticktock ticktocktocktocktocktockticktock" | |
text = text.replace(" ", "") | |
binary = ticktock_to_binary(text) | |
print(binary) | |
# 01110001 01100111 01100100 01100011 01110010 01100100 01111011 01110101 01111000 01101011 01110100 01011111 01100010 01111000 01100011 01101010 01101001 01110100 01101000 01011111 01101010 01100011 01101001 01111000 01100001 01011111 01100010 01111000 01110011 01100011 01111000 01110110 01110111 01101001 01111101 | |
content = ''.join(chr(int(binary[i:i+8], 2)) for i in range(0, len(binary), 8)) | |
print(content) | |
# qgdcrd{uxkt_bxcjith_jcixa_bxscxvwi} |
最后通过遍历凯撒密码便可以得到 flag:
bronco{five_minutes_until_midnight}