首页
社区
课程
招聘
[原创]某国内社交软件的消息加解密分析
2023-4-20 18:46 6105

[原创]某国内社交软件的消息加解密分析

2023-4-20 18:46
6105

首先来熟悉一下ida的使用。

首先找到打开ida,点击"debugger"-> "run" -> "local windows debugger"后,从Application中找到需要调试的程序并点击"ok",程序便会停在入口处。
图片描述

因为我们要调试的是接收数据包的加解密过程,一般来说可以从recv,WSARecv这几个常用网络API入手,这时在右边的"Module"窗口处,输入ws2查找过滤模块ws2_32.dll:
图片描述
鼠标双击选中ws2_32.dll后,可以在Module窗口中双击需要查看的模块中的API函数,比如recv函数,这时主窗口显示的就是recv的反汇编代码了。
图片描述

此时可以通过鼠标点击或者菜单下断点调试了。

同时,我们需要开启wireshark程序抓包,可以看到登录过程产生如下几个数据包。
客户端到服务器:
图片描述
服务器到客户端:
图片描述

通过对recv的反复调试,在某种执念驱使下,多次低水平重复,一点一点的推进,中间经过了多次的调试和放松,最终确定,数据包的处理主要在以下的函数中:

这里要说明2点:

  1. 因为程序是动态加载的,所以地址并不一定跟本文的图中相同。
  2. 本文是叙述性的,有很多片段是不可描述的细节总结,不可能还原全过程的所有细节。读万卷书行万里路,只看文字是无法描绘所有场景的,某些事只能在行动中定义。

