-
-
[原创]某国内社交软件的消息加解密分析
-
发表于: 2023-4-20 18:46 6613
-
首先来熟悉一下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点:
- 因为程序是动态加载的,所以地址并不一定跟本文的图中相同。
- 本文是叙述性的,有很多片段是不可描述的细节总结,不可能还原全过程的所有细节。读万卷书行万里路,只看文字是无法描绘所有场景的,某些事只能在行动中定义。
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/
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
- RC4算法解析 5715
- [原创]Android应用之【隐藏桌面图标的一种方法】 4464
- [调查]闲下来不知道该做什么 7955