BUUCTF-crackMe

因为人太菜了这个题做了蛮久的,网上能搜到的 wp 也不是很多,而且几乎都省略了动态调试部分,通过这个题也算是熟悉了一下动调,因此记录下来

题目描述:小张从网上下载到一个黑客软件,然而开发者并不打算共享,所以小张注册了一个用户名叫welcomebeijing,但是密码需要进行逆向计算,请求出密码,进行MD5的32位小写哈希,进行提交。 注意:得到的 flag 请包上 flag{} 提交

查壳,没壳,载入 IDA,查看主函数

int wmain()
{
  FILE *v0; // eax
  FILE *v1; // eax
  char v3; // [esp+3h] [ebp-405h]
  char v4[256]; // [esp+4h] [ebp-404h] BYREF
  char Format[256]; // [esp+104h] [ebp-304h] BYREF
  char user_pass[256]; // [esp+204h] [ebp-204h] BYREF
  char user_name[256]; // [esp+304h] [ebp-104h] BYREF

  printf("Come one! Crack Me~~~\n");
  memset(user_name, 0, sizeof(user_name));
  memset(user_pass, 0, sizeof(user_pass));
  while ( 1 )
  {
    do
    {
      do
      {
        printf("user(6-16 letters or numbers):");
        scanf("%s", user_name);
        v0 = (FILE *)sub_4024BE();
        fflush(v0);
      }
      while ( !(unsigned __int8)check_let_num(user_name) );
      printf("password(6-16 letters or numbers):");
      scanf("%s", user_pass);
      v1 = (FILE *)sub_4024BE();
      fflush(v1);
    }
    while ( !(unsigned __int8)check_let_num(user_pass) );
    sub_401090(user_name);
    memset(Format, 0, sizeof(Format));
    memset(v4, 0, sizeof(v4));
    v3 = ((int (__cdecl *)(char *, char *))loc_4011A0)(Format, v4);
    if ( sub_401830((int)user_name, user_pass) )
    {
      if ( v3 )
        break;
    }
    printf(v4);
  }
  printf(Format);
  return 0;
}

输入用户名和密码,判断是否均为字母和数字,关键函数为 sub_40183,要求此函数返回值应该是 true,查看一下

bool __cdecl sub_401830(int a1, const char *a2)
{
  int v3; // [esp+18h] [ebp-22Ch]
  int v4; // [esp+1Ch] [ebp-228h]
  int v5; // [esp+28h] [ebp-21Ch]
  unsigned int v6; // [esp+30h] [ebp-214h]
  char v7; // [esp+36h] [ebp-20Eh]
  char v8; // [esp+37h] [ebp-20Dh]
  char v9; // [esp+38h] [ebp-20Ch]
  unsigned __int8 v10; // [esp+39h] [ebp-20Bh]
  unsigned __int8 v11; // [esp+3Ah] [ebp-20Ah]
  char v12; // [esp+3Bh] [ebp-209h]
  int v13; // [esp+3Ch] [ebp-208h] BYREF
  char v14; // [esp+40h] [ebp-204h] BYREF
  char v15[255]; // [esp+41h] [ebp-203h] BYREF
  char v16[256]; // [esp+140h] [ebp-104h] BYREF

  v4 = 0;
  v5 = 0;
  v11 = 0;
  v10 = 0;
  memset(v16, 0, sizeof(v16));
  v14 = 0;
  memset(v15, 0, sizeof(v15));
  v9 = 0;
  v6 = 0;
  v3 = 0;
  while ( v6 < strlen(a2) )
  {
    if ( isdigit(a2[v6]) )                      // 判断是不是数字
    {
      v8 = a2[v6] - 48;                         // 字符转整型
    }
    else if ( isxdigit(a2[v6]) )                // 判断是不是十六进制数字
    {
      if ( *((_DWORD *)NtCurrentPeb()->ProcessHeap + 3) != 2 )
        a2[v6] = 34;
      v8 = (a2[v6] | 0x20) - 87;                // 转整型
    }
    else
    {
      v8 = ((a2[v6] | 0x20) - 97) % 6 + 10;     // 6个一组,对应10~15
    }
    __rdtsc();
    __rdtsc();
    v9 = v8 + 16 * v9;
    if ( !((int)(v6 + 1) % 2) )                 // 两两一组的16进制整形
    {
      v15[v3++ - 1] = v9;
      v9 = 0;
    }
    ++v6;
  }
  while ( v5 < 8 )
  {
    v10 += byte_416050[++v11];
    v12 = byte_416050[v11];
    v7 = byte_416050[v10];
    byte_416050[v10] = v12;
    byte_416050[v11] = v7;
    if ( (NtCurrentPeb()->NtGlobalFlag & 0x70) != 0 )// 反调试
      v12 = v10 + v11;
    v16[v5] = byte_416050[(unsigned __int8)(v7 + v12)] ^ v15[v4 - 1];
    if ( (unsigned __int8)*(_DWORD *)&NtCurrentPeb()->BeingDebugged )// 反调试
    {
      v10 = -83;
      v11 = 43;
    }
    sub_401710((int)v16, (const char *)a1, v5++);
    v4 = v5;
    if ( v5 >= (unsigned int)(&v15[strlen(&v14)] - v15) )
      v4 = 0;
  }
  v13 = 0;
  ((void (__cdecl *)(char *, int *))sub_401470)(v16, &v13);// dbappsec
  return v13 == 43924;
}

