preface
This article is based on anhkgg big man's article "Research on wechat PC end technology (2) - win voice", the original link: [original] Research on wechat PC technology (2) - save chat voice - Software Reverse - Snow watching Forum - Security Community | security recruitment | bbs.pediy.com
In this article, the anhkgg boss found the interface to save voice messages. Here, the relevant feature codes are directly given for easy location (the wechat version I use is still 2.6.8.52)
The offset is 0x30E326, and the following characteristic code
Copy code behind code 67E3E319 C745 FC 0100000>mov dword ptr ss:[ebp-0x4],0x1 67E3E320 FF77 34 push dword ptr ds:[edi+0x34] ; length 67E3E323 FF77 30 push dword ptr ds:[edi+0x30] ; content 67E3E326 E8 85F07300 call WeChatWi.6857D3B0 67E3E32B 8D85 58FFFFFF lea eax,dword ptr ss:[ebp-0xA8] 67E3E331 50 push eax 67E3E332 E8 090E0000 call WeChatWi.67E3F140
Copy code behind code C745 FC 01000000 FF77 ?? FF77 ?? E8 ???????? 8D85 ???????? 50 E8 ????????
Correlation extension based on saved speech
In fact, this place not only has voice messages, but also picture messages. When we send a picture message
The content of [edi+0x30] stores the relevant data of the picture sent this time, including a series of original data such as wechat ID. Of course, we can write HOOK in this place to save pictures, but it's not necessary. Because there are too many messages here, it will be relatively troublesome to process.
Related processes of image processing
Since this place is the most original message content, the message will be processed later. And we already know that the pictures received by wechat will be saved locally by XOR encryption. Then we might as well guess the processing flow related to the picture.
First, after receiving the original message, a series of processing will be carried out on the message, including judging whether the message is a picture. If it is a picture, the picture data will be taken out and the picture will be encrypted in memory. After the encryption completes, the API of the file operation is called, and the encrypted picture is written to the local.
The whole process is shown in the figure:
Ideas about automatically saving pictures
Now that we know the process of image processing and have a call to receive image messages, we can find the algorithm and function to encrypt the image after receiving the image message and before CreateFileW creates the image, and save the image before encryption.
Save chat pictures in practice
Find the call to save voice in OD, send a picture message to disconnect the program, and disconnect CreateFileW at the same time. After that, F9 runs
At this time, the file path is xlog, which obviously does not meet our requirements. Continue to run F9
When the Image path with the Image keyword is always found, the Image is created
At this point, click K to display the stack, find the return address of the first layer, and right-click to display the call
When wechat runs here, the image encryption has been completed. We need to find the image encryption algorithm before this function. In fact, it is a little above, and you can see it by turning the mouse up a little
After finding this place, clear all the remaining breakpoints and keep only this one
Correlation analysis of saved picture call
Send a picture again and the program will be disconnected. This code first encrypts the picture in a circular way. The number of cycles is the value of ecx, that is, the size of the picture. There are two important data.
Let's first check the contents of [ebp-0x14] in memory. It's actually very simple to know what this data is. Go to the CreateFileW breakpoint first
When the CreateFileW breakpoint is broken, execute to return and view the open file handle
At this time, the open picture handle is 0xF80, and then the WriteFile breakpoint is set
After the WriteFile is disconnected, you can see that the handle is F80, the written buffer address is 39FE820, and the address of [ebp-0x14] is exactly 39FE820. That is to say, [ebp-0x14] stores the encrypted picture data
Return to the previous breakpoint and send a picture again to view the data of [ebp-0x4]
At this time, the received picture is saved in [ebp-0x4], and ecx saves the size of the picture
Here, use PCHunter view - > process memory to dump this data
The problem is that this is just a 4KB thumbnail. Go back to OD and press F9 again
The breakpoint is broken, but the value of ecx becomes 0x5A140
Use the same method to dump the memory. The size is 360KB. This is the original figure we need.
In other words, this place will be brought down twice. The first time is the thumbnail, and the second time is the original picture we want.
Code to save chat pictures
The example code is as follows:
Copy code behind code void HookSaveImages() { DWORD dwBaseAddress = (DWORD)GetModuleHandle(TEXT("WeChatWin.dll")); //The address of the hook is required SaveImageAddress = dwBaseAddress + SaveImages; //Jump back to your address SaveImageAddressBackAddress = SaveImageAddress + 5; //Assembly jump data BYTE jmpCode[5] = { 0 }; jmpCode[0] = 0xE9; //Calculate offset *(DWORD*)& jmpCode[1] = (DWORD)FnSaveImages - SaveImageAddress - 5; // Save previous properties for restore DWORD OldProtext = 0; // Because you want to write data to the code segment, and because the code segment is not writable, you need to modify the properties VirtualProtect((LPVOID)SaveImageAddress, 5, PAGE_EXECUTE_READWRITE, &OldProtext); //Write your own code memcpy((void*)SaveImageAddress, jmpCode, 5); // Restore is required after the operation VirtualProtect((LPVOID)SaveImageAddress, 5, OldProtext, &OldProtext); }
Copy code behind code __declspec(naked) void FnSaveImages() { __asm { mov ebx, dword ptr ss : [ebp - 0x4]; mov ImageData, ebx; mov ImageDataLen, ecx; pushad; pushfd; } //Call the function that receives the message FnSaveImagesCore(); //Restore site __asm { popfd popad //Jump back to the next instruction that is the HOOK instruction jmp SaveImageAddressBackAddress; } }
Copy code behind code void FnSaveImagesCore() { //If the picture is longer than 10KB, save it if (ImageDataLen >= 10240) { //Get temporary folder directory char temppath[MAX_PATH] = { 0 }; GetTempPathA(MAX_PATH, temppath); char imagedir[20] = { "WeChatRecordImages" }; //Splice catalog char WeChatExpressionsPath[MAX_PATH] = { 0 }; sprintf_s(WeChatExpressionsPath, "%s%s\\