基丶 发表于 2021-4-27 13:06

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

本帖最后由 基丶 于 2021-4-27 18:10 编辑

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

分析:

先查下壳,无壳。




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


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



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


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

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


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

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);
      sub_418A00(v9);
      sub_41B0D1(1013, v8);
    }
}
return v3;
}


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



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



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



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; //
int v18; //
int v19; //

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;
}


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





岂可修,不行的话那么最值得怀疑的就是 sub_401C80 函数了。因为加密后还调用了这个函数,可能还会实现二次加密。
IDA跟一波这个函数
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; //
signed int i; //

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;
    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;
LABEL_15:                                       // 失败
      v16 = GetParent(v9);
      sub_418A00(v16);
      return sub_41B0D1(1013, v8);
      }
      v12 = v10 - 1;
    }
    else
    {
      v13 = a2;                            // 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);
          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,照搬不了的就直接看汇编还原下。


int main(){


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

      int StrLen = strlen(UserName);

      char Key = { 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 = { 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 = 45;
                        Index = Temp1 - 1;
                }//TEMP1忽略了五的倍数,当执行到5的倍数时不会+1
                else
                {
                        int KeyChar = Key;//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;
}

Hmily 发表于 2021-4-27 16:29

https://www.52pojie.cn/misc.php?mod=faq&action=faq&id=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 同学看这个学习下怎么把图片插入 ...

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

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

一看代码就脑瓜疼,怎么破
页: [1]
查看完整版本: 看雪CTF逆向练习题-[异想天开]