主体大致可以分为三部分,函数先将字符串密码转换为两两组合的16进制整型,然后通过一个异或生成 v16 数组,最后将 v16 与 v13=0 传入 sub_401470,而 sub_401470 的返回值应当为 43924

看一下 sub_401470

unsigned int *__usercall sub_401470@<eax>(int a1@<ebx>, _BYTE *a2, unsigned int *a3)
{
  int *_EAX; // eax
  char v5; // al
  char _AL; // al
  unsigned int *result; // eax

  if ( *a2 != 'd' )
    *a3 ^= 3u;
  else
    *a3 |= 4u;
  if ( a2[1] != 'b' )
  {
    *a3 &= 97u;
    _EAX = (int *)*a3;
  }
  else
  {
    _EAX = (int *)a3;
    *a3 |= 20u;
  }
  __asm { aam }
  if ( a2[2] != 'a' )
    *a3 &= 10u;
  else
    *a3 |= 132u;
  if ( a2[3] != 'p' )
    *a3 >>= 7;
  else
    *a3 |= 276u;
  if ( a2[4] != 'p' )
    *a3 *= 2;
  else
    *a3 |= 896u;
  if ( *((_DWORD *)NtCurrentPeb()->ProcessHeap + 3) != 2 )
  {
    if ( a2[5] != 'f' )
      *a3 |= 33u;
    else
      *a3 |= 732u;
  }
  if ( a2[5] != 's' )
  {
    v5 = (char)a3;
    *a3 ^= 429u;
  }
  else
  {
    *a3 |= 2564u;
    v5 = (char)a3;
  }
  _AL = v5 - (~(a1 >> 5) - 1);
  __asm { daa }
  if ( a2[6] != 'e' )
    *a3 |= 74u;
  else
    *a3 |= 8976u;
  if ( a2[7] != 'c' )
  {
    *a3 &= 931u;
    return (unsigned int *)*a3;
  }
  else
  {
    result = a3;
    *a3 |= 35344u;
  }
  return result;
}

合理猜测只要每个条件都符合了就会返回 43924,需要注意的一点是第六个字符有一个小坑,会判断是否处于调试状态,如果是就取 'f',所以正确的字符应该取下面那个 's',得到 v16 的值应当为 "dbappsec"

知道了 v16 的值,接下来只需要知道 byte_416050 的值就可以逆推得到密码,byte_416050 的获取通过动调来实现

先在 IDA 中定位到相关位置,异或的操作位于 sub_401830 函数内,在 IDA View-A 窗口中找到对应地址

然后看到代码中

调用 byte_416050 处会有一个明显的异或操作,以及在此下方调用了一个函数,因此在汇编中会有一个明显的 xor 并且往下不远会存在一个 call,据此在 IDA View-A 寻找符合条件的地方

然后打开 od,找到 1B3E 的位置

以防万一再对比一下周围,上面两个 movzx,再往上三个 mov,下面三个 mov,没问题

在此处下断点,输入用户名和密码,运行到这,可以看到 ecx 的值为 0x2A

然后不断 ctrl+F9,把每次 ecx 的值提取出来,得到 byte_416050 的值为

0x2A, 0xD7, 0x92, 0xE9, 0x53, 0xE2, 0xC4, 0xCD

写脚本得到密码

#include <bits/stdc++.h>
using namespace std;
int a[] = {0x2A, 0xD7, 0x92, 0xE9, 0x53, 0xE2, 0xC4, 0xCD};
string s = "dbappsec";
int main() {
  for (int i = 0; i < 8; i++) {
  	int num = (int)s[i]; 
  	int ans = num ^ (int)a[i];
  	cout << hex << ans;
  }
  return 0;
}

最后进行 md5 加密,取 32 位小写,得到 flag

flag{d2be2981b84f2a905669995873d6a36c}

posted @ 2022-01-07 12:27  Moominn  阅读(726)  评论(0编辑  收藏  举报