吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3792|回复: 7
收起左侧

[CTF] 看雪CTF逆向练习题-[异想天开]

  [复制链接]
基丶 发表于 2021-4-27 13:06
本帖最后由 基丶 于 2021-4-27 18:10 编辑

题目链接:https://ctf.pediy.com/itembank.htm

分析:

先查下壳,无壳。

0.jpg


直接拖OD搜字符串,搜不到,看来是压缩过了。
z.jpg

换一种思路,给GetWindowText下断,试试能不能断下。
发现能断下

1.jpg

回溯一层回到调用GetWindowText的那段代码下断。

2.jpg
程序在按确定时会断下两次,第一次获取注册码,第二次获取用户名。
在第二次时,给buffer下内存访问断点,试试能不能追到加密函数。

下了内存访问断点之后会断下,但是发现断下的模块是系统模块(KernelBase、Ntdll、User32),这些模块直接跳过。
接着就断到程序自己的模块里
3.jpg

分析函数用OD不怎么舒服,拖到IDA分析一波。
这个程序没有设置动态基址,并且使用的也是默认基址,所以打开IDA后直接跳转到00401BF0 分析就可以。
在IDA里,F5以下,看下这个程序的伪代码

[C++] 纯文本查看 复制代码
int __thiscall sub_401BF0(HWND *this, int a2)
{
  int v2; // edx
  int v3; // ebx
  int v4; // esi
  int v5; // edi
  char v6; // al
  char v7; // al
  const CHAR *v8; // esi
  HWND v9; // eax

  v2 = 0;
  v3 = 1;
  v4 = *(_DWORD *)(a2 + 128);
  v5 = *(_DWORD *)(v4 - 8);
  if ( v5 > 0 )
  {
    while ( 1 )
    {
      v6 = *(_BYTE *)(v4 + v2);
      if ( (v6 < 48 || v6 > 57) && (v6 < 65 || v6 > 90) && (v6 < 97 || v6 > 122) )
        break;
      if ( ++v2 >= v5 )
        goto LABEL_11;
    }
    v3 = 0;
LABEL_11:
    while ( v2 > 1 )
    {
      v7 = *(_BYTE *)(v4 + v2-- - 1);
      if ( v7 == *(_BYTE *)(v4 + v2 - 1) )
        v3 = 0;
    }
    if ( !v3 )
    {
      v8 = *(const CHAR **)(a2 + 100);
      v9 = GetParent(this[7]);
      sub_418A00(v9);
      sub_41B0D1(1013, v8);
    }
  }
  return v3;
}



发现这个代码就是检查用户名字符串长度>=5并且仅支持大小写字母+数字的。
需要调用这个函数就证明接下来就是实现加密部分了,我们给这个函数的栈帧下断点。


4.jpg
断下后回溯一层,就能找到我们的加密函数。


5.jpg
故技重施再次拖到IDA,F5看下伪代码好分析。


[C++] 纯文本查看 复制代码
int __thiscall sub_401AE0(int this)
{
  HWND v2; // eax
  int v3; // eax
  HWND v4; // eax
  int v5; // esi
  int result; // eax
  _BYTE *v7; // edi
  int v8; // ebp
  int v9; // eax
  int v10; // esi
  int v11; // eax
  int v12; // ecx
  int v13; // edx
  char *v14; // eax
  int i; // ecx
  int v16; // esi
  int v17; // [esp+10h] [ebp-Ch]
  int v18; // [esp+14h] [ebp-8h]
  int v19; // [esp+18h] [ebp-4h]

  v2 = GetParent(*(HWND *)(this + 28));
  v3 = sub_418A00(v2);
  v4 = GetParent(*(HWND *)(v3 + 28));
  v5 = sub_418A00(v4);
  v17 = v5;
  result = sub_417669(0x15u);
  v7 = (_BYTE *)result;
  v8 = *(_DWORD *)(*(_DWORD *)(v5 + 128) - 8);
  v19 = result;
  if ( v8 >= 5 )                                // V8 = 字符串长度
  {
    result = sub_401BF0((HWND *)this, v5);      // 对字符串效验
    if ( result )
    {
      v9 = 0;                                   // index
      while ( 1 )
      {
        v10 = *(_DWORD *)(v5 + 128);            // 取出用户名
        v11 = v9 + 1;                           // index
        v18 = v11;                              // 保存了下Index
        v12 = *(char *)(v10 + v11 - 1);         // 用户名 + index - 1 取出子串
        if ( v11 < v8 )
        {
          v13 = v11;
          v14 = (char *)(v10 + 1);
          do
          {
            v12 += *v14++;
            ++v13;
          }
          while ( v13 < v8 );
        }
        for ( ; v12 < 10000; v12 *= 3 )
          ;
        for ( i = v12 / 3; i; *v7 = 0 )
        {
          v16 = i % 10;
          ++v7;
          i = (i - i % 10) / 10;
          *(v7 - 1) = *(_BYTE *)(v16 + *(_DWORD *)(this + 92));
        }
        v9 = v18;
        if ( v18 >= 5 )
          break;
        v5 = v17;
      }
      result = sub_401C80(v19, v17);
    }
  }
  return result;
}