recv后数据的走向如下函数所示:

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
int __thiscall sub_9E3A40(int this, int a2)
{
  volatile signed __int32 *v3; // ebx
  volatile signed __int32 *v4; // ebx
  _DWORD *v5; // eax
  int v6; // ebx
  int v7; // ecx
  int v8; // eax
  int v9; // eax
  _DWORD *v10; // eax
  int result; // eax
  int v12; // edx
  int Error; // eax
  int v14; // edi
  LONG v15; // eax
  int v16; // ecx
  unsigned int v17; // [esp-4h] [ebp-20090h]
  char v18; // [esp+0h] [ebp-2008Ch] BYREF
  int v19; // [esp+14h] [ebp-20078h]
  int pExceptionObject[4]; // [esp+28h] [ebp-20064h] BYREF
  int v21; // [esp+38h] [ebp-20054h] BYREF
  char *v22; // [esp+3Ch] [ebp-20050h] BYREF
  _DWORD v23[4]; // [esp+40h] [ebp-2004Ch] BYREF
  _DWORD *v24; // [esp+50h] [ebp-2003Ch]
  char v25[8]; // [esp+58h] [ebp-20034h] BYREF
  LPVOID lpMem[3]; // [esp+60h] [ebp-2002Ch] BYREF
  int v27; // [esp+6Ch] [ebp-20020h] BYREF
  int v28; // [esp+70h] [ebp-2001Ch]
  unsigned int v29; // [esp+74h] [ebp-20018h]
  char v30[131072]; // [esp+78h] [ebp-20014h] BYREF
  char *v31; // [esp+2007Ch] [ebp-10h]
  int v32; // [esp+20088h] [ebp-4h]
 
  v31 = &v18;
  v3 = *(volatile signed __int32 **)(this + 336);
  *(_DWORD *)(this + 336) = 0;
  v22 = (char *)this;
  *(_DWORD *)(this + 332) = 0;
  if ( v3 )
  {
    if ( !_InterlockedExchangeAdd(v3 + 1, 0xFFFFFFFF) )
    {
      (**(void (__thiscall ***)(volatile signed __int32 *))v3)(v3);
      if ( !_InterlockedExchangeAdd(v3 + 2, 0xFFFFFFFF) )
        (*(void (__thiscall **)(volatile signed __int32 *))(*v3 + 4))(v3);
    }
  }
  v4 = *(volatile signed __int32 **)(this + 348);
  *(_DWORD *)(this + 348) = 0;
  *(_DWORD *)(this + 344) = 0;
  if ( v4 )
  {
    if ( !_InterlockedExchangeAdd(v4 + 1, 0xFFFFFFFF) )
    {
      (**(void (__thiscall ***)(volatile signed __int32 *))v4)(v4);
      if ( !_InterlockedExchangeAdd(v4 + 2, 0xFFFFFFFF) )
        (*(void (__thiscall **)(volatile signed __int32 *))(*v4 + 4))(v4);
    }
  }
  v21 = 1;
  v29 = this - 12;
  v29 = sub_9E5210(sub_9E4410, 0, &v21);
  v32 = 1;
  v24 = 0;
  v5 = operator new(0x28u);
  v6 = (int)v5;
  if ( !v5 )
    unknown_libname_325();
  v17 = v29;
  *v5 = std::_Func_impl<std::_Callable_obj<nbase::WeakCallback<std::_Bind<1,void,std::_Pmf_wrap<void (__thiscall nbiz::LinkSocket::*)(int),void,nbiz::LinkSocket,int>,nbiz::LinkSocket * const &,int &>>,0>,std::allocator<std::_Func_class<void,>>,void,>::`vftable';
  sub_57E2A0(v17);
  v24 = (_DWORD *)v6;
  LOBYTE(v32) = 3;
  v7 = v19;
  if ( v19 && !_InterlockedDecrement((volatile signed __int32 *)(v19 + 8)) )
    (*(void (__thiscall **)(int))(*(_DWORD *)v7 + 4))(v7);
  v8 = sub_52C600(v25, v23);
  LOBYTE(v32) = 4;
  sub_52C680(v8);
  LOBYTE(v32) = 3;
  sub_52AFC0(v25);
  v9 = *(_DWORD *)(sub_936F30() + 176);
  if ( v9 >= 15 )
  {
    if ( v9 > 240 )
      v9 = 240;
  }
  else
  {
    v9 = 15;
  }
  v10 = (_DWORD *)sub_54DA90(v9, v9 >> 31);
  sub_8EBCB0(*v10, v10[1]);
  LOBYTE(v32) = 5;
  result = (*(int (__thiscall **)(int, char *, int, _DWORD))(*(_DWORD *)(this + 64) + 20))(this + 64, v30, 0x20000, 0);
  v12 = result;
  if ( result == -1 )
  {
    result = sub_9E2950();
    v12 = result;
  }
  v32 = 3;
  if ( v12 == -1 )
  {
    Error = WSAGetLastError();
    v14 = Error;
    if ( Error == 10035 || Error == 10036 )
      return sub_52F090(v23);
    v15 = sub_8EB5B0();
    if ( (unsigned int)(*(int (__thiscall **)(LONG))(*(_DWORD *)v15 + 16))(v15) >= 2 )
    {
      v27 = 2;
      v28 = 37;
      v29 = (unsigned int)"E:\\14_svn_branch\\src\\biz/network/socket_wrapper.h";
      sub_8EB730(&v27, "[nbiz::WouldBlock] err_code:%d", v14);
    }
    result = sub_9E3770(this - 12);
    v16 = *(_DWORD *)(this + 4);
    if ( v16 )
      result = (*(int (__thiscall **)(int, int))(*(_DWORD *)v16 + 4))(v16, 1);
  }
  else if ( v12 > 0 )
  {
    if ( !*(_BYTE *)(this + 24) )
    {
      sub_8EA440(2, "core\\link_socket_asyncsockex.cpp", 286, "Receive Message While Not Encrypted", v18);
      v22 = "Receive Message While Not Encrypted";
      std::exception::exception((std::exception *)pExceptionObject, (const char *const *)&v22);
      pExceptionObject[0] = (int)&nbase::NException::`vftable';
      pExceptionObject[3] = 5004;
      _CxxThrowException(pExceptionObject, (_ThrowInfo *)&_TI3_AVNException_nbase__);
    }
    v29 = 15;
    v28 = 0;
    LOBYTE(lpMem[0]) = 0;
    LOBYTE(v32) = 7;
    (*(void (__thiscall **)(_DWORD, char *, int, LPVOID *))(**(_DWORD **)(this + 8) + 24))(
      *(_DWORD *)(this + 8),
      v30,
      v12,
      lpMem);
    sub_55D4C0((_DWORD *)(this + 188), lpMem, 0, 0xFFFFFFFF);
    LOBYTE(v32) = 3;
    if ( v29 >= 0x10 )
      opus_repacketizer_destroy(lpMem[0]);
    sub_9E4220((_DWORD *)(this - 12));
    return sub_52F090(v23);
  }
  v32 = -1;
  if ( v24 )
    return (*(int (__stdcall **)(bool))(*v24 + 16))(v24 != v23);
  return result;
}

其中的"result = (*(int (__thiscall **)(int, char , int, _DWORD))((_DWORD *)(this + 64) + 20))(this + 64, v30, 0x20000, 0);"即跳转到recv函数中,

