攻防世界逆向高手题之zorropub
继续开启全栈梦想之逆向之旅~
这题是攻防世界逆向高手题的zorropub
.
.
最近有点偷懒了,几天没写题了,时间都是挤出来的,再忙也应该每天抽出时间来做题保持积累才对。
.
.
下载附件,照例扔入exeinfope
中查看信息:
.
.64
位ELF
文件无壳,kali
上运行不了,可是由于我的linux
只有kali
,所以只能结合别人的资料来分析了。照例扔入IDA64
中查看伪代码,有main
函数看main
函数:
v15 = __readfsqword(0x28u);
seed = 0;
puts("Welcome to Pub Zorro!!");
printf("Straight to the point. How many drinks you want?");
__isoc99_scanf("%d", &v5);
if ( v5 <= 0 )
{
printf("You are too drunk!! Get Out!!");
exit(-1);
}
printf("OK. I need details of all the drinks. Give me %d drink ids:", (unsigned int)v5);
for ( i = 0; i < v5; ++i )
{
__isoc99_scanf("%d", &v6);
if ( v6 <= 16 || v6 > 65535 )
{
puts("Invalid Drink Id.");
printf("Get Out!!");
exit(-1);
}
seed ^= v6;
}
i = seed;
v9 = 0;
while ( i )
{
++v9;
i &= i - 1;
}
if ( v9 != 10 )
{
puts("Looks like its a dangerous combination of drinks right there.");
puts("Get Out, you will get yourself killed");
exit(-1);
}
srand(seed);
MD5_Init(v10);
for ( i = 0; i <= 29; ++i )
{
v9 = rand() % 1000;
sprintf(s, "%d", v9);
v3 = strlen(s);
MD5_Update(v10, s, v3);
v12[i] = v9 ^ LOBYTE(qword_6020C0[i]);
}
v12[i] = 0;
MD5_Final(v11, v10);
for ( i = 0; i <= 15; ++i )
sprintf(&s1[2 * i], "%02x", (unsigned __int8)v11[i]);
if ( strcmp(s1, "5eba99aff105c9ff6a1a913e343fec67") )
{
puts("Try different mix, This mix is too sloppy");
exit(-1);
}
return printf("\nYou choose right mix and here is your reward: The flag is nullcon{%s}\n", v12);
}
.
.
先分析代码前半部分,输入的v5
决定了v6
的数量,但是v6
又是用于生成seed
的,所以v5
和v6
其实可以联系在一起,都是用于生成seed
的。
然后生成的seed
又要满足条件才可以作为srand
的随机数种子,所以这里是一个限制条件。
.
.
(这里积累第一个经验)
然后分析后半部分,这里先补充一些函数和以前积累的知识:
(以前积累的知识)
这里也是犯下的第二个错误,以前就听过rand是伪随机数,要用srand
生成随机数种子才行,不然产生的随机数列表都是一样的,而单独产生的随机数也不会在随机数列表用随意取值,而是固定的
第一次这个值,第二次那个值。所以这里我们可以直接修改源代码调试,打印出first_letter的值。
函数积累:
int MD5_Init(MD5_CTX *c)
函数:
初始化 MD5 Contex, 成功返回1,失败返回0
.int MD5_Update(MD5_CTX *c, const void *data, size_t len)
函数:
循环调用此函数,可以将不同的数据加在一起计算MD5,成功返回1,失败返回
.int MD5_Final(unsigned char *md, MD5_CTX *c)
函数:
输出MD5结果数据,成功返回1,失败返回0
.unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md)
函数:
MD5_Init,MD5_Update,MD5_Final三个函数的组合,直接计算出MD5的值
.void MD5_Transform(MD5_CTX *c, const unsigned char *b)
函数:
内部函数,不需要调用
其它积累:
%02x
x 表示以十六
进制形式输出02
表示不足两位
,,前面补0
输出,如果超过
两位,则以实际输出。sprintf(&s1[2 * i], "%02x", (unsigned __int8)v11[i]);
的意思是把相当于char的__int8的两位输出到&s1[2*i]中,也就是一次输出两个__int8(char)类型的v11[i]到s1的偶数
地址中,所以相当于一个个
赋值而已。
v9
是用特定seed
随机数种子生成的特定的列表,v12
就是flag
。黄框就是特定的v9
列表不断拼凑出的md5加密列表,只要v9
md5
加密后等于5eba99aff105c9ff6a1a913e343fec67
,那么v9
与LOBYTE(qword_6020C0[i])
异或后就是flag
:
.
.
.
(这里积累第二个经验)
所以总的逻辑梳理一下,用户输入的两个数生成满足i &= i - 1
和v9==10
条件的seed
种子,然后这个这个符合条件的种子生成特定的v9
列表群,最后挑选一个列表md5
加密后满足5eba99aff105c9ff6a1a913e343fec67
的v9
异或LOBYTE(qword_6020C0[i])
就是flag:
.
.
所以参照别人的博客和理解python subprocess模块
后写下自己的脚本:
(注意:kali是运行不了的,会爆error while loading shared libraries: libcrypto.so.1.0.0: cannot open shared object file libc错误)
模块知识博客地址:https://www.cnblogs.com/lincappu/p/8270709.html
import subprocess
c=0
seed=[] #v5和v6都是用于生成seed种子,所以可以合并成一步
for i in range(16,65535,1): #源代码中是先i=seed再验证i的符合性,所以逆向的时候就要先验证i的符合性再seed=i
while(i):
c+=1
i&=i-1
if(c==10):
seed.append(i) #获取随机数种子列表,种子固定后rand生成的随机数就会固定。
flag=""
for i in seed: #传入符合的i值,其实就是传入一定的seed值,
proc=subprocess.Popen(['./zorropub'],stdin=subprocess.PIPE,stdout=subprocess.PIPE); #用subprocess的Popen方法开启proc子进程并用stdin和stdout获取子进程的输入和输出
out=proc.communicate(('1\n%s\n'%i).encode('utf-8'))[0] #传入参数,第一个传入1即可,第二个传入符合的seed种子,第一个无论传入多少生成的seed都只有一个,所以传入1即可。用.encode('utf-8')属性可以传入字符串参数,不然就要传入bytes类型的参数了
if "nullcon".encode('utf-8') in out:
print(out) #打印符合的输出字符串
print(i) #打印符合的seed值
.
.
结果:(我运行不了,所以没有结果截图)
nullcon{nu11c0n_s4yz_x0r1n6_1s_4m4z1ng}
.
.
.
总结:
1: (这里积累第一个经验) 然后分析后半部分,这里先补充一些函数和以前积累的知识:
(以前积累的知识) 这里也是犯下的第二个错误,以前就听过rand是伪随机数,要用
srand
生成随机数种子才行,不然产生的随机数列表都是一样的,而单独产生的随机数也不会在随机数列表用随意取值,而是固定的
第一次这个值,第二次那个值。所以这里我们可以直接修改源代码调试,打印出first_letter的值。函数积累:
int MD5_Init(MD5_CTX *c)
函数: 初始化 MD5 Contex, 成功返回1,失败返回0 .int MD5_Update(MD5_CTX *c, const void *data, size_t len)
函数:
循环调用此函数,可以将不同的数据加在一起计算MD5,成功返回1,失败返回 .int MD5_Final(unsigned char *md, MD5_CTX *c)
函数: 输出MD5结果数据,成功返回1,失败返回0 .unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md)
函数:
MD5_Init,MD5_Update,MD5_Final三个函数的组合,直接计算出MD5的值 .void MD5_Transform(MD5_CTX *c, const unsigned char *b)
函数: 内部函数,不需要调用其它积累:
%02x
x 表示以十六
进制形式输出02
表示不足两位
,,前面补0
输出,如果超过
两位,则以实际输出。sprintf(&s1[2 * i], "%02x", (unsigned __int8)v11[i]);
的意思是把相当于char的__int8的两位输出到&s1[2*i]中,也就是一次输出两个__int8(char)类型的v11[i]到s1的偶数
地址中,所以相当于一个个
赋值而已。
2:
(这里积累第二个经验)
所以总的逻辑梳理一下,用户输入的两个数生成满足i &= i - 1
和v9==10
条件的seed
种子,然后这个这个符合条件的种子生成特定的v9
列表群,最后挑选一个列表md5
加密后满足5eba99aff105c9ff6a1a913e343fec67
的v9
异或LOBYTE(qword_6020C0[i])
就是flag:
解毕!敬礼!