拦截一波加密后的字符串试试,输入进去看看行不行。
6.jpg


7.jpg

岂可修,不行的话那么最值得怀疑的就是 sub_401C80 函数了。因为加密后还调用了这个函数,可能还会实现二次加密。
IDA跟一波这个函数
[C++] 纯文本查看 复制代码
int __thiscall sub_401C80(HWND *this, const char *a2, int a3)
{
  HWND *v3; // ebx
  unsigned int v20; // kr04_4
  signed int KeyLen; // ebp
  int V25; // eax
  const CHAR *v8; // esi
  HWND v9; // edx
  signed int v10; // ecx
  int v11; // ebx
  int v12; // ecx
  int v13; // ecx
  const CHAR *v14; // edi
  HWND v15; // eax
  HWND v16; // eax
  int v17; // [esp+10h] [ebp-Ch]
  signed int i; // [esp+24h] [ebp+8h]

  v3 = this;
  sub_417669(0x16u);                            // 分配缓冲区
  v20 = strlen(a2) + 1;                         // A2是加密后的文本缓冲区指针
  v17 = 1;
  KeyLen = v20 - 1;                             // V6为加密后用户名的长度
  V25 = v20 - 1 + 4;                            // 密码的长度
  if ( V25 != *(_DWORD *)(*(_DWORD *)(a3 + 124) - 8) )// 这里判断密码长度与加密后用户名长度如果不相等
  {
    v8 = *(const CHAR **)(a3 + 100);
    v9 = v3[7];
    goto LABEL_15;
  }
  v10 = 0;
  v11 = 0;
  for ( i = 0; v10 < KeyLen; i = v10 )
  {
    if ( v11 % 5 == 4 )                         // 判断子串是第否是第5、10、15个子串,等等五的倍数的子串
    {
      V25 = *(_DWORD *)(a3 + 124);              // 现在这个是我们的密码缓冲区
      if ( *(_BYTE *)(v11 + V25) != 45 )        // 取出密码的子串,子串一定要=45,否则失败,字符ASCALL码45为 ‘-’
                                                // 那就是说明,注册码的第五的倍数个必为 ‘-’,否则注册码出错,弹出失败
      {
        v8 = *(const CHAR **)(a3 + 100);        // 应该是开始效验
        v9 = this[7];
LABEL_15:                                       // 失败
        v16 = GetParent(v9);
        sub_418A00(v16);
        return sub_41B0D1(1013, v8);
      }
      v12 = v10 - 1;
    }
    else
    {
      v13 = a2[v10];                            // a2是加密后的KEY,这里取出KEY子串
      V25 = v13 / 5;
      KeyLen = v20 - 1;                         // KEY长度
      if ( v13 - v13 % 5 - 20 == *(char *)(v11 + *(_DWORD *)(a3 + 124)) + v13 % 2 + 12 )// a3+124是我们的密码缓冲区,这就是二次加密的算法
      {
        if ( v17 == v20 - 1 )
        {
          v14 = *(const CHAR **)(a3 + 96);      // /成功
          v15 = GetParent(this[7]);
          sub_418A00(v15);
          sub_41B0D1(1013, v14);
          sub_41B0D1(1016, *(LPCSTR *)(a3 + 128));
          sub_41B079(1003);
          V25 = sub_41B268(5);
        }
        ++v17;
      }
      v12 = i;
    }
    v10 = v12 + 1;
    ++v11;
  }
  return V25;
}


经过分析之后就能写个注册机还原下算法。
能照搬IDA的就直接照搬IDA,照搬不了的就直接看汇编还原下。