而" (*(void (__thiscall **)(_DWORD, char *, int, LPVOID *))(**(_DWORD **)(this + 8) + 24))(
*(_DWORD *)(this + 8),"这一行,即进入以下函数中执行:
图片描述
通过在接收数据的缓冲区地址中设置数据读写断点,发现最终程序经过多次跳转,进入如下函数:

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
_DWORD *__cdecl sub_7B4CB0(_DWORD *a1, unsigned int a2, _BYTE *a3, _BYTE *a4)
{
  _DWORD *result; // eax
  int v5; // ecx
  char v6; // bl
  int v8; // esi
  int v10; // ecx
  int v11; // edx
  int v12; // esi
  int v13; // ebx
  int v14; // ecx
  int v15; // edx
  int v16; // esi
  int v17; // ebx
  int v18; // ecx
  int v19; // edx
  int v20; // esi
  int v21; // ebx
  int v22; // ecx
  int v23; // edx
  int v24; // esi
  int v25; // ebx
  int v26; // ecx
  int v27; // edx
  int v28; // esi
  int v29; // ebx
  int v30; // ecx
  int v31; // edx
  int v32; // esi
  int v33; // ebx
  int v34; // ecx
  int v35; // edx
  int v36; // esi
  int v37; // ebx
  int v38; // edx
  int v39; // ebx
  int v40; // edx
  int v41; // ebx
  bool v42; // zf
  int v43; // edx
  int v44; // ebx
  int v45; // edx
  int v46; // ebx
  int v47; // edx
  int v48; // ebx
  int v49; // edx
  int v50; // ebx
  int v51; // edx
  int v52; // ebx
  int v53; // edx
  int v54; // ebx
  int v55; // edx
  int v56; // ebx
  unsigned int v57; // [esp+14h] [ebp+4h]
  int v58; // [esp+14h] [ebp+4h]
  int v59; // [esp+14h] [ebp+4h]
  int v60; // [esp+14h] [ebp+4h]
  int v61; // [esp+14h] [ebp+4h]
  int v62; // [esp+14h] [ebp+4h]
  int v63; // [esp+14h] [ebp+4h]
  int v64; // [esp+14h] [ebp+4h]
  int v65; // [esp+14h] [ebp+4h]
 
  result = a1;
  v5 = *a1;
  v6 = a2;
  v8 = a1[1];
  v57 = a2 >> 3;
  if ( a2 >> 3 )
  {
    do
    {
      v10 = (unsigned __int8)(v5 + 1);
      v11 = result[v10 + 2];
      v12 = (unsigned __int8)(v11 + v8);
      v13 = result[v12 + 2];
      result[v10 + 2] = v13;
      result[v12 + 2] = v11;
      *a4 = *a3 ^ LOBYTE(result[(unsigned __int8)(v11 + v13) + 2]);
      v14 = (unsigned __int8)(v10 + 1);
      v15 = result[v14 + 2];
      v16 = (unsigned __int8)(v15 + v12);
      v17 = result[v16 + 2];
      result[v14 + 2] = v17;
      result[v16 + 2] = v15;
      a4[1] = a3[1] ^ LOBYTE(result[(unsigned __int8)(v15 + v17) + 2]);
      v18 = (unsigned __int8)(v14 + 1);
      v19 = result[v18 + 2];
      v20 = (unsigned __int8)(v19 + v16);
      v21 = result[v20 + 2];
      result[v18 + 2] = v21;
      result[v20 + 2] = v19;
      a4[2] = a3[2] ^ LOBYTE(result[(unsigned __int8)(v19 + v21) + 2]);
      v22 = (unsigned __int8)(v18 + 1);
      v23 = result[v22 + 2];
      v24 = (unsigned __int8)(v23 + v20);
      v25 = result[v24 + 2];
      result[v22 + 2] = v25;
      result[v24 + 2] = v23;
      a4[3] = a3[3] ^ LOBYTE(result[(unsigned __int8)(v23 + v25) + 2]);
      v26 = (unsigned __int8)(v22 + 1);
      v27 = result[v26 + 2];
      v28 = (unsigned __int8)(v27 + v24);
      v29 = result[v28 + 2];
      result[v26 + 2] = v29;
      result[v28 + 2] = v27;
      a4[4] = a3[4] ^ LOBYTE(result[(unsigned __int8)(v27 + v29) + 2]);
      v30 = (unsigned __int8)(v26 + 1);
      v31 = result[v30 + 2];
      v32 = (unsigned __int8)(v31 + v28);
      v33 = result[v32 + 2];
      result[v30 + 2] = v33;
      result[v32 + 2] = v31;
      v34 = (unsigned __int8)(v30 + 1);
      a4[5] = a3[5] ^ LOBYTE(result[(unsigned __int8)(v31 + v33) + 2]);
      v35 = result[v34 + 2];
      v36 = (unsigned __int8)(v35 + v32);
      v37 = result[v36 + 2];
      result[v34 + 2] = v37;
      result[v36 + 2] = v35;
      a4[6] = a3[6] ^ LOBYTE(result[(unsigned __int8)(v35 + v37) + 2]);
      v5 = (unsigned __int8)(v34 + 1);
      v38 = result[v5 + 2];
      v8 = (unsigned __int8)(v38 + v36);
      v39 = result[v8 + 2];
      result[v5 + 2] = v39;
      result[v8 + 2] = v38;
      LOBYTE(v38) = a3[7] ^ LOBYTE(result[(unsigned __int8)(v38 + v39) + 2]);
      a3 += 8;
      a4[7] = v38;
      a4 += 8;
      --v57;
    }
    while ( v57 );
    v6 = a2;
  }
  v58 = v6 & 7;
  if ( (v6 & 7) != 0 )
  {
    v5 = (unsigned __int8)(v5 + 1);
    v40 = result[v5 + 2];
    v8 = (unsigned __int8)(v40 + v8);
    v41 = result[v8 + 2];
    result[v5 + 2] = v41;
    result[v8 + 2] = v40;
    v42 = v58 == 1;
    v59 = v58 - 1;
    for ( *a4 = *a3 ^ LOBYTE(result[(unsigned __int8)(v40 + v41) + 2]);
          !v42;
          *a4 = *a3 ^ LOBYTE(result[(unsigned __int8)(v55 + v56) + 2]) )
    {
      v5 = (unsigned __int8)(v5 + 1);
      v43 = result[v5 + 2];
      v8 = (unsigned __int8)(v43 + v8);
      v44 = result[v8 + 2];
      result[v5 + 2] = v44;
      result[v8 + 2] = v43;
      v42 = v59 == 1;
      v60 = v59 - 1;
      a4[1] = a3[1] ^ LOBYTE(result[(unsigned __int8)(v43 + v44) + 2]);
      if ( v42 )
        break;
      v5 = (unsigned __int8)(v5 + 1);
      v45 = result[v5 + 2];
      v8 = (unsigned __int8)(v45 + v8);
      v46 = result[v8 + 2];
      result[v5 + 2] = v46;
      result[v8 + 2] = v45;
      v42 = v60 == 1;
      v61 = v60 - 1;
      a4[2] = a3[2] ^ LOBYTE(result[(unsigned __int8)(v45 + v46) + 2]);
      if ( v42 )
        break;
      v5 = (unsigned __int8)(v5 + 1);
      v47 = result[v5 + 2];
      v8 = (unsigned __int8)(v47 + v8);
      v48 = result[v8 + 2];
      result[v5 + 2] = v48;
      result[v8 + 2] = v47;
      v42 = v61 == 1;
      v62 = v61 - 1;
      a4[3] = a3[3] ^ LOBYTE(result[(unsigned __int8)(v47 + v48) + 2]);
      if ( v42 )
        break;
      v5 = (unsigned __int8)(v5 + 1);
      v49 = result[v5 + 2];
      v8 = (unsigned __int8)(v49 + v8);
      v50 = result[v8 + 2];
      result[v5 + 2] = v50;
      result[v8 + 2] = v49;
      v42 = v62 == 1;
      v63 = v62 - 1;
      a4[4] = a3[4] ^ LOBYTE(result[(unsigned __int8)(v49 + v50) + 2]);
      if ( v42 )
        break;
      v5 = (unsigned __int8)(v5 + 1);
      v51 = result[v5 + 2];
      v8 = (unsigned __int8)(v51 + v8);
      v52 = result[v8 + 2];
      result[v5 + 2] = v52;
      result[v8 + 2] = v51;
      v42 = v63 == 1;
      v64 = v63 - 1;
      a4[5] = a3[5] ^ LOBYTE(result[(unsigned __int8)(v51 + v52) + 2]);
      if ( v42 )
        break;
      v5 = (unsigned __int8)(v5 + 1);
      v53 = result[v5 + 2];
      v8 = (unsigned __int8)(v53 + v8);
      v54 = result[v8 + 2];
      result[v5 + 2] = v54;
      result[v8 + 2] = v53;
      v42 = v64 == 1;
      v65 = v64 - 1;
      a4[6] = a3[6] ^ LOBYTE(result[(unsigned __int8)(v53 + v54) + 2]);
      if ( v42 )
        break;
      v5 = (unsigned __int8)(v5 + 1);
      v55 = result[v5 + 2];
      v8 = (unsigned __int8)(v55 + v8);
      v56 = result[v8 + 2];
      result[v5 + 2] = v56;
      result[v8 + 2] = v55;
      v42 = v65 == 1;
      v59 = v65 - 1;
    }
  }
  result[1] = v8;
  *result = v5;
  return result;
}

