首页
社区
课程
招聘
[原创]KCTF2022签到题
2022-5-12 09:01 6588

[原创]KCTF2022签到题

2022-5-12 09:01
6588

最近在看《加密与解密》,也在学习IDA的使用。

 

此题正好和书中的例子类似,一段自解密的代码,可以使用IDC脚本进行修复。

 

之前也没做过,拿到题目,首先验证了以下用户名和序列号的正确性,也发现成功以后按钮就禁用了。

 

可以想到EnableWindow函数。

 

随后随意进行输入,发现虽然是错误的,但是按钮还是禁用了。

 

这种文本框取文本的操作,一般想到的就是GetDlgItemTextA之类的。

 

直接拖进IDA看一下导入表。

 

图片描述

 

果然是有的,然后直接开始动态调试吧。

 

随意键入,然后在取文本函数的地方下个断点,点击check。

 

果然就断下了。F8往下走吧,可以看见连续的2个调用。取消断点即可。

 

图片描述

 

通过观察寄存器,可以看见这一段是用来限制输入的长度。

 

其中用户名>0,序列号<=14

 

随后F8往下走吧,走到结束即可。看下整体流程,不用关心那么多细节。

 

图片描述

 

然后就遇到了异常,还有没见过的指令aaa。抱着试一试的心态,继续F8,没想到直接就提示失败了。

 

好在有参照物,直接输入给定的用户名和序列号。走到同样的位置,发现指令大不相同。

 

图片描述

 

而且还是经过上面的call以后。那么可以断定上面的函数是用来修改代码的。

 

重新跑一遍,注意看参数。

 

0X4BC,0x195,0x4015D2。

 

图片描述

 

走进去看看,注意看寄存器,可以知道0x195和循环次数相关。也就是 0x195>>2 = 0x65

 

再看IDA吧。

 

图片描述

 

进入这个call,然后F5反编译一下。

1
2
3
4
5
6
7
8
unsigned int __cdecl sub_4017E0(_DWORD *a1, unsigned int a2, int a3)
{
  unsigned int result; // eax
 
  for ( result = a2 >> 2; result; --result )
    *a1++ ^= a3;
  return result;
}

清晰可见的代码。对指定位置循环异或一个数据。

 

这就可以用IDC解密一下了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<idc.idc>
static main()
{
    auto addr = 0x4015D2;
    auto place = 0;
    auto temp = 0;
    for(place = addr;place<0x401766;place = place+0x4)
    {
        temp = Dword(place);
        temp = temp^0x4BC;
        Message("%X\n",temp);
        PatchDword(place,temp);
    }
}

其中0x4015D2 + 0x65*4 = 0x401766

 

运行一下,然后对0x4015D2-0x401766处全部按键C进行反汇编。

 

解密以后,对比下OD相同位置处,结果一致,说明脚本是没问题的。

 

有一点需要注意的是,我们刚才是在GetDlgItemTextA处断下的,随后一直走,都是在一个函数段里面。

 

此时IDA里面按键g跳到OD里面的地址处:0x40145D

 

图片描述

 

还没有看见函数开头,继续往上翻一翻。

 

图片描述

 

可以看见类似于函数头的

1
2
push ebp
mov ebp,esp

注意看交叉引用,是从DialogFunc跳转过来的,过去看看。

 

图片描述

 

那么可以确定401340处是一个函数了。

 

按键ESC,可以跳转回来,然后在401340处按键P修改为函数。

 

图片描述

 