[C++] 纯文本查看 复制代码
int main(){


        char UserName[] = "CTFHUB";
        char VerifyBuf[] = "zouzhiyong";

        int StrLen = strlen(UserName);

        char Key[24] = { 0 };
        char *KeyPointer = Key;

        if (StrLen >= 5)//判断字符串长度是否>=5
        {
                int Temp = 0;
                //接着是对字符串效验的函数 必须是 大小写英文/数字

                while (1)
                {
                        int Index = Temp + 1; // V11
                        int TempIndex = Index; //  V18
                        LONG Character = *(char*)(UserName + Index - 1);

                        if (Index < StrLen)
                        {
                                int TempIndex2 = Index;
                                char * CharacterAddress = (char*)(UserName + 1);

                                do 
                                {
                                        char axx = (*CharacterAddress);
                                        Character += axx;
                                        CharacterAddress++;
                                        ++TempIndex2;
                                } while (TempIndex2 < StrLen);
                        }

                        for (; Character < 10000; Character *= 3);


                        //
                        for (int i = Character / 3; i; *KeyPointer=0)
                        {
                                int Num = i % 10;
                                ++KeyPointer;
                                i = (i - i % 10) / 10;
                                *(KeyPointer - 1) = *(char*)(Num + VerifyBuf);
                        }
                        
                        Temp = TempIndex;
                        if (Temp >= 5) break;
                }
        }



        //获取KEY完成,开始计算注册码
        int V17 = 0;

        unsigned int KeyLen = 0;
        KeyLen = strlen(Key);

        char RegisterCode[30] = { 0 };

        int Index = 0;//V12
        int Temp1 = 0;//V10
        int Temp2 = 0;//V11
        for (int i = 0; i < KeyLen; i = Temp1)
        {
                //判断子串是第否是第5、10、15个子串,等等五的倍数的子串
                if (Temp2 % 5 == 4)
                {
                        RegisterCode[Temp2] = 45;
                        Index = Temp1 - 1;
                }//TEMP1忽略了五的倍数,当执行到5的倍数时不会+1
                else
                {
                        int KeyChar = Key[Temp1];//V13
                        //
                        //if (KeyChar - KeyChar % 5 - 20 == *(char*)(Temp2 + (*(DWORD*)RegisterCode)) + KeyChar % 2 + 12)
                        //{
                        //}

                        //*(char*)(Temp2 + RegisterCode) = KeyChar - KeyChar % 5 - 20 - 12 - KeyChar % 2;
                        *(char*)(Temp2 + RegisterCode) = KeyChar - KeyChar % 5 - 20 - KeyChar % 2 - 12;
                        Index = i;

                }
                Temp1 = Index + 1;
                Temp2++;
        }

        printf("%s", RegisterCode);

        system("pause");



        return 1;
}

免费评分

参与人数 6威望 +1 吾爱币 +25 热心值 +6 收起 理由
你是我的人 + 1 + 1 谢谢@Thanks!
末日凌霄 + 1 + 1 谢谢@Thanks!
mzw793600 + 1 + 1 我很赞同!
Lucifer_BW + 1 + 1 热心回复!
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Lrogzin + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Hmily 发表于 2021-4-27 16:29
https://www.52pojie.cn/misc.php? ... 29&messageid=36 同学看这个学习下怎么把图片插入文章中,然后编辑一下主题内容,另外你是不是注册过账号了?
 楼主| 基丶 发表于 2021-4-27 17:04
Hmily 发表于 2021-4-27 16:29
https://www.52pojie.cn/misc.php?mod=faq&action=faq&id=29&messageid=36 同学看这个学习下怎么把图片插入 ...

感谢,我马上去看看。另外我确实之前注册过一个号,昵称涉及我的手机号,改不了,所以我重新注册了个

点评

可以申请改名的,既然已经注册了,那你直接用注册邮箱给官方邮箱发申请注销之前账号吧,论坛不然刚注册多个账号。  详情 回复 发表于 2021-4-27 17:06
Hmily 发表于 2021-4-27 17:06
基丶 发表于 2021-4-27 17:04
感谢,我马上去看看。另外我确实之前注册过一个号,昵称涉及我的手机号,改不了,所以我重新注册了个{:1_ ...

可以申请改名的,既然已经注册了,那你直接用注册邮箱给官方邮箱发申请注销之前账号吧,论坛不然刚注册多个账号。
 楼主| 基丶 发表于 2021-4-27 17:09
Hmily 发表于 2021-4-27 17:06
可以申请改名的,既然已经注册了,那你直接用注册邮箱给官方邮箱发申请注销之前账号吧,论坛不然刚注册多 ...

好的好的
Hmily 发表于 2021-4-27 17:16
@基丶 你把图片删了过几天缓存失效图片就不显示了,你还是看看帮助,怎么把上传的截图贴到正文里吧。
 楼主| 基丶 发表于 2021-4-27 18:11
Hmily 发表于 2021-4-27 17:16
@基丶 你把图片删了过几天缓存失效图片就不显示了,你还是看看帮助,怎么把上传的截图贴到正文里吧。

OK了,谢谢大哥
MasterW 发表于 2021-4-27 20:39
一看代码就脑瓜疼,怎么破
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-28 14:50

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表