至此,我们以比较大的把握猜测,加解密函数算法是一个rc4算法。我们知道,rc4函数需要一个初始化密钥,于是在进入加解密函数之前设置断点,我在内存中找到了这个密钥,并手动抄了出来。初始的密钥如下:

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
const int DecryptKey[260] =
{
        0x00,0x00,0x34,0x6a,
        0x81,0xD4,0xEA,0x47,
        0xBC,0xC1,0x24,0xCA,
        0x7B,0x19,0x70,0x2F,
        0x1A,0x2A,0x68,0x62,
        0xB7,0x4E,0xFB,0x53,
        0x50,0x04,0x3A,0xD3,
        0x48,0xDD,0x71,0x4D,
        0x32,0x77,0x3F,0xEB,
        0x55,0x02,0x14,0xB6,
        0x93,0xA1,0xAB,0x03,
        0x11,0x8F,0xF1,0x57,
        0xB1,0xEE,0x52,0xB2,
        0xAC,0x7A,0xAE,0x06,
        0x86,0xF3,0x82,0xAA,
        0xBD,0x85,0xF7,0xD9,
        0x22,0xB0,0x8E,0x8A,
        0x79,0xF4,0x6C,0xE7,
        0xA4,0x9F,0xE3,0x96,
        0xFC,0x7C,0xBA,0x2C,
        0x4C,0xA3,0x80,0x98,
        0x65,0x5E,0xE6,0xAF,
        0xCB,0x58,0x67,0x1C,
        0x08,0x4F,0x3C,0x44,
        0xC9,0x6B,0xE4,0x7E,
        0x1E,0x3D,0xE8,0xA6,
        0xBB,0x5F,0x6F,0x59,
        0x97,0x9E,0xB4,0x30,
        0x2D,0x2B,0xF2,0x6E,
        0x0A,0xA7,0xE9,0x5D,
        0x29,0x5C,0x33,0xEF,
        0x91,0xE5,0x39,0x36,
        0x17,0xDF,0x31,0x45,
        0x8D,0x5B,0x05,0xF0,
        0xC8,0x09,0xA5,0xF9,
        0x84,0xC7,0x1B,0xB5,
        0xC6,0x9B,0xE2,0xC5,
        0x89,0xE0,0x9A,0x27,
        0xC4,0x40,0x0D,0x20,
        0x66,0x7F,0xA9,0xC3,
        0x07,0xB3,0x2E,0x49,
        0xFA,0xD5,0xAD,0xE1,
        0x83,0x6D,0x0E,0x43,
        0x28,0x78,0x42,0x64,
        0x8B,0x16,0x74,0x88,
        0xD8,0x1F,0xA0,0x3E,
        0x63,0x60,0xFD,0xA8,
        0xD7,0x76,0x72,0x18,
        0xDC,0x56,0xFE,0xD0,
        0x90,0xD1,0x9D,0xDB,
        0x94,0x8C,0xDA,0xDE,
        0xBE,0xCD,0xCF,0xC2,
        0x1D,0x51,0xCE,0xBF,
        0x73,0x35,0x00,0x92,
        0x7D,0xC0,0x12,0x69,
        0x26,0x38,0x4B,0xFF,
        0xED,0x9C,0x13,0xF5,
        0x15,0xD2,0xEC,0xF8,
        0xB9,0x46,0xA2,0x41,
        0xF6,0x99,0x75,0x95,
        0x87,0x23,0x10,0x61,
        0x25,0xB8,0x37,0xCC,
        0x0C,0xD6,0x01,0x0F,
        0x21,0x4A,0x54,0x3B,
        0x5A,0x0B,0x00,0x00
};

现在还有一个问题,因为我不知道这个初始化密钥来自何处,所以就面临一个问题,这个密钥是多变的吗?是否是客户指纹产生的动态密钥呢?如果是的话,那么这个密钥将毫无用处。另外,发送函数也是这样加解密的吗?于是我注册了两个账号,最终经过验证,发现该密钥是都一样的!

忘了说一声,该软件的名字叫做某网的某信,版本号4.4.1280。
图片描述

测试代码和抓包在附件中。windows版程序样本下载地址:http://www.yixin.im/


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

最后于 2024-2-3 15:58 被satadrover编辑 ,原因:
上传的附件:
收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回