深入浅出,知无不答,言无不尽,力求详尽
老规矩查壳, Die打开看一眼
无壳, windows可以直接启动
启动之后是这样, 这个游戏是这么玩的, 输入对应的序号就会让那个符号对应的灯亮起来, 同时左右两边的符号也会受影响, 我随便输一下看一下情况就大概明白了。
输入2之后符号变化
输入3之后的变化
把上面这些符号全部点亮就可以胜利了。
规则挺简单的, 按规则来很快就可以通关, 以前上课摸鱼还想过这样的游戏。
按顺序输入12345678就可以了, 输一个数字回车一次。
就出来了....
但是我们也不想在比赛的时候偷偷玩游戏吧, 还是牢样子, 打开ida或者od啥的。
IDA 64位 一切默认
这题我们F5之后一直跳转跳转...发现找不到熟悉的main函数啊, 好多代码要分析, 太难了,
我们可以直接ALT+T, 输入main
搜索是空白的, 记得点开这两个选项(只开其中一个也可以), 找到后双击跳转
再按F5看下伪c代码, 双击main_0跳转
上面这串sub_45A78E就是在屏幕输出字符的函数, 类似print, 对做题没啥用
我们观察下面的代码结构
制作过小游戏的同学应该知道, 一般游戏主体是要放在一个无限循环里的, 如果这是一个动态游戏还会有tick机制...所以开头29行是一个无限循环条件, 35-39行又是输出函数和捕获输入函数, 下面才是输入判断部分.
v5 是我们输入的值(第38行), 如果v5 > 9, 弹出信息, 继续第二个循环, < 9 则跳出第二个循环继续第一个循环。
44-54行做了边界检测, 具体细节很复杂, 不好详细讲, 和做题关系也不大, 大概能明白它是用来捕获异常的就行。(53行report_rangecheck_failure)
57行开了一个提示符窗口, sub_458054函数里面是每次输入后的分隔符号还有换行的输出.
60-67行是游戏的主体判断, 用了逻辑或来判断有没有达成规则(胜利条件), || 这个操作符的特点是两边的操作数为假才会输出假值, 一个真值则是真, 它后面又 != 1的判断条件, 则是反过来了。
既然这是胜利条件, 那sub_457AB4()这个函数就是胜利后的输出内容了。
(因为这是windows执行程序, 而且胜利条件特别简单, 你也可以直接改条件让游戏直接胜利...
或者od里调用到这个模块然后输出)
双击函数跳转(两次跳转)
int sub_45E940()
{
int i; // [esp+D0h] [ebp-94h]
char v2[22]; // [esp+DCh] [ebp-88h] BYREF
char v3[32]; // [esp+F2h] [ebp-72h] BYREF
char v4[4]; // [esp+112h] [ebp-52h] BYREF
char v5[64]; // [esp+120h] [ebp-44h]
sub_45A7BE("done!!! the flag is ");
v5[0] = 18;
v5[1] = 64;
v5[2] = 98;
v5[3] = 5;
v5[4] = 2;
v5[5] = 4;
v5[6] = 6;
v5[7] = 3;
v5[8] = 6;
v5[9] = 48;
v5[10] = 49;
v5[11] = 65;
v5[12] = 32;
v5[13] = 12;
v5[14] = 48;
v5[15] = 65;
v5[16] = 31;
v5[17] = 78;
v5[18] = 62;
v5[19] = 32;
v5[20] = 49;
v5[21] = 32;
v5[22] = 1;
v5[23] = 57;
v5[24] = 96;
v5[25] = 3;
v5[26] = 21;
v5[27] = 9;
v5[28] = 4;
v5[29] = 62;
v5[30] = 3;
v5[31] = 5;
v5[32] = 4;
v5[33] = 1;
v5[34] = 2;
v5[35] = 3;
v5[36] = 44;
v5[37] = 65;
v5[38] = 78;
v5[39] = 32;
v5[40] = 16;
v5[41] = 97;
v5[42] = 54;
v5[43] = 16;
v5[44] = 44;
v5[45] = 52;
v5[46] = 32;
v5[47] = 64;
v5[48] = 89;
v5[49] = 45;
v5[50] = 32;
v5[51] = 65;
v5[52] = 15;
v5[53] = 34;
v5[54] = 18;
v5[55] = 16;
v5[56] = 0;
qmemcpy(v2, "{ ", 2);
v2[2] = 18;
v2[3] = 98;
v2[4] = 119;
v2[5] = 108;
v2[6] = 65;
v2[7] = 41;
v2[8] = 124;
v2[9] = 80;
v2[10] = 125;
v2[11] = 38;
v2[12] = 124;
v2[13] = 111;
v2[14] = 74;
v2[15] = 49;
v2[16] = 83;
v2[17] = 108;
v2[18] = 94;
v2[19] = 108;
v2[20] = 84;
v2[21] = 6;
qmemcpy(v3, "`S,yhn _uec{", 12);
v3[12] = 127;
v3[13] = 119;
v3[14] = 96;
v3[15] = 48;
v3[16] = 107;
v3[17] = 71;
v3[18] = 92;
v3[19] = 29;
v3[20] = 81;
v3[21] = 107;
v3[22] = 90;
v3[23] = 85;
v3[24] = 64;
v3[25] = 12;
v3[26] = 43;
v3[27] = 76;
v3[28] = 86;
v3[29] = 13;
v3[30] = 114;
v3[31] = 1;
strcpy(v4, "u~");
for ( i = 0; i < 56; ++i )
{
v2[i] ^= v5[i];
v2[i] ^= 0x13u;
}
return sub_45A7BE("%s\n");
}
前面这些不说了, 就是初始化和赋值操作, 值得注意的是中间有qmemcpy函数还有末尾的strcpy函数, 举个例子就明白这个函数大概是干嘛的了, a是一个32位的数组, b是需要填入a的数据, c是填入多少位, qmemcpy(a, b, c) 就是往a里填入c位 "b"的意思, strcpy函数则是直接添加在数组中, 前者参数是数组, 后者参数是需要填入的数据。
接下来最后一块, 109行
对两个数组的元素进行了异或操作, 从我这边ida看到的代码来看v2根本没有56位, 是会索引溢出范围的, 从上面v3不知所云的操作, 应该是v3、v2、v4结合起来了(也是我研究了一下flag结构才发现的, 有坑啊!)
写一下python脚本
# [攻防世界]game 解题脚本
# IDA 多变量提取脚本
def multivariable_extraction(m_string):
i = 0
result = []
while i != len(m_string):
try:
# 读取后缀 ; 符号检测赋值
if m_string[i] == ";":
if m_string[i-3].isdigit():
result.append(int(m_string[i-3:i]))
elif m_string[i-2].isdigit():
result.append(int(m_string[i-2:i]))
else:
result.append(int(m_string[i-1]))
else:
i += 1
continue
i += 1
except IndexError:
print("索引异常")
continue
return result
# ascii码转字符
def ascii_conversion(m_list):
result = []
for digit in m_list:
result.append(chr(digit))
result = ''.join(result)
return result
# 字符转换ASCII码
def char_conversion_ascii(string):
result = []
for str1 in string:
result.append(ord(str1))
return result
# 数据输入
v5 = """ v5[0] = 18;
v5[1] = 64;
v5[2] = 98;
v5[3] = 5;
v5[4] = 2;
v5[5] = 4;
v5[6] = 6;
v5[7] = 3;
v5[8] = 6;
v5[9] = 48;
v5[10] = 49;
v5[11] = 65;
v5[12] = 32;
v5[13] = 12;
v5[14] = 48;
v5[15] = 65;
v5[16] = 31;
v5[17] = 78;
v5[18] = 62;
v5[19] = 32;
v5[20] = 49;
v5[21] = 32;
v5[22] = 1;
v5[23] = 57;
v5[24] = 96;
v5[25] = 3;
v5[26] = 21;
v5[27] = 9;
v5[28] = 4;
v5[29] = 62;
v5[30] = 3;
v5[31] = 5;
v5[32] = 4;
v5[33] = 1;
v5[34] = 2;
v5[35] = 3;
v5[36] = 44;
v5[37] = 65;
v5[38] = 78;
v5[39] = 32;
v5[40] = 16;
v5[41] = 97;
v5[42] = 54;
v5[43] = 16;
v5[44] = 44;
v5[45] = 52;
v5[46] = 32;
v5[47] = 64;
v5[48] = 89;
v5[49] = 45;
v5[50] = 32;
v5[51] = 65;
v5[52] = 15;
v5[53] = 34;
v5[54] = 18;
v5[55] = 16;
v5[56] = 0; """
v2 = """ v2[2] = 18;
v2[3] = 98;
v2[4] = 119;
v2[5] = 108;
v2[6] = 65;
v2[7] = 41;
v2[8] = 124;
v2[9] = 80;
v2[10] = 125;
v2[11] = 38;
v2[12] = 124;
v2[13] = 111;
v2[14] = 74;
v2[15] = 49;
v2[16] = 83;
v2[17] = 108;
v2[18] = 94;
v2[19] = 108;
v2[20] = 84;
v2[21] = 6; """
v3 = """ v3[12] = 127;
v3[13] = 119;
v3[14] = 96;
v3[15] = 48;
v3[16] = 107;
v3[17] = 71;
v3[18] = 92;
v3[19] = 29;
v3[20] = 81;
v3[21] = 107;
v3[22] = 90;
v3[23] = 85;
v3[24] = 64;
v3[25] = 12;
v3[26] = 43;
v3[27] = 76;
v3[28] = 86;
v3[29] = 13;
v3[30] = 114;
v3[31] = 1; """
v2_ = "{ "
v3_ = "`S,yhn _uec{"
v4_ = "u~"
_space_ = [0] # 字符型列表结束符
# 数字列表转换
v2_list = char_conversion_ascii(v2_)
v3_list = char_conversion_ascii(v3_)
v4_list = char_conversion_ascii(v4_)
# 从变量中提取数字
v2_list += multivariable_extraction(v2)
v3_list += multivariable_extraction(v3)
v5_list = multivariable_extraction(v5)
# 数据处理
v2v3_list = v2_list + v3_list + v4_list + _space_
for i in range(0, 56):
v2v3_list[i] ^= v5_list[i]
v2v3_list[i] ^= 0x13
# 整数答案列表转换成字符答案
print(ascii_conversion(v2v3_list))
flag:
zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}