成功了,很多变量啥的。直接F5反编译看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
int __cdecl sub_401340(HWND hDlg)
{
  int result; // eax
  unsigned int Value; // [esp+20h] [ebp-3B8h]
  int v3; // [esp+2Ch] [ebp-3ACh]
  int v4; // [esp+34h] [ebp-3A4h]
  signed int v5; // [esp+38h] [ebp-3A0h]
  UINT v6; // [esp+3Ch] [ebp-39Ch]
  HWND hWnd; // [esp+4Ch] [ebp-38Ch]
  HWND hWnda; // [esp+4Ch] [ebp-38Ch]
  char Buffer; // [esp+54h] [ebp-384h] BYREF
  char v10[199]; // [esp+55h] [ebp-383h] BYREF
  CHAR v11; // [esp+11Ch] [ebp-2BCh] BYREF
  char v12[199]; // [esp+11Dh] [ebp-2BBh] BYREF
  char v13[36]; // [esp+1E4h] [ebp-1F4h] BYREF
  int v14[50]; // [esp+208h] [ebp-1D0h] BYREF
  CHAR String; // [esp+2D0h] [ebp-108h] BYREF
  char v16[199]; // [esp+2D1h] [ebp-107h] BYREF
  char v17[16]; // [esp+398h] [ebp-40h] BYREF
  char Destination; // [esp+3A8h] [ebp-30h] BYREF
  int v19; // [esp+3A9h] [ebp-2Fh]
  int v20; // [esp+3ADh] [ebp-2Bh]
  int v21; // [esp+3B1h] [ebp-27h]
  int v22; // [esp+3B5h] [ebp-23h]
  __int16 v23; // [esp+3B9h] [ebp-1Fh]
  char v24; // [esp+3BBh] [ebp-1Dh]
  CPPEH_RECORD ms_exc; // [esp+3C0h] [ebp-18h]
 
  v11 = 0;
  memset(v12, 0, sizeof(v12));
  LOBYTE(v14[0]) = 0;
  memset((char *)v14 + 1, 0, 0xC7u);
  Buffer = 0;
  memset(v10, 0, sizeof(v10));
  String = 0;
  memset(v16, 0, sizeof(v16));
  strcpy(v17, "www.pediy.com");
  Destination = 0;
  v19 = 0;
  v20 = 0;
  v21 = 0;
  v22 = 0;
  v23 = 0;
  v24 = 0;
  strcpy(v13, "23456781ABCDEFGHJKLMNPQRSTUVWXYZ");
  v5 = GetDlgItemTextA(hDlg, 1001, &String, 201);
  v6 = GetDlgItemTextA(hDlg, 1000, &v11, 201);
  if ( v5 > 14 || v6 == 0 )
  {
    SetDlgItemTextA(hDlg, 1001, "Wrong Serial!");
    hWnd = GetDlgItem(hDlg, 1014);
    EnableWindow(hWnd, 0);
    result = 0;
  }
  else
  {
    v14[0] = *(_DWORD *)&v16[9];
    v3 = sub_4034AD(3, (int)v14);
    if ( dword_413000 )
      sub_4017E0(&loc_4015D2, &loc_401767 - &loc_4015D2, v3);
    ms_exc.registration.TryLevel = 0;
    Value = sub_401260(&v11, v6);
    strncpy(&Destination, &String, v5 - 4);
    _ultoa(Value, &Buffer, 10);
    dword_413000 = 0;
    v4 = strcmp(&Buffer, &Destination);
    if ( v4 )
      v4 = v4 < 0 ? -1 : 1;
    if ( v4 )
      SetDlgItemTextA(hDlg, 1001, "Wrong Serial!");
    else
      SetDlgItemTextA(hDlg, 1001, "Success!");
    hWnda = GetDlgItem(hDlg, 1014);
    EnableWindow(hWnda, 0);
    ms_exc.registration.TryLevel = -2;
    result = 0;
  }
  return result;
}

还有有一些没见过的语句:ms_exc.registration.TryLevel = 0;

 

结合调试过程,推测是用来捕捉异常的,所以输错了还能提示失败。

 

经过分析,可以看见用户名经过什么操作,然后格式化为字符串,再和序列号去掉后4为进行比较。

 

此时注意看sub_401260就好了,用到用户名的文本以及长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
int __cdecl sub_401260(char *a1, int a2)
{
  int v2; // ecx
  unsigned int v3; // eax
  unsigned int v4; // eax
  unsigned int v5; // eax
  unsigned int v6; // eax
  unsigned int v7; // eax
  unsigned int v8; // eax
  unsigned int v9; // eax
  unsigned int v10; // eax
  int v11; // edx
  unsigned int i; // ecx
  char v14; // al
  int v16[256]; // [esp+0h] [ebp-404h]
 
  v2 = 0;
  do
  {
    v3 = (unsigned int)v2 >> 1;
    if ( (v2 & 1) != 0 )
      v3 ^= 0xEDB88320;
    if ( (v3 & 1) != 0 )
      v4 = (v3 >> 1) ^ 0xEDB88320;
    else
      v4 = v3 >> 1;
    if ( (v4 & 1) != 0 )
      v5 = (v4 >> 1) ^ 0xEDB88320;
    else
      v5 = v4 >> 1;
    if ( (v5 & 1) != 0 )
      v6 = (v5 >> 1) ^ 0xEDB88320;
    else
      v6 = v5 >> 1;
    if ( (v6 & 1) != 0 )
      v7 = (v6 >> 1) ^ 0xEDB88320;
    else
      v7 = v6 >> 1;
    if ( (v7 & 1) != 0 )
      v8 = (v7 >> 1) ^ 0xEDB88320;
    else
      v8 = v7 >> 1;
    if ( (v8 & 1) != 0 )
      v9 = (v8 >> 1) ^ 0xEDB88320;
    else
      v9 = v8 >> 1;
    if ( (v9 & 1) != 0 )
      v10 = (v9 >> 1) ^ 0xEDB88320;
    else
      v10 = v9 >> 1;
    v16[v2++] = v10;
  }
  while ( v2 < 256 );
  v11 = a2;
  for ( i = -1; v11; --v11 )
  {
    v14 = *a1++;
    i = v16[(unsigned __int8)(i ^ v14)] ^ (i >> 8);
  }
  return ~i;
}

