题目

CSDN免积分下载

文件

先查壳,有壳脱壳,没壳拖入IDA
image.png
32位无壳exe

IDA

image.png

答案

zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}

Writeup

解题

题目描述

菜鸡最近迷上了玩游戏,但它总是赢不了,你可以帮他获胜吗

三种解题方式

结论先行,先把解题方式说了。
1️⃣二进制取反(加法)—————————最“真相”
2️⃣修改成功逻辑————————————最快
3️⃣解密成功逻辑中的密文(即,Flag)——最慢最保险

运行

image.png

The n is the serial number of the lamp,and m is the state of the lampIf m of the Nth lamp is 1,it’s on ,if not it’s off At first all the lights were closed Now you can input n to change its state But you should pay attention to one thing,if you change the state of the Nth lamp,the state of (N-1)th and (N+1)th will be changed too When all lamps are on,flag will appear

解题思路

审题

所有的灯都关上了 可以输入 n 来改变灯的状态

输入N时,第(N-1)个和第(N+1)个的状态也会改变

当所有灯都亮时,会出现flag

解:flag=所有灯都亮。
通过输入n(1-8)控制,从初始状态所有灯不亮⚫到最终状态所有灯都亮⚪。
当输入一个值时,其前后的值(±1)也会被改变。
如:
Step1:初始状态1⚫2⚫3⚫4⚫5⚫6⚫7⚫8⚫,输入3时,234都会取反:
——1⚫2⚪3⚪4⚪5⚫6⚫7⚫8⚫
Step2:在Step1后输入4,345都会取反:
——1⚫2⚪3⚫4⚫5⚪6⚫7⚫8⚫

代码逻辑

  1. 接受一个数值,判断数值的范围要在1-8之间
  2. 将该值-1后,传入修改灯状态的函数(sub_4576D6)中处理
  3. 当灯状态全部为1时,即成功:

image.png

修改灯状态

修改灯状态的函数,IDA自动反编译这样显示的:
image.png
明显有数组的特征:

  1. 相近/相邻的地址
  2. 相同的数据类型(此处为byte)

为了更好阅读,将byte_532E27修改为数组类型:
image.png
先前的byte_532E28就变为了byte_532E27+1,byte_532E29变为了byte_532E27+2:
image.png
因为该值是灯的状态数组,再将其重命名(N)为更容易理解了:
image.png
上面的代码仅做阅读使用,其实不是真正需要的代码,但是为了找到真正的代码,也是需要分析一下的,目的是找到成功的逻辑:
image.png
上述的灯状态,适合正式做题,拿来理解计算逻辑使用。
但是从题意可知,输入的并不是和其他题目相似的密文,输入的目的只是为了条件成立。最终输出flag的函数是不需要输入参数的,所以完全可以跳过输入部分,直接通过逻辑或者调试器的设置,执行最终输出flag的函数。

最终输出flag的函数

定位到正确逻辑的方式,除了上面修改灯状态的代码逻辑找到外,在先前的步骤中,其他字符串在IDA中的显示方式,可以很容易猜测出,该程序的所有字符串都是可以定位到的。

  1. 在内存中查看翻找正确提示的字符串:

image.png

  1. 或F12查看字符串,搜索字符串:

image.png
image.png
找到后发现该函数(sub_457AB4)是无参的,确认了上面的猜测,该程序可以不输入参数直接调用或跳转执行最终输出flag的函数。

1️⃣二进制加法

❌错解 - 无交集步长

一开始我认为是考“步长”的概念。通过步长的方式,将所有的元素,不重复的遍历一遍。
即:从2开始,步长为3的取值 = 2,5,8。
初始状态:⚫⚫⚫⚫⚫⚫⚫⚫
输入2,123取反:⚪⚪⚪⚫⚫⚫⚫⚫
输入5,456取反:⚪⚪⚪⚪⚪⚪⚫⚫
❌输入8,789取反:⚪⚪⚪⚪⚪⚪⚪⚪——并不是这样
⭕输入8,789取反:⚫⚪⚪⚪⚪⚪⚪⚪——1也被取反了
当我以为9算是“溢出”不影响结果时,却发现1代替了9,这可以理解为取余,如:9 % 8=1。

⭕正解 - 二进制取反(二进制加法)

image.png
三个二进制数相加时:

  1. 0101
  2. 0110
  3. + 1001
  4. ————————
  5. 10100
  6. 可以分解为(0101 + 0110)+ 1001
  7. 0101
  8. + 0110
  9. ————————
  10. 1011
  11. 1011
  12. + 1001
  13. ————————
  14. 10100

初始:0 0000 0000
正解:0 1111 1111 = 255

输入值与表格

输入值 灯(8→1) Hex
1 1 0 0 0 0 0 1 1 83
2 0 0 0 0 0 1 1 1 7
3 0 0 0 0 1 1 1 0 14
4 0 0 0 1 1 1 0 0 28
5 0 0 1 1 1 0 0 0 56
6 0 1 1 1 0 0 0 0 112
7 1 1 1 0 0 0 0 0 224
8 1 1 0 0 0 0 0 1 193

