wp参考:
2024 ByteCTF wp
2024 ByteCTF WP- Nepnep
ByteCTF 2024 writeup by Arr3stY0u
五冠王!ByteCTF 2024 初赛WriteUp By W&M
ByteCTF 2024 By W&M - W&M Team
ByteCTF Re WP - 吾爱破解 - 52pojie.cn
2024 ByteCTF - BediveRe_RE - 博客园 (cnblogs.com)
Rev
babyapk
DIE扫出来是一个flutter架构的apk,考虑用blutter来反编译
我之前在ubuntu上配置成功过,后来因为虚拟机坏了全删了。重新配置时kali、ubuntu
学习孤恒师傅在win上又装了一遍,参考下面的文章
[原创]《安卓逆向这档事》番外实战篇3-拨云见日之浅谈Flutter逆向-Android安全-看雪-安全社区|安全招聘|kanxue.com
出现上面的报错就是网络问题(全局代理)
cmake相关的报错就是缺少环境,一定要检查VS这个模块有没有安装
成功之后就快乐启动
得到输出
根据教程利用里面的文件可以恢复符号表,把addName的脚本跑一遍(我跑一遍之后符号表还是缺,结果又跑了一遍一样的脚本就好了,匪夷所思)
搜一下apk名字段可以找到一些相关的函数
babyapk_main__MyHomePageState::test_264c0c里藏着大量逻辑,不过需要修复一下函数结构。对识别成数据的部分,我们采取“先U再C再P”的原则,对代码反复调教,最终可以恢复出能看的伪代码
看到一些字符串匹配的函数名,猜测是对字符串开头的检测;35行45应该是字符串长度
我们用frida hook看看情况,一般的安卓模拟器跑不起来,没有arm的底层,推荐直接上真机
blutter提供了非常方便的hook脚本,只要改个地址就能hook
手机端启动frida-server
frida -U -f com.example.babyapk -l E:\Downloads\CTF\ByteCTF2024\babyapk_output\blutter_frida.js
注入后触发
可以看到是一个flag头检测
0x198d18的函数跑不出东西,0x198df8可以跑出上面的flag头
hook 264d88 则可以跑出flag尾部验证
所以flutter层只是个表面,对flag格式进行验证,加密部分还在rust部分
m3N4B5V6在librust_lib_babyapk.so里面有字符串
定位到这个函数里
这个函数里还有greet字段,wm的wp解释了这一点
sub_39B24里面翻,最终找到3AEE0看着像加密函数
这里的连接符验证,应该为flag的格式
最后的验证部分,一堆方程式,又是z3
from z3 import *
data = [0x1EE59, 0x22A, 0x1415, 0x40714, 0x13E0, 0x8B8, 0xFFFDCEA0, 0x313B,
0x3D798, 0xFFFFFE6B, 0xC4E, 0x23884, 0x8D, 0x1DB4, 0xFFFC1328, 0x1EAC,
0x43C64, 0x142B, 0xFFFFF622, 0x23941, 0xFFFFEF6D, 0x120C, 0xFFFBD30F,
0x1EBE, 0x45158, 0xFFFFEF66, 0x1D3F, 0x4C46B, 0xFFFFF97A, 0x1BFD,
0xFFFBA235, 0x1ED2
]
for i in range(4):
s = Solver()
v46, v47, v45, v44, v48, v49, v50, v51 = BitVecs("v46 v47 v45 v44 v48 v49 v50 v51", 8)
s.add(And(48 <= v46, v46 <= 127))
s.add(And(48 <= v47, v47 <= 127))
s.add(And(48 <= v45, v45 <= 127))
s.add(And(48 <= v44, v44 <= 127))
s.add(And(48 <= v48, v48 <= 127))
s.add(And(48 <= v49, v49 <= 127))
s.add(And(48 <= v50, v50 <= 127))
s.add(And(48 <= v51, v51 <= 127))
s.add((v51 + v47 * v44 * v49 - (v46 + v50 + v45 * v48)) & 0xffffffff ==data[i * 8])
s.add((v44 - v48 - v46 * v49 + v51 * v47 + v45 + v50) & 0xffffffff ==data[i * 8 + 1])
s.add((v46 * v49 - (v48 + v51 * v47) + v45 + v50 * v44) & 0xffffffff ==data[i * 8 + 2])
s.add((v47 + v48 * v46 - (v51 + v45) + v50 * v49 * v44) & 0xffffffff ==data[i * 8 + 3])
s.add((v49 * v44 + v47 + v45 * v48 - (v50 + v51 * v46)) & 0xffffffff ==data[i * 8 + 4])
s.add((v46 * v49 + v47 * v44 + v45 - (v50 + v48 * v51)) & 0xffffffff ==data[i * 8 + 5])
s.add((v51 - v47 + v45 * v49 + v50 - v48 * v46 * v44) & 0xffffffff ==data[i * 8 + 6])
s.add((v44 - v51 - (v47 + v49) + v48 * v46 + v50 * v45) & 0xffffffff ==data[i * 8 + 7])
if s.check()==sat:
print(s.model()[v46], end=",")
print(s.model()[v47], end=",")
print(s.model()[v45], end=",")
print(s.model()[v44], end=",")
print(s.model()[v48], end=",")
print(s.model()[v49], end=",")
print(s.model()[v50], end=",")
print(s.model()[v51], end=",")
print()
byte = [51,50,101,55,53,48,99,56,102,98,50,49,52,53,54,50,97,102,50,50,57,55,51,102,98,53,49,55,54,98,57,99]
flag = ''
j = 0
for i in byte:
flag += chr(i)
print(flag)
v45-v51八个变量,每个变量4字节,(4*8=32)+4(四个“-”)+9(“ByteCTF{}”)=45,可以推出四个连接符,但不知道加在哪儿
看了各个wp,可以通过经验猜测是GUID格式
GUID 的格式为“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”,其中每个 x 是 0-9 或 a-f 范围内的一个十六进制数。例如:6F9619FF-8B86-D011-B42D-00C04FC964FF 即为有效的 GUID 值。
硬抠伪代码勉强能看出来一点,但是不太好看。不知有什么好方法
又研究了52的wp,感觉写的很好,提供了更多的思路
我们要充分利用blutter提供的资源
打开asm\babyapk\main.dart
其实可以通过符号名猜测到很多逻辑
也是我们之前hook出来的数据
看到重要的m3N4B5V6函数,可以进行进一步定位
不过也没太多信息
不过IDA是能搜到simple的字符串的
顺着交叉引用就能找到加密函数位置
而且既然知道了加密函数,z3解完了不知道“-”加在哪里,也可以直接爆破其位置
#参考了rea1师傅的脚本!
from itertools import combinations
# 原始字符串
original_string = "32e750c8fb214562af22973fb5176b9c"
# 定义用于验证的 byte 数组(byte_18E46)
byte_18E46 = [ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
# 验证逻辑函数
def validate_hyphen_positions(input_str):
byte = byte_18E46
input_bytes = [ord(c) for c in input_str]
# 模拟你提供的验证逻辑
v2 = byte[input_bytes[0]]
if v2 == 36:
return False
v3 = byte[input_bytes[v2]] + v2
if v3 == 36:
return False
v4 = v3 + byte[input_bytes[v3]]
if v4 == 36:
return False
v5 = v4 + byte[input_bytes[v4]]
if v5 == 36:
return False
v6 = v5 + byte[input_bytes[v5]]
if v6 == 36:
return False
v7 = v6 + byte[input_bytes[v6]]
if v7 == 36:
return False
v8 = v7 + byte[input_bytes[v7]]
if v8 == 36:
return False
v9 = v8 + byte[input_bytes[v8]]
if v9 == 36:
return False
v10 = input_bytes[v9]
if v10 != ord('-'):
return False
v12 = v9 + byte[input_bytes[v9]]
if v12 == 36:
return False
v13 = v12 + byte[input_bytes[v12]]
if v13 == 36:
return False
v14 = v13 + byte[input_bytes[v13]]
if v14 == 36:
return False
v15 = v14 + byte[input_bytes[v14]]
if v15 == 36:
return False
v16 = v15 + byte[input_bytes[v15]]
if v16 == 36:
return False
v18 = input_bytes[v16]
if v18 != ord('-'):
return False
v20 = v16 + byte[input_bytes[v16]]
if v20 == 36:
return False
v21 = v20 + byte[input_bytes[v20]]
if v21 == 36:
return False
v22 = v21 + byte[input_bytes[v21]]
if v22 == 36:
return False
v23 = v22 + byte[input_bytes[v22]]
if v23 == 36:
return False
v24 = v23 + byte[input_bytes[v23]]
if v24 == 36:
return False
v25 = input_bytes[v24]
if v25 != ord('-'):
return False
v27 = v24 + byte[input_bytes[v24]]
if v27 == 36:
return False
v28 = v27 + byte[input_bytes[v27]]
if v28 == 36:
return False
v29 = v28 + byte[input_bytes[v28]]
if v29 == 36:
return False
v30 = v29 + byte[input_bytes[v29]]
if v30 == 36:
return False
v31 = v30 + byte[input_bytes[v30]]
if v31 == 36:
return False
v32 = input_bytes[v31]
if v32 != ord('-'):
return False
return True
# 生成插入 '-' 的位置的组合
positions = list(combinations(range(len(original_string) + 1), 4)) # 选择 4 个插入位置
# 计数器
count = 0
valid_count = 0 # 成功验证的组合计数器
# 遍历所有组合
for pos in positions:
temp_str = original_string
# 插入时要注意位置的偏移,每次插入后,字符串长度增加
for i, p in enumerate(pos):
temp_str = temp_str[:p + i] + '-' + temp_str[p + i:] # 插入 '-' 并调整位置索引
# 验证插入的 '-' 是否符合条件
if validate_hyphen_positions(temp_str):
print(f"Valid combination: {temp_str}")
valid_count += 1 # 计数通过验证的组合
# 计数
count += 1
# 打印总次数
print(f"Total combinations: {count}")
print(f"Total valid combinations: {valid_count}")
'''
Valid combination: 32e750c8-fb21-4562-af22-973fb5176b9c
Total combinations: 40920
Total valid combinations: 1
'''
ByteBuffer
010看到一堆点和边的字样,猜测是要画图
由于只给了个bin文件,没有太多的办法,只能对着结构硬猜,其实仔细观察是能发现一些端倪的
首先是Edge的部分
通过这几个字符的特征应该可以猜测,每个Edge #?字段的结构体数据应该是存在字符串前面的
用简单的脚本提取一下,可以发现,除了头尾的数据有点怪,中间部分的数据段都是很有规律的
结构体前部是一些很大的数字,01后面是两个四字节的整数,再往后是四字节的4,和四字节的9或者8
每个部分的含义看起来都不明所以,除了可以很勉强地看出最后一个数据段是字符串部分的长度
那先往下看一下dot的部分
结构也类似,不过既然是点,就可以猜测第2、3个四字节部分是x,y坐标。末尾的数据同上是字符串标识的长度
我们可以发现dot一共有120(0x78)个,而edge中这两个字段的值都不大于0x78
所以edge中这两个字段表示的是这条边两个端点的编号
至于前面的大数不知道什么意思,我们也不管了,上面那些数据足够我们画图
读取结构体数据的脚本如下,一些细节做了注释
# 参考了wm大佬的脚本,非常佩服wm的师傅!
import struct
import matplotlib.pyplot as plt
def u32(a):
return struct.unpack('<I', a)[0] # 将一个字节序列解包为一个32位无符号整数(小端序)
with open(R"E:\Downloads\CTF\ByteCTF2024\ByteBuffer\ByteBuffer.bin", 'rb') as fp:
buffer = fp.read()
begin_offset = 0x3AC
addr = begin_offset
edges = []
while addr <= 0x1203: #只读取Edge数据段倒数直到第二个结构体的内容
#print(hex(addr))
bts = bytes() # 创建空字节对象一枚
id_length = u32(buffer[addr + 0x14:addr + 0x18]) #读取字符串长度部分
#print(id_length)
#不同的字符串长度 数据段末尾的空格长度都不一样
if id_length == 9: #id段字符串长度为9时,数据末尾会空3个字节,其他的以此类推
offset_in = 3
elif id_length == 8:
offset_in = 4
elif id_length == 7:
offset_in = 1
bts += buffer[addr:addr + 0x14 + 4 + id_length + offset_in] #把一整段结构体保存为一个字节对象
addr += 24 + id_length + offset_in #地址指针移动一个结构体的长度,到下一个结构体
edges.append(bts)
bts = buffer[addr + 12:addr + 44] #特殊处理最后一个结构体,因为它的长度和前面的不一样
edges.append(bts)
addr += 0x2C
'''
for i in edges:
print(len(i),i)
'''
#print(hex(addr))
dots = []
while addr <= 0x1f85: #读取到Dot数据段倒数第二个结构体
#print(hex(addr))
bts = bytes()
bts += buffer[addr:addr + 16]
id_length = u32(buffer[addr + 16:addr + 20])
#print(id_length)
if id_length == 8:
offset_in = 4
elif id_length == 7:
offset_in = 1
elif id_length == 6:
offset_in = 2
bts += buffer[addr + 0x10:addr + 0x10 + 4 + id_length + offset_in]
addr += 0x14 + id_length + offset_in
dots.append(bts)
#print(hex(addr))
bts = buffer[addr + 8:addr + 34] #最后一个结构体特殊处理
dots.append(bts)
'''
for i in dots:
print(len(i),i)
'''
points = []
for i in dots:
x_d = u32(i[4:8])
y_d = u32(i[8:12])
points.append((x_d, y_d)) #读取dot数据中的坐标值
id3 = u32(i[12:16]) #结构体后面部分,其实没啥用,因为看不懂什么意思
#print(x_d, y_d, id3, i[-8:])
points = points[:-2] + [points[-1], points[-2]] #读取的dot最后两个点数据弄反了,手动调整一下
#print(len(points))
for i in edges:
p1 = u32(i[8:12])
p2 = u32(i[12:16])
print(p1, p2, id3, i[-8:]) #读取edge数据中每条边两个端点的编号
p2p = [120-p1, 120-p2] #points列表中的数据是倒着存进去的,检索索引值时索引值也是反的
x_coords = [points[i][0] for i in p2p] # 检索x坐标
y_coords = [points[i][1] for i in p2p] # 检索y坐标
plt.plot(x_coords, y_coords, '-o')
all_x_coords, all_y_coords = zip(*points)
plt.scatter(all_x_coords, all_y_coords) #绘制
plt.gca().invert_yaxis() #翻转y轴
plt.show()
ByteKit
给了这些玩意儿
sh文件是启动qemu镜像的脚本,qcow2是镜像
bios.bin大概是固件
qemu启动脚本,打开一个虚拟机,用root可以登陆
有个getflag.sh脚本
是一个flag验证,似乎目标是$BYTECTF_OUTPUT_VAR_FILE
也就是"/sys/firmware/efi/efivars/ByteCTFOut-93e91ed6-1a7a-46a1-b880-c5c281700ea2"
binwalk看一下里面是有efi文件的,不过GUID不是我们想要的,先试试
从bios.bin中提取文件有两种方法
- 7zip直接解压
然后可以找到Bytekit字样的可疑文件
- 使用uefi_retool工具
python uefi_retool.py get-images bin路径
有ByteCTFIn、KEY:的字符串,不过函数部分被ollvm混淆了
D810可以大致去掉混淆
然后去看ModuleEntryPoint函数
有几个可疑的点
65行的第二个地址,是一长串十六进制,应该是一个文件
83行出现了11位循环异或
key的地址看起来有点怪
观察加密的文件
出现了微妙的对应,应该是异或的结果。所以得到异或的密钥实际上是这一部分(2CE83EE0BC1A0956B9D994)
这个位置之后都是填充的BD,没啥用。脚本把文件解密导出来
异或后头部也出现了MZ的PE头
#参考wm佬的
from idaapi import *
startaddr=0x3A37
xoraddr=0x3A2C
size=0x6c4 #实际上密钥前面那个数表示的就是文件大小
for i in range(size):
patch_byte(startaddr+i,get_byte(startaddr+i)^get_byte(xoraddr+i%11))
fp=open("E:\Downloads\CTF\ByteCTF2024\ByteKit\output","wb+")
fp.write(get_bytes(startaddr,size))
fp.close()
导出的文件再次拖入IDA
一个开头“KEY:”的验证
下面是异或加密和验证flag
异或相当于流密钥加密,解密只要把加密过程再实现一遍即可
enc = [0x4B, 0x27, 0x42, 0x55, 0x48, 0x6E, 0x41, 0x29, 0x1F, 0x5E, 0x04, 0x04, 0x6B, 0x3E, 0x57, 0x5F, 0x08, 0x07, 0x5F, 0x3A, 0x31, 0x17, 0x40, 0x30, 0x5F, 0x7A, 0x75, 0x67, 0x36, 0x36, 0x36, 0x36]
key = [0x0000000000000062, 0x0000000000000001, 0x000000000000000B, 0x0000000000000079,
0x0000000000000002, 0x0000000000000003, 0x0000000000000074, 0x0000000000000003,
0x0000000000000007, 0x0000000000000065, 0x0000000000000004, 0x000000000000000E,
0x0000000000000064, 0x0000000000000005, 0x000000000000000D, 0x0000000000000061,
0x0000000000000006, 0x000000000000000A, 0x000000000000006E, 0x0000000000000007,
0x000000000000000F, 0x0000000000000063, 0x0000000000000008, 0x000000000000000C,
0x0000000000000065, 0x0000000000000009, 0x000000000000000A, 0x0000000000000000]
for i in range(len(key)//3):
v14 = key[i*3+1]
v15 = v14 + key[i*3+2]
while(v15 > v14):
enc[v14] ^= key[i*3]
v14 += 1
flag = ''
for i in enc:
flag += chr(i)
print(flag)
# KEY:By71d@nnc6_Wan77_y@0_zug6666
输回去就可以得到flag,但是识别有点问题(前面是notepad里一起粘贴进来的,后面是手打./getflag.sh 然后从python复制的key粘贴的,后面的识别不出来,纯手打也识别不出来),不知为啥
Mobile
极限逃脱
直接分析IPA,选择里面的ByteCTFDemo部分
有不少符号表
看到ViewController firsButtonClicked
void __cdecl -[ViewController firsButtonClicked:](ViewController *self, SEL a2, id a3)
{
uint32_t rand; // w20
UITextField *v5; // x21
NSString *v6; // x22
void *input; // x23
UIAlertController *v8; // x20
UIAlertAction *v9; // x21
UITextField *v10; // x22
UILabel *v11; // x22
UIButton *v12; // x19
rand = arc4random_uniform(0x1F4u);
v5 = objc_retainAutoreleasedReturnValue(-[ViewController firstInput](self, "firstInput"));
v6 = objc_retainAutoreleasedReturnValue(-[UITextField text](v5, "text"));
input = -[NSString integerValue](v6, "integerValue");
objc_release(v6);
objc_release(v5);
if ( input == rand )
{
v8 = objc_retainAutoreleasedReturnValue(
+[UIAlertController alertControllerWithTitle:message:preferredStyle:](
&OBJC_CLASS___UIAlertController,
"alertControllerWithTitle:message:preferredStyle:",
CFSTR("提示"),
CFSTR("恭喜你拿到入场券"),
1LL));
v9 = objc_retainAutoreleasedReturnValue(
+[UIAlertAction actionWithTitle:style:handler:](
&OBJC_CLASS___UIAlertAction,
"actionWithTitle:style:handler:",
CFSTR("确定"),
0LL,
&__block_literal_global_58));
-[UIAlertController addAction:](v8, "addAction:", v9);
-[ViewController presentViewController:animated:completion:](
self,
"presentViewController:animated:completion:",
v8,
1LL,
0LL);
v10 = objc_retainAutoreleasedReturnValue(-[ViewController inputText](self, "inputText"));
-[UITextField setHidden:](v10, "setHidden:", 0LL);
objc_release(v10);
v11 = objc_retainAutoreleasedReturnValue(-[ViewController noticeLabel](self, "noticeLabel"));
-[UILabel setHidden:](v11, "setHidden:", 0LL);
objc_release(v11);
v12 = objc_retainAutoreleasedReturnValue(-[ViewController secondButton](self, "secondButton"));
-[UIButton setHidden:](v12, "setHidden:", 0LL);
objc_release(v12);
}
else
{
v8 = objc_retainAutoreleasedReturnValue(
+[UIAlertController alertControllerWithTitle:message:preferredStyle:](
&OBJC_CLASS___UIAlertController,
"alertControllerWithTitle:message:preferredStyle:",
CFSTR("提示"),
CFSTR("咒语错误"),
1LL));
v9 = objc_retainAutoreleasedReturnValue(
+[UIAlertAction actionWithTitle:style:handler:](
&OBJC_CLASS___UIAlertAction,
"actionWithTitle:style:handler:",
CFSTR("确定"),
0LL,
&__block_literal_global_53));
-[UIAlertController addAction:](v8, "addAction:", v9);
-[ViewController presentViewController:animated:completion:](
self,
"presentViewController:animated:completion:",
v8,
1LL,
0LL);
NSLog(&stru_10000C408.isa);
}
objc_release(v9);
objc_release(v8);
}
是要过一个随机数啥的
再看看ViewController secondButtonClicked
(推荐使用ida9,反编译效果好)
这里有个正则,暗示了flag格式也是uuid的
flag格式验证
初始化一些数据,很恶心的一点是第一部分的数据用的实际上是fifth的数据,first部分的数据根本没用
接下来是加密部分
先是用初始化的数据拼了一个flag格式的字符串,然后算了个SHA256
之后每个部分分段进行替换
CC_SHA256(v14, v15, md);
v16 = objc_retainAutoreleasedReturnValue(+[NSMutableString string](&OBJC_CLASS___NSMutableString, "string"));
for ( i = 0LL; i != 32; ++i )
objc_msgSend(v16, "appendFormat:", CFSTR("%02x"), md[i]);
v18 = objc_retainAutoreleasedReturnValue(objc_msgSend(v16, "substringWithRange:", 1LL, objc_msgSend(f1, "length")));
v19 = objc_retainAutoreleasedReturnValue(objc_msgSend(v18, "stringByReplacingOccurrencesOfString:withString:", CFSTR("a"), CFSTR("b")));
objc_release(v18);
v20 = objc_retainAutoreleasedReturnValue(objc_msgSend(v16, "substringWithRange:", objc_msgSend(f1, "length") + 1, objc_msgSend(f2_, "length")));
v38 = objc_retainAutoreleasedReturnValue(objc_msgSend(v20, "stringByReplacingOccurrencesOfString:withString:", CFSTR("b"), CFSTR("c")));
objc_release(v20);
v21 = objc_retainAutoreleasedReturnValue(objc_msgSend(v16, "substringWithRange:", objc_msgSend(f2_, "length") + 1, objc_msgSend(f3, "length")));
v43 = objc_retainAutoreleasedReturnValue(objc_msgSend(v21, "stringByReplacingOccurrencesOfString:withString:", CFSTR("c"), CFSTR("d")));
objc_release(v21);
v22 = objc_retainAutoreleasedReturnValue(objc_msgSend(v16, "substringWithRange:", objc_msgSend(f3, "length") + 1, objc_msgSend(f4, "length")));
v42 = objc_retainAutoreleasedReturnValue(objc_msgSend(v22, "stringByReplacingOccurrencesOfString:withString:", CFSTR("d"), CFSTR("e")));
objc_release(v22);
v23 = objc_retainAutoreleasedReturnValue(objc_msgSend(v16, "substringWithRange:", objc_msgSend(f4, "length") + 1, objc_msgSend(f5, "length")));
v24 = objc_retainAutoreleasedReturnValue(objc_msgSend(v23, "stringByReplacingOccurrencesOfString:withString:", CFSTR("e"), CFSTR("f")));
objc_release(v23);
v25 = objc_retainAutoreleasedReturnValue(-[NSArray objectAtIndexedSubscript:](v10, "objectAtIndexedSubscript:", 0LL));
6c9838a3c6810bdb2633ed5910b8547c09a7a4c08bf69ae3a95c5c37f9e8f57e
enc = 'a67be199da4b-b092-bd3e-e777-a67be199da4b'
hash = '6c9838a3c6810bdb2633ed5910b8547c09a7a4c08bf69ae3a95c5c37f9e8f57e'
p1 = ''
for i in range(1,9):
p1 += hash[i]
p1 = p1.replace('a', 'b')
print(p1)
p2 = ''
for i in range(9,13):
p2 += hash[i]
p2 = p2.replace('b', 'c')
print(p2)
p3 = ''
for i in range(5,9):
p3 += hash[i]
p3 = p3.replace('c', 'd')
print(p3)
p4 = ''
for i in range(5,9):
p4 += hash[i]
p4 = p4.replace('d', 'e')
print(p4)
p5 = ''
for i in range(5,17):
p5 += hash[i]
p5 = p5.replace('e', 'f')
print(p5)
flag = 'ByteCTF{' + p1 + '-' + p2 + '-' + p3 + '-' + p4 + '-' + p5 + '}'
print(flag)
# ByteCTF{c9838b3c-6810-8a3d-8a3c-8a3c6810bdb2}