拿到题目先查一下壳,看一下信息
发现是一个ELF文件,64位的
用IDA Pro 64 打开这个文件
然后点击F5进行伪代码转换
可以看到有五个if判断,第一个argc != 5这个判断并没有起太大作用,主要是下面四个if判断
根据题目,其实可以判断出来,这个就是一道数学题
现在开始逐步分析一下代码
赋值操作:
strtol函数,作用是把字符串转成长整型,具体解释看这个C 库函数 -- strtol() | 菜鸟教程
然后将v4传入f函数,剩下的v10,v11,v12和v4一样的操作,这里主要就是f函数
然后下面这几个if判断,可以理解为几个方程
假设x = v4,y = v9, a = v10,b = v11, c = v12,那么就有下面几个方程:
y - a = 151381742876(这里将十六进制转换成十进制了,下面的类同)
y - b = 117138004530
y - c = 155894355749
x + a + b + c = 1349446086540

很明显,五个未知数,四个方程解不出来的,这里还有一个点就是v9是通过v4得出的,也就是v9和v4还有关联,只要求出来一个,所有的都可以解出来了
现在查看一下f函数的内容
可以看到传进去的v4也就是这个局部变量的a1,是有限制的,即,1 < a1 <= 200
所以就知道了v4的取值范围也是(1-200]

那就根据这些条件,话不多说直接上脚本
cpp
#include <stdio.h>
#include <windows.h>
#include <inttypes.h>
uint64_t y(int var_24);
int main()
{
uint64_t v10, v11, v12 ,argv = 0;
uint64_t dbNumber = 0;
for (int v4 = 1; v4 < 200; ++v4)
{
dbNumber = y(v4);
v10 = dbNumber - 151381742876;
v11 = dbNumber - 117138004530;
v12 = dbNumber - 155894355749;
argv = v4 + 25923;
if (v4 + v12 + v11 + v10 == 1349446086540)
{
printf("v9=%" PRIX64 ",v10=%" PRIX64 ",v11=%" PRIX64 ",v12=%" PRIX64 ",v4=%X,argv=%" PRIX64 "\r\n", dbNumber, v10, v11, v12, v4,argv);
printf("%" PRIX64 "%" PRIX64 "%" PRIX64 "%" PRIX64 "\r\n", v10, v11, v12, argv);
}
}
system("pause");
}
uint64_t y(int var_24)
{
uint64_t *ptr;
uint64_t var_10;
uint64_t var_14;
if (var_24 <= 1)
{
return 0;
}
else if (var_24 <= 200)
{
ptr = (uint64_t *)malloc(var_24 * 8);
ptr[0] = 1;
ptr[1] = 1;
var_10 = 0;
for (var_14 = 2; var_14 < var_24; ++var_14)
{
ptr[var_14] = ptr[var_14 - 1] + ptr[var_14 - 2];
var_10 = ptr[var_14];
}
free(ptr);
return var_10;
}
}
下面开始分析这个脚本的作用,以及如何写成的
首先,其中定义的变量都用了uint64_t,原因是,在我最开始写这个脚本的时候,用的long long型定义的,发现还是放不下其中的数值,数值貌似是5000亿,所以这里将所有的变量都改为uint64_t,不过用uint64_t的时候,需要包含头文件inttypes.h
类型 | 位数 | 是否有符号 | 等效传统类型 | 备注 |
---|---|---|---|---|
uint64_t |
64 | 无符号 | unsigned long long |
保证64位 |
unsigned long long |
至少64 | 无符号 | - | 可能比64位更大(依赖平台) |
size_t |
平台相关 | 无符号 | - | 通常用于表示内存大小 |
uint32_t |
32 | 无符号 | unsigned int |
32位无符号 |
然后接着往下分析,整体代码中除了主函数(main)还有一个y函数,这个等同于IDA中的f函数,也就是我将f函数的伪代码转换成C语言了,也就是用来计算IDA中v9的值
然后我main函数中,有一个for循环,这里我的思路是爆破v4的值,然后前面分析也知道了v4的值在(1,200]之间,所以利用一个for循环遍历v4,并将v4传入y函数中,用dbNumber接收返回值,这里dbNumber等同于IDA中的v9
然后v10、v11、v12的值都是通过dbNumber求出来的,也就是根据前面列的方程求
然后再加了一个if语句,是为了判断v4的值是否正确,也就是看看当前根据爆破出来的v4值求出来的v10、v11、v12是否正确,如果正确,那就全部输出出来
最后这个printf的用法,跟正常使用的时候不一样,这是为啥呢,因为我们定义的变量都是uint64_t,而printf
系列函数中,能正确格式化输出 uint64_t
类型的变量的就是PRIX64
-
宏名称 :
PRIx64
-
PRI
:表示 "print"(格式化输出) -
x
:表示十六进制小写输出(X
表示大写十六进制) -
64
:表示64位宽度
-
脚本的所有地方都解释清楚了,直接运行

可以看到,第一行是当时为了调试,看看所有变量有没有正确输出,第二行才是真正的flag,也就是将所有变量拼接起来,拼接顺序是v10、v11、v12、argv,为什么呢,因为在IDA中,赋值的地方就看到了v10是argv[1],v11是argv[2],v12是argv[3],v4是argv[4] - 25923,所以argv[4]=v4+25923,也就是我脚本中的argv变量

最后将得到的一串十六进制数复制到厨子中进行十六进制解密,就得到flag了

最后多说一句
如果师傅们还需要更多的资料,欢迎进入泷羽Sec资源库,里面不仅包含了各种面试题,还有各种网课资源,各种源码分享
泷羽Sec资料库,加入泷羽Sec帮会,享受各种IT资源,资源持续更新中
