前言
万万没想到,游戏的笔试居然出了道逆向的题来作压轴,要不是这道题,我原以为这辈子都不会碰这些逆向的工具=。=
题目
给出二进制文件,功能是输入0到1的x,输出f(x),输入输出均是浮点数。
自己实现F(x),可以和二进制文件输出同样的结果。
二进制文件在http://59.111.13.242/leihuo_2019_guess.zip下载
思路
先跑几遍guess_windows.exe
分析
可以看出,当且仅当输入为0~1之间的数时,会进行运算,当输大于等于1或者小于0时,输出error并退出。
用x64dbg加载guess_windows.exe
不停的下断点找到主函数入口:
我们手动运行,步进到第一个红框处,输入测试数据:0.55555
由于输入的是合法数据,因此我们可以跳转到计算f(x)的函数
其中,我们可以看到f(x)函数应该是这句:
call guess_windows_FC8761
我们跟进去看看:
//数据段中的常数
ds:[1080EA8]
ds:[1080E98]
ds:[1080E68]
ds:[1080E78]
ds:[1080E88]
...
上图中的fmul, fld fsubp等都是浮点数运算指令,其中的数据段数据ds:[xxxxxx]的应该就是隐藏在程序中的常数,理论上我们到相应内存地址里去找相应的数据万事大吉了。
但问题是内存中的数据是16进制,转成十进制还好说,转浮点数就有点费劲了,不急,我们慢慢调试来看:
我们先来看执行完下面这句后:
fld st(0), dword ptr ss:[ebp+8]
寄存器st(0)载入了0.55555,即我们输入的x,
那么看来
dword ptr ss:[ebp+8]//这个就是我们输入x的地址
回到计算f(x)的反汇编的核心代码,我们不难猜想f(x)的大致结构:
F(x)=ax4−bx3+cx2+dx+eF(x) = ax^4-bx^3 + cx^2+dx+eF(x)=ax4−bx3+cx2+dx+e
其中的常数a, b, c, d, e应该就是下面这几个了
//数据段中的常数
ds:[1080EA8]
ds:[1080E98]
ds:[1080E68]
ds:[1080E78]
ds:[1080E88]
在动态一步步调试的时候, 每一步的计算结果会实时反映在寄存器st(0)的变化中,如下:
在执行完下面这句:
fmul st(0),dword ptr ds:[1080EA8]
也就是算完ax,其中x=0.55555ax, 其中x=0.55555ax,其中x=0.55555, 我们的**寄存器st(0)**变为:22.42810821068…
即
0.55555a=22.428108210680.55555a =22.42810821068 0.55555a=22.42810821068
我们可以手动算出aaa的值, 约40左右的一个常数,但是这样太麻烦了,
并且因为float在寄存器中的存储并不精确(0.55555都变成了0.555549979…),我们无法知道做除法的时候该保留几位才合适,因此这种方法并不合适。
慢!
回想一下上面的式子F(x)=ax4−bx3+cx2+dx+eF(x) = ax^4-bx^3 + cx^2+dx+eF(x)=ax4−bx3+cx2+dx+e如果我们输入x为1,那么在调试的时候,ax=aax = aax=a
我们直接可以从寄存器的窗口中读取a,b,c,d,ea, b, c, d, ea,b,c,d,e的值,岂不是会变得很简单!
但是,1是非法输入,在进到f(x)函数前便会退出,因此我们需要把1变成合法输入,然后进入f(x)
爆破
重新Reload程序,在窗口中输入1,运行到
我们修改反汇编代码,把jne改成jmp,直接跳到f(x)函数:
st(0)寄存器的值为1,说明跳转成功!
再执行下面一条语句,寄存器st(0)应该就会显示aaa的值了
fmul st(0),dword ptr ds:[1080EA8]
可以看到a的值为40.371,与上面输入0.55555时预估的40左右差不多, 说明此方法可行!
同理,我们可以得到剩余b,c,d,eb, c, d, eb,c,d,e的值,这里不再赘述。
五个常数值分别为:
a=40.371,b=36.819,c=0.378,d=0.3855,e=3.0521a=40.371, b =36.819,c=0.378, d=0.3855, e=3.0521a=40.371,b=36.819,c=0.378,d=0.3855,e=3.0521
那么
F(x)=40.371x4−36.819x3+0.378x2+0.3855x+3.0521F(x) =40.371 x^4-36.819x^3 + 0.378x^2+0.3855x+3.0521 F(x)=40.371x4−36.819x3+0.378x2+0.3855x+3.0521
编写代码
下面的代码写好后,满以为100%AC,输入测试样例:0.268044
#include <stdio.h>
#include <math.h>
float f(float x)
{
float a = 40.371, b = 36.819, c = 0.378, d = 0.3855, e = 3.0521;
return a*pow(x, 4) - b*pow(x, 3) + c * pow(x, 2) + d * x + e;
}
int main()
{
float x = 0;
scanf("%f", &x);
if (x <0.0 || x >=1.0)
{
printf("error\n");
return 0;
}
printf("%0.6f", f(x));
return 0;
}
齐活儿~~
提交
emmm…居然不是100%AC, 只有98%能过
后记
在交完卷后又反思了一下,试了0.999999(如下图),我的程序输出7.367545, 提供的程序输出7.367547,最后一位不一样
猜想计算f(x)的时候用float精度不够,于是把f(x)中的float全换成double
double f(float x)
{
double a = 40.371, b = 36.819, c = 0.378, d = 0.3855, e = 3.0521;
return a*pow(x, 4) - b*pow(x, 3) + c * pow(x, 2) + d * x + e;
}
测试用例:0.999999,输出变成:7.367547,可惜没机会再次提交了。