题目是个consoleapplication4.exe,运行一下:
是一个游戏:
n是灯的序列号,m是灯的状态
如果m (n) = 1,它是开着的,如果不是,它是关着的
起初所有的灯都关着
现在你可以输入n来改变它的状态
但是你要注意一件事,如果你改变N灯的状态,(N-1)th和(N+1)th的状态也会改变
当所有的灯都亮着的时候,flag就会出现
现在,输入n。
就是输入一个1到8之间的数,例如1:就会改变周围的三个数的状态,使8、1、2三个数变亮,再输入一个2:也会改变2周围三个数的状态,使1、2熄灭,3亮起。无限次输入后,最后8个数都亮起的时候就会输出flag。
方法一,直接玩!
玩了好一会儿,发现把1到8之间的数字都输一遍就能通过这个游戏,出现flag:
但逆向总不能每次都自己试出来吧。下面开始逆向的分析:
方法二:自己计算flag
用IDA打开exe之前要先查壳,这个exe是没有加壳的,用IDA打开静态分析,通过之前运行exe文件出现的字符串定位到关键函数的位置,按F5:这个exe可以反编译,那我们就直接看到类似源代码的代码:
关键函数的主要逻辑就是上面说的游戏规则的实现。
再看一下输出flag的函数:就是将两个已经初始化的数组求与之后,再按位和0x13求与之后,转化成字符串输出即可:
所以我们在这就可以直接自己把两个数组拿出来,然后自己算一遍得到flag。
源代码:
# py -3
# coding:utf-8
array1 = [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]
array2 = [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]
flag = ''
for i in range(len(array1)):
flag+= chr(array1[i] ^ array2[i] ^ 0x13 )
print (flag)
得到flag为:zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}
方法三:修改源代码
用OD打开,查找字符串“done!!! the flag is”找到输出flag的关键位置:
网上找几行找到这个输出flag的函数的入口位置:
发现它是跳转来自 01077AB4。所以我们再往上转到 01077AB4:
0107E940=ConsoleA.0107E940 本地调用来自 0107F66C
0107E940=ConsoleA.0107E940 表明了它指向了输出flag的函数;本地调用来自 0107F66C说明它是由0107F66C调用的,所以我们再往回转到 0107F66C:
我们到了这,发现上面由8个JNZ,这不就是源代码中的8个判断吗?
一旦只要由一个判断不满足,程序就跳转到输出flag的函数的下一条去了,这样就不会输出flag而重新开始循环要求你输出n了:
所以我们要修改源代码使得程序不管怎样都会进入输出flag的函数,这样我们只要运行程序就能得到flag了。将原来的“jmp 0107F4FB”(跳转到循环重新开始的位置)改为jmp 01077AB4(跳转到输出flag函数的位置),这样只要我们输入1~8直接的数,程序最终都是跳转到0107F4FB(flag函数的入口地址):
保存到可执行文件,运行:
同样也得到flag。这样一想其实我们也可以修改其他地方的call和JMP,让程序跳转到输出flag的函数也行,前提是不影响程序的正常运行,至少在输出flag之前要正常运行,可以修改的地方很多,就不一一演示了。
方法四:直接用OD边调试边运行
主要思想是修改8次JNZ判断的值:开始程序每次判断出我们的1~8数字有没亮起的就跳转的循环重新开始的位置,所以我们每次在程序要跳转的时候通过修改ZF的值,让程序不跳转。先在8次JZN的位置设上断点:
开始运行后程序会先停在让我们输入数字的位置,输入数字“1”后程序会自动停在第一个JNZ断点上:
因为我们输入的数字是1,点亮了8、1、2三个数字,所以程序在第8、第1、第2个JNZ处已经符合要求不会调转了,我们只需要修改第3~7个JNZ跳转处的值,接着我们按F7向下单步运行,我们也可以清楚的看到寄存器中的每个值的变化:下面是单步运行到第2个JNZ的位置上,我们可以看到第二次的ZF已经为1,JNZ不会跳转,所以我们也不用修改了:
继续单步运行到第三个JNZ处,可以看到ZF为0,程序原本是要跳转到重新开始循环的位置的:
我们把ZF标志位改为1,让程序继续向下单步运行:
如法炮制:我们将第4~7个JNZ处的ZF标志为也改为1:
到这所有需要修改的值都已经改好了,点一下OD的开始运行,只要你没有在后面的程序设断点程序就会直接运行处flag了。接下来的只是一些继续的分析和佐证我们上面的步骤正确性的单步调试。下面继续单步运行,到下面第8个JNZ,前面已经说了ZF标志位已经为1了,这里果然已经为1证明我们之前的分析是正确的:
让程序逐行运行,最终到达跳转到输出flag的那个函数的那条指令处:
再运行就可以进入输出flag函数了,这里我们为了看的更清楚,继续单步进入flag函数:
我们就成功的进入了输出flag的函数,看到了之前通过定位字符串找到的位置。再继续单步运行,我们可以对这个函数理解的更清楚:我们可以看到数组是如何生成的,在数据窗口中的位置、值的变化:
后续的就是两个数组求与,再按位和0x13求与了,你在数据窗口中可以清楚的看到每一位的变化,这里就不截图了。
上面也说了,其实你只要修改完需要修改的JNZ的值,之后点开始运行就好了,这里放出运行结果的截图:
flag为:zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}
这种方法和上一种OD修改源代码的区别就是:这种是通过修改ZF标志位来控制跳转,使程序进入输出flag的函数,严格来说是修改标志位,并没有修改源代码;而上一种方法是通过修改跳转的源代码,让程序进入输出flag的函数。