好在,IDA非常给力,直接F5反编译成了高级代码。

 

直接写个Cpp吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include<iostream>
using namespace std;
int __cdecl first(char *a1, int a2)
{
  int v2; // ecx
  unsigned int v3; // eax
  unsigned int v4; // eax
  unsigned int v5; // eax
  unsigned int v6; // eax
  unsigned int v7; // eax
  unsigned int v8; // eax
  unsigned int v9; // eax
  unsigned int v10; // eax
  int v11; // edx
  unsigned int i; // ecx
  char v14; // al
  int v16[256]; // [esp+0h] [ebp-404h]
 
  v2 = 0;
  do
  {
    v3 = (unsigned int)v2 >> 1;
    if ( (v2 & 1) != 0 )
      v3 ^= 0xEDB88320;
    if ( (v3 & 1) != 0 )
      v4 = (v3 >> 1) ^ 0xEDB88320;
    else
      v4 = v3 >> 1;
    if ( (v4 & 1) != 0 )
      v5 = (v4 >> 1) ^ 0xEDB88320;
    else
      v5 = v4 >> 1;
    if ( (v5 & 1) != 0 )
      v6 = (v5 >> 1) ^ 0xEDB88320;
    else
      v6 = v5 >> 1;
    if ( (v6 & 1) != 0 )
      v7 = (v6 >> 1) ^ 0xEDB88320;
    else
      v7 = v6 >> 1;
    if ( (v7 & 1) != 0 )
      v8 = (v7 >> 1) ^ 0xEDB88320;
    else
      v8 = v7 >> 1;
    if ( (v8 & 1) != 0 )
      v9 = (v8 >> 1) ^ 0xEDB88320;
    else
      v9 = v8 >> 1;
    if ( (v9 & 1) != 0 )
      v10 = (v9 >> 1) ^ 0xEDB88320;
    else
      v10 = v9 >> 1;
    v16[v2++] = v10;
  }
  while ( v2 < 256 );
  v11 = a2;
  for ( i = -1; v11; --v11 )
  {
    v14 = *a1++;
    i = v16[(unsigned __int8)(i ^ v14)] ^ (i >> 8);
  }
  return ~i;
}
int main()
{
    string s = "KCTF";
    int value = first((char*)s.c_str(),s.length());
    char Buffer[10];
    _ultoa(value, Buffer, 10);
    cout<<Buffer<<endl;
}

没想到完全不用改什么,编译就通过了。

 

输出为1592086348 10位

 

带入461D7AD538F179EE,可以得到4040328634 也是10位,正好就是给定序列号的前10位。

 

那就说明这一段是正确的,直接带入1592086348 + 给定序列号的后4位看看,成功了。

 

真是让人感叹,IDAYYDS,太好用了。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞0
打赏
分享
最新回复 (2)
雪    币: 296
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_hxjvlycc 2022-7-8 11:24
2
0
小白请教一个问题,
    strncpy(&Destination, &String, v5 - 4);
    _ultoa(Value, &Buffer, 10);
为什么你的这两个的函数符号可以显示,我的还是sub_xxxx,用的是看雪工具包中的IDA6.8,已经加载了基础的微软符号包,大神能帮忙解答一下吗?
雪    币: 3205
活跃值: (1927)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
yyjeqhc 1 2022-7-9 15:42
3
0
mb_hxjvlycc 小白请教一个问题, strncpy(&Destination, &String, v5 - 4); _ultoa(Value, &Buffer, 10); ...
我用的是IDA7.5,版本不同,能识别的库函数有较大区别。
游客
登录 | 注册 方可回帖
返回