攻防世界逆向高手题之zorropub

攻防世界逆向高手题之zorropub

继续开启全栈梦想之逆向之旅~
这题是攻防世界逆向高手题的zorropub
攻防世界逆向高手题之zorropub
.
.
最近有点偷懒了,几天没写题了,时间都是挤出来的,再忙也应该每天抽出时间来做题保持积累才对。
.
.
下载附件,照例扔入exeinfope中查看信息:
攻防世界逆向高手题之zorropub
.
.
64ELF文件无壳,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的,所以v5v6其实可以联系在一起,都是用于生成seed的。
然后生成的seed又要满足条件才可以作为srand的随机数种子,所以这里是一个限制条件。
攻防世界逆向高手题之zorropub
.
.
(这里积累第一个经验)
然后分析后半部分,这里先补充一些函数和以前积累的知识:

(以前积累的知识)
这里也是犯下的第二个错误,以前就听过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,那么v9LOBYTE(qword_6020C0[i])异或后就是flag
攻防世界逆向高手题之zorropub
.
.
.
(这里积累第二个经验)
所以总的逻辑梳理一下,用户输入的两个数生成满足i &= i - 1v9==10条件的seed种子,然后这个这个符合条件的种子生成特定的v9列表群,最后挑选一个列表md5加密后满足5eba99aff105c9ff6a1a913e343fec67v9异或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 - 1v9==10条件的seed种子,然后这个这个符合条件的种子生成特定的v9列表群,最后挑选一个列表md5加密后满足5eba99aff105c9ff6a1a913e343fec67v9异或LOBYTE(qword_6020C0[i])就是flag:

解毕!敬礼!

上一篇:AI预标注,人工智能基础数据服务行业的新引擎丨曼孚科技


下一篇:每日总结