二进制加法的结果从1-8位全部为1,就是正解。发现把1-8全部输入一遍,即可满足所有的位数都是单数次(3次)+1,最终留下1(点亮)的值:
image.png

2️⃣【👍推荐👍】跳转

如何直接运行最终输出flag的函数也有两种方式:

  1. 直接运行成功函数
  2. 修改跳转

不过最终效果都是一样的:
image.png

【👍👍最推荐👍👍】直接运行 - 修改EIP

已知要跳转到45E940地址:
image.png
程序载入OD后,停在OEP处,可以看到EIP:
image.png
此时还不可以修改,因为还有一些程序的初始化,堆栈的问题。要运行到main函数后(Ctrl+G,输入45F400;再F4),才可以Ctrl+G跳转到45E940:
image.png
右击该地址,选择“此处为新EIP”:
image.png
可以看到EIP的值已经变成了最终输出flag的函数的起始地址:
image.png
F9运行:
image.png
这个操作等于进入main函数后,直接调用了最终输出flag的函数,跳过了题目打印还有输入等,所以没有其他的字符串显示。

修改跳转 - ZF标志位

当有一些其他参数参与或者堆栈问题是,无法直接调用最终输出flag的函数,此时可以通过修改逻辑执行最终输出flag的函数。
IDA中显示如下:
image.png
通过串联(也就是“并且”、&&、||)的逻辑,当满足所有逻辑时,会跳转到输出flag的函数。对应的汇编流程图如下:
image.png
在if判断处(45F5D8)下断,运行后输入1-8之间的任一数,会在if判断出停下:
image.png
需要做的是,在判断if条件是否满足的JNZ处停下,修改ZF标志位,改为不跳转的灰色线,即ZF改为1:
image.png
不过因为串联逻辑要改8个ZF,有点麻烦:
image.png
嫌麻烦的还可以在if后,把代码NOP掉:
image.png
选中内存范围后,右击-二进制-用NOP填充:
image.png
F9运行:
image.png

修改跳转 - 汇编JMP

不过既然都NOP了,也算“超纲”涉及到编辑汇编指令了。完全可以直接在判断出修改代码,改为JMP 45F66C:
image.png
image.png
跳转到打印Flag的函数了:
image.png
不过既然到跳转了,其实也可以在前面就跳,在最提前的部分跳转,就相当于修改EIP了。
所以方法不止一种,但是解决方案可能根据自身需求的不同而改变,方便还是效率。

3️⃣【❌不推荐❌】代码执行

这种方式不是很建议,脱离了原始题意。可以当做练Python,或者在没有运行程序环境时,可以把最终这个要打印Flag的代码抠出来执行。
最终打印的字符串为两次异或解密出的字符串:
image.png
此时IDA(上图为7.5 SP3)明显出现反编译代码识别错误的问题(v3取值超出范围了),选中qmemcpy查看反汇编代码,并不是真的调用qmemcpy进行内存复制,依旧是赋值的汇编代码:
image.png
在IDA 7.0识别为:
image.png
识别错误的情况是经常出现的,并且由于版本的不同,还会有不同的错误细节。
选中,将其识别为数组:
image.png
数组的大小填57( 56+1 ):
image.png
另一参数同理:
image.png
这时在IDA中的可读性就很强了,可以看得出来是给两个字符串数组赋值:
image.png
image.png
最终的解密代码也很清晰:

  for ( i = 0; i < 56; ++i )
  {
    v2[i] ^= v3[i];
    v2[i] ^= 0x13u;
  }

Python代码照着这个逻辑还原即可:

v3 = [ 18, 64, 98, 5, 2, 4, 6, 3, 6, 48, 49, 65, 32, 12, 48, 65, 31, 78, 62, 32, 49, 32, 1, 57, 96, 3, 21, 9, 4, 62, 3, 5, 4, 1, 2, 3, 44, 65, 78, 32, 16, 97, 54, 16, 44, 52, 32, 64, 89, 45, 32, 65, 15, 34, 18, 16, 0 ]
v2 = [ 123, 32, 18, 98, 119, 108, 65, 41, 124, 80, 125, 38, 124, 111, 74, 49, 83, 108, 94, 108, 84, 6, 96, 83, 44, 121, 104, 110, 32, 95, 117, 101, 99, 123, 127, 119, 96, 48, 107, 71, 92, 29, 81, 107, 90, 85, 64, 12, 43, 76, 86, 13, 114, 1, 117, 126, 0 ]
i = 0
flag = ''

while ( i < 56 ):
    #异或运算
    v3[i] ^= v2[i]
    v3[i] ^= 0x13

    #保存每个异或的结果
    flag += chr(v3[i])
    #下标+1
    i+=1
print(flag)

image.png