寻找seed

main函数 能看到我们的比较是比较一个md5值 一般这种题目都是爆破

int __fastcall main(int a1, char **a2, char **a3)
{
  size_t v3; // rax
  int v5; // [rsp+1Ch] [rbp-104h] BYREF
  int v6; // [rsp+20h] [rbp-100h] BYREF
  signed int i; // [rsp+24h] [rbp-FCh]
  unsigned int seed; // [rsp+28h] [rbp-F8h]
  int v9; // [rsp+2Ch] [rbp-F4h]
  _BYTE v10[96]; // [rsp+30h] [rbp-F0h] BYREF
  char v11[16]; // [rsp+90h] [rbp-90h] BYREF
  char v12[32]; // [rsp+A0h] [rbp-80h] BYREF
  char s[32]; // [rsp+C0h] [rbp-60h] BYREF
  char s1[40]; // [rsp+E0h] [rbp-40h] BYREF
  unsigned __int64 v15; // [rsp+108h] [rbp-18h]

  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:", v5);
  for ( i = 0; i < v5; ++i )
  {
    __isoc99_scanf("%d", &v6);
    if ( v6 <= 16 || v6 > 0xFFFF )
    {
      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((__int64)v10, (__int64)s, v3);
    v12[i] = v9 ^ LOBYTE(dword_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);
}

然后第一个接收 收了一个总数 然后第二个接收 是drink id 然后对每一个id进行xor 然后有一个判断 v9 必须等于10 那v9是什么呢

v9 = 0;
  while ( i )
  {
    ++v9;
    i &= i - 1;
  }

这是经典的计算bit数量 也就是 seed 的二进制必须正好有 10 个 1 然后利用这个seed初始化随机数

然后循环 30 次

for (i = 0; i <= 29; ++i)
{
    v9 = rand() % 1000;
    sprintf(s, "%d", v9);
    MD5_Update(v10, s, strlen(s));
    v12[i] = v9 ^ LOBYTE(dword_6020C0[i]);
}

这里做了两件事 第一件事 生成 30 个随机数 每个都是

rand() % 1000

然后把这些数字的十进制字符串拼起来算 MD5 比如随机数是

12 345 6

那么 MD5 算的是

"123456"

不是数组 也不是加分隔符 最后要求 MD5 必须等于

5eba99aff105c9ff6a1a913e343fec67

所以我们只要爆破 seed 找到哪个 seed 生成的 30 个随机数能满足这个 MD5

exp ai写的 嘿嘿有点懒

from hashlib import md5

enc = [
    0x3c8,0x32,0x2ce,0x302,0x7f,0x1b8,0x37e,0x188,0x349,0x27f,
    0x5e,0x234,0x354,0x1a3,0x96,0x340,0x128,0x2fc,0x300,0x28e,
    0x126,0x1b,0x32a,0x2f5,0x15f,0x368,0x1eb,0x79,0x11d,0x24e
]

target = "5eba99aff105c9ff6a1a913e343fec67"

def glibc_rand(seed, n):
    if seed == 0:
        seed = 1

    r = [0] * (344 + n)
    r[0] = seed

    for i in range(1, 31):
        r[i] = (16807 * r[i - 1]) % 2147483647

    for i in range(31, 34):
        r[i] = r[i - 31]

    for i in range(34, 344 + n):
        r[i] = (r[i - 31] + r[i - 3]) & 0xffffffff

    return [r[i] >> 1 for i in range(344, 344 + n)]

for seed in range(0x10000):
    if seed.bit_count() != 10:
        continue

    nums = [x % 1000 for x in glibc_rand(seed, 30)]

    if md5("".join(map(str, nums)).encode()).hexdigest() == target:
        print('nullcon{'+"".join(chr((nums[i] ^ enc[i]) & 0xff) for i in range(30))+'}')
        break

flag

nullcon{nu11c0n_s4yz_x0r1n6_1s_4m4z1ng}