99ss Desktop App Analysis: Bypass Anti-MITM and HTTP payload encryption
It’s not that I can’t find the admission ticket, but it’s a bit painful to announce the results during the holiday. [White Eyes] Originally, if you can’t find the admission ticket, you can go to a magical website, but when you want to check it these two days, that website has been shut down, revealing the 404 of the cloud service provider. Looking at the issue of that project, it seems that 99ss was updated by the way when the results were released in June, so this website is not easy to use. I had no choice but to download the 99 customer service client by myself and experience it. By the way, I got rid of this copper-smelling software and made it a web version, which is convenient for the general public.
In fact, the baby just wanted to be an honest and good boy, and didn’t want to make this software, but because there is a ProxyCap in the virtual machine that cannot be turned off, it is in the blacklist of 99ss.** I want to check the results, but the programs installed on the computer are scanned? You are awesome! ** The original intention was just to grab a bag and look at the request, and it would be over. This is simply forcing people to play hard, isn’t it?
This software does not have any encryption or packing process, and can be directly loaded into OD. Scan the string “incompatible software” to find the key point, put a few exit(1)
nop off.
Little eggs. When I was wondering why it wouldn’t let me use ProxyCap, I searched for proxy in the string, and then followed up xref to see the list. The disassembly results are as follows. Offended a group of friends and businessmen.
// ...
v78 = "AdClean LSP Filter";
v50 = "AdClean LSP Filter";
v2 = 0;
v88 = a2;
// 杀毒软件
lpMultiByteStr = "Dr.COM";
// 水果同步
v75 = "Itunes";
// 虚拟机
v76 = "vmware";
// 杀毒软件
v77 = "AVSDA";
// 迅雷加速器
v28 = "XLacc";
// 迅雷加速器
v29 = "XLNetAcc";
// 迅游加速器
v30 = "xunyou";
// 网易UU (广告:个人觉得超好用)
v31 = "Netease UU";
// 国外加速器(?)
v32 = "GameCap";
v33 = "NGC-TcpFilter";
v34 = "NGC-UdpFilter";
// 唔,又是友商
v35 = "海豚加速器";
v36 = "xrush_socks";
// 应该是TX游戏助手
v37 = "QQVIPLSP";
v38 = "IERD_TGP_LSP";
v39 = "Easy2Game";
// 这也不行??
v40 = "XunLei Net Monitor";
v41 = "iKu Smart Network";
v42 = "ttslsp";
v43 = "SangforLSP";
v44 = "TGPLSP";
v45 = "VMCI sockets";
v46 = "MSAFD RfComm [Bluetooth]";
v47 = "FunAccelerator";
v48 = "MSAFD RSVP";
v49 = "MSAFD IPLAYER GAMELSP";
v51 = "HomeSafe LSPRAW";
v52 = "HomeSafe LSPTCP";
v53 = "HomeSafe LSPUDP";
v54 = "GameLSP";
v55 = "EvilLSP";
v56 = "RainSoft IP";
v57 = "rtwspi";
v58 = "Hyper-V RAW";
v59 = "GsLSP";
v60 = "LSP Pro";
v61 = "iKu Smart Network LSP";
// proxy
v62 = "PROXIFIER LSP";
v63 = "GameLSP IPLayer";
// proxy
v64 = "ASProxy";
v65 = "LavasoftLSP";
v66 = "TESTLSP";
v67 = "TenLSP";
// 也是迅雷
v68 = "XLNetAccLsp";
v69 = "SinforLSP";
v70 = "Network Tunnel Layered IP";
v71 = "XLNet";
// 唔
v72 = "Letv Network";
// 磨灭了一个输入法的野心
v73 = "SogouIpFilter";
// ...
hehe. A company appointed to check the grades of CET-4 and CET-6 insists on forcing a function that can be done on the web page into a client, and also bundles a garbage accelerator (reducer) module, and prevents people from using these other software.** The smell of copper can be smelled through the screen. ** The feeling of being appointed is just different.
Then I entered the main interface, checked a person casually, proxycap + mitmproxy stopped the request, looked at it and found that the request timed out… I changed the timeout to 0xFF, and then skipped the judgment of the query interval, and all went up Alibaba Cloud still cares about this access pressure, is it stingy or not?
The biggest difficulty in reverse engineering is how to judge which function is used for what. Here I would like to thank the 99ss code farmer for his posture level. The program is so concise and clear that it only needs to give you the source code. Here is the disassembly of the key parts.
// sub_40D2C0
default:
AfxMessageBox("无法找到对应的准考证号,请联系客服", 0, 0);
stopconn(&sock);
break;
}
}
else
{
AfxMessageBox("服务器响应错误,请稍后尝试", 0, 0);
stopconn(&sock);
}
}
else
{
AfxMessageBox("连接服务器失败,请稍后尝试", 0, 0);
stopconn(&sock);
}
}
else
{
AfxMessageBox("分配内存失败,请重启尝试", 0, 0);
}
}
The stopconn is guessed by calling the winapi of the socket connection inside, then the else is wrong, and the function in the if is the correct processing!
[Did you know] The coders of the 99 dormitory customer service system, ah, no programmers, are all proficient in the HTTP network protocol, and can even use nc to chat and laugh with the server. When they need to connect to the Internet, it is easy to write HTTP requests one by one by hand, link raw socket, sprintf is smooth and smooth, and look at their code, you will benefit a lot!
//add computer macaddr to post parameters
strlen = (int)&strs[sprintf(&strs[(_DWORD)¶ms], "&m=%s", &macaddr)];
calcmagic((char *)&tmp2);
if ( encryptmessage((char *)&tmp2, strlen, ¶ms) )
{
*(_DWORD *)ArgList = 17;
startconn((int)&sock);
LOBYTE(v84) = 6;
if ( postmessage((int)"http://find.cet.99sushe.com/search", ¶ms, strlen, retstatus, (int *)ArgList) == 1 )
{
if ( *(_DWORD *)ArgList == 1 || *(_DWORD *)ArgList == 17 )
{
retstatus[*(_DWORD *)ArgList] = 0;
switch ( atoi(retstatus) )
{
case 2:
AfxMessageBox("参数错误,请联系客服", 0, 0);
Sure enough, after following up, the function in if encryptmessage
The parameters with are calcmagic
The password generated by the function. The principle of this function is a bit like the automaton seen in cryptography. In fact, it can generate infinitely long random passwords. I wrote the py version, the link is to be added, the 99cs coder copied this function, changed the parameters inside to generate a new 8-digit ascii password, and then called it a version update. Hehe, I will also change the parameters in the future.
I have been stuck here for a long time. After the encryption function entered, I passed the two parameters to the three functions, and the three functions called five, six, seven or eight sub-functions. The series of bit operations inside made me calm down and start Thinking about life, I just want to check a test number, dear…I…
Then I opened QQ and asked my test number on the back desk, and then I found a random place to check the results.
End.
No wonder. Until I accidentally turned to this when I was flipping through the code.
Prosperous in a trance, I seem to have suddenly realized the true meaning of reverse.
Search string ah dear!
I found the openssl 1.0.0a version on the Internet, the 2010 version, tsk. It looks like it should be statically linked in. OD identification is hopeless, IDA has a FLAIR (Fast Library Acquisition for Identification and Recognition) that can automatically generate a library feature file, the general process is as follows (pasted from README)
Typical scenario of a signature creation is: - run a parser and create pattern (PAT) files - run sigmake and get EXC file with collisions - edit EXC file and resolve collisions - run sigmake again and get SIG file - repeat the above 2 steps till collisions exist - run zipsig and get compressed SIG file
In fact, it is three orders.
- link Unzip all obj modules in lib
- plb extracts the features of the module and takes out the names of functions
- sigmake removes the features of duplicate functions/modules from all modules and packs them into the signature of the entire lib
under linux ar
The command seems to be not so friendly to the lib of win. I don’t know why, but it can’t be decompressed. Broken IDA can only read, can decompress it but won’t give you the file, you little devil. I can’t run away and downloaded the most annoying one. VC++6.0 简体中文精简版
, use it to bring link
Command to decompress module by module. In the end, I couldn’t stand it anymore and ran to find a batch script. Toss a sig loaded. The world instantly brightened.
Encryption function part:
char *__usercall encryptmessage@<eax>(char *key@<ecx>, size_t len@<edi>, char *text)
{
char *pkey; // ebx@1
char *rststr; // eax@1
char *pstrstr; // esi@1
char *v6; // ecx@2
int v7; // edx@2
void *v8; // ebx@2
unsigned int v9; // [sp+0h] [bp-A8h]@0
int v10; // [sp+Ch] [bp-9Ch]@2
void *ptxt; // [sp+10h] [bp-98h]@1
char v12; // [sp+14h] [bp-94h]@2
char *pkey_1; // [sp+98h] [bp-10h]@2
int v14; // [sp+9Ch] [bp-Ch]@2
pkey = key;
ptxt = text;
rststr = (char *)operator new[](v9);
pstrstr = rststr;
if ( rststr )
{
v6 = *(char **)pkey;
v7 = *((_DWORD *)pkey + 1);
v10 = 0;
pkey_1 = v6;
v14 = v7;
DES_set_odd_parity((int)&pkey_1);
DES_set_key_checked((int)&pkey_1, (int)&v12);
v8 = ptxt;
DES_cfb64_encrypt((char *)ptxt, pstrstr, len, (int)&v12, (int)&pkey_1, &v10, 1);
memcpy_0(v8, pstrstr, len);
operator delete(pstrstr);
rststr = (char *)1;
}
return rststr;
}
oh. Amazing.
Then a DES encryption chant. Put the dog to search, CFB64 encryption is still the hidden plot of libopenssl! It once again confirmed my judgment—the programmers in the 99 dormitory are all masters!
oh! Amazing!
About the end of the analysis, I found the GH code library of the original magic website to see how much it can be used. After reading it again, I found that it is all-purpose. Just change your password.
Oh yeah!
Looking back at the code carefully, I want to admire how the unknown master wrote the encryption and decryption part. turn out,
if not libcrypto_path:
from ctypes.util import find_library
libcrypto_path = find_library('crypto')
if not libcrypto_path:
raise Exception('libcrypto(OpenSSL) not found')
self.libcrypto = CDLL(libcrypto_path)
oh. Amazing.
python, so amazing.
After deploying venv, I quickly opened it to check my admission ticket…no. Okay, then I’ll check my back table. Found it. But my exam admission ticket on the back table doesn’t start with 5… I ordered it again. Well, it doesn’t start with 8 either… It seems that something is wrong.
I looked at the request with mitmproxy, and it looks like this.
POST /search HTTP/1.1
Host: find.cet.99sushe.com
Content-Length: 77
User-Agent: python-requests/2.7.0 CPython/2.7.10 Darwin/15.6.0
Connection: keep-alive
Accept: */*
Accept-Encoding: gzip, deflate
...
The request sent by the program is like this.
POST /search HTTP/1.0
host: find.cet.99sushe.com
Accept: */*
Connection: keep-alive
Content-Length: 71
...
intuitive judgment is User-Agent
pot. Try to change to an empty UA, the returned result is still a random number. Then use mitmproxy to remove the UA field completely, and the returned result is correct.
So I discovered a wonderful fact that python’s Requests library can’t make a request header that doesn’t contain UA. The cause is, requests.utils.default_headers
It defines the default header, which will contain requests and version number as the default UA. This is troublesome. If you don’t use requests, you need to use又臭又长 urllib2 rewrites the request part, the original one-sentence thing needs to be expanded into one function so much? God.
Alas, in virtualenv anyway. Bundle default_user_agent
Comment it out there. lazy [cover face]
In fact, it is not very laborious to write the code by yourself, but one is on vacation, and the other is that the focus of learning is not on the code, (I can’t think of such a weird way to call libcrypto). It’s not about playing MC.