【GDOUCTF 2023】L!S!
收获
对比两个程序中函数的相似性时,
BinDiff
的使用方法遇到未给定或难以确定的数据时,可以通过确定数据的范围,直接进行爆破
学到了一种新的转换小端序的方法
(2023年4月16日)【GDOUCTF 2023】L!S!
思路
解压得到两个文件:
文件名是个提示,ls-original
是原始的文件,ls-patched
是修改后的文件
分别在 64 位 IDA 中打开这两个文件,发现他们的主函数内容几乎是一摸一样的
(这里不贴图了,主函数特别长,有 1100 多行代码)
结合文件的名字,这两个文件可能只有一点细微的差异,其他内容都是一样的
识别二进制文件中的差异,可以使用 IDA 的 BinDiff
插件
首先下载 BinDiff
,详情见本站的《二进制文件相似性》一文 (文章链接不可用,请直接搜索文章名)
识别结果如下,按相似度低到高排序:
发现只有 extract_dirs_from files
这个函数的相似度是 0.84,其他的都是 1
所以突破口肯定就在函数 extract_dirs_from files
中了
跟进一下ls-original
中的函数:
ls-patched
中的函数:
两边同时开对比看一下:
左边多定义了三个变量,继续往下翻,发现差别主要就是在 lmao[]
的地方:
黄色框中是相同的地方,主要是多出了红色框中的内容
将不同的部分提取出来:
LABEL_7:
if ( v9 && !v9[1] )
{
*&lmao[8] = 0x3F7D132A2A252822LL;
*lmao = 0x7D2E370A180F1604LL;
*&lmao[24] = 0x31207C7C381320LL;
*&lmao[16] = 0x392A7F3F39132D13LL;
v18 = lmao;
do
*v18++ ^= **v7;
while ( &lmao[31] != v18 );
puts(lmao);
}
goto LABEL_9;
}
这里 v18
是一个指向 lmao
首地址的指针,而 lmao
的值是由 4 组 8 字节的数据拼接而成(小端序存放)
(注意:拼接得到 lmao 的值时,要先对每一组小端序数据进行还原)
while ( &lmao[31] != v18 )
控制 do while 循环一直将 lmao
中的所有元素全部与 *v7
的值进行异或 ,然后将异或结果输出
但是 *v7
的值在程序中无法得知,所以只能对 *v7
进行爆破
而 *v7
的取值只有 256 种可能,从 0 ~ 255
脚本
解法一
def little_endian(num, width_num): # 将小端序转换为正序
global buffer
hex_str = hex(num) # 将int数据转换为十六进制的字符串
while len(hex_str) != width_num + 2:
hex_str = "0x" + "0" * (width_num - len(hex_str[2:])) + hex_str[2:] # 位数不足width的用0凑齐
index = width_num
while index >= 2:
tmp = int((hex_str[index: index + 2]), 16) # 每两位string转换为十六进制int型数据
buffer.append(tmp) # 将int型作为char存入buffer
index -= 2
return buffer
buffer = [] # 存放结果的列表
lmao1 = 0x7D2E370A180F1604
lmao2 = 0x3F7D132A2A252822
lmao3 = 0x392A7F3F39132D13
lmao4 = 0x31207C7C381320
little_endian(lmao1, 16)
little_endian(lmao2, 16)
little_endian(lmao3, 16)
little_endian(lmao4, 14)
print(buffer)
# [4, 22, 15, 24, 10, 55, 46, 125, 34, 40, 37, 42, 42, 19, 125, 63, 19, 45, 19, 57, 63, 127, 42, 57, 32, 19, 56, 124, 124, 32, 49]
for key in range(256): # 直接爆破key
flag = ""
print("key = ", key)
for k in range(len(buffer)):
tmp = buffer[k] ^ key # 逐个与key异或
flag += chr(tmp) # 对应的字符存入flag
print(flag)
if 'HZCTF' in flag or 'NSSCTF' in flag: # 只输出包含'HZCTF'或'NSSCTF'的结果
break
解法二
发现官方 Writeup 有一种更方便地将一组小端序数据合并成一个正序数据的方法,记录一下
(但也有一个缺点,无法自己随意控制数据的长度,只能统一为相同长度)
import struct
stack_bytes = [
0x7d2e370a180f1604,
0x3f7d132a2a252822,
0x392a7f3f39132d13,
0x31207c7c381320
]
# 将stack_bytes中的数据按照小端字节序打包为二进制数据
xored_bytes = struct.pack("<4Q", *stack_bytes)
# 其中,< 表示小端序,Q代表一个无符号长整型
# 每个无符号长整型整数占8个字节,所以总共打包出来的字符串长度为32个字节
# b'\x04\x16\x0f\x18\n7.}"(%**\x13}?\x13-\x139?\x7f*9 \x138|| 1\x00'
for xorkey in range(256):
output = bytes(byte ^ xorkey for byte in xored_bytes)
if b"HZCTF{" in output:
print(output)
# b'HZCTF{b1ndiff_1s_a_us3ful_t00l}L'
# (因为在lmao4的高位补了一个0,所以多输出了一个L)
结果
NSSCTF{b1ndiff_1s_a_us3ful_t00l}
(最终要求将 HZCTF 改为 NSSCTF)