Reversing a Desktop IM Client with Frida - Sending Tampering and Listening to Messages
About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.
This research is published for educational and defensive purposes. The target sample is anonymized throughout.
Extending the Windows IM client is well-trodden territory; this article does the same for the macOS client of WeChat, using Frida. We’ll cover two things: sending messages and listening to incoming messages. (There’s also a static-injection approach, but I’ll skip it to keep the Xcode footprint small. This article also assumes you already know your way around Frida.)

Tools
- Hopper Disassembler
- class-dump
- Frida
- PyCharm (optional)
- VSCode (optional)
Dumping the headers
Start by dumping the client’s headers with class-dump:
class-dump -H /Applications/WeChat.app
That produces a lot of headers:
-rw-r--r-- 1 n staff 927B 2 15 19:19 WXCPbQcwxtalkPackage.h
-rw-r--r-- 1 n staff 975B 2 15 19:19 WXCPbReportItem.h
-rw-r--r-- 1 n staff 1.7K 2 15 19:19 WXCPbSCAddVoiceGroupMemberResp.h
-rw-r--r-- 1 n staff 772B 2 15 19:19 WXCPbSCCancelCreateVoiceGroupResp.h
-rw-r--r-- 1 n staff 7.2K 2 15 19:19 WXCPbSCCreateVoiceGroupResp.h
-rw-r--r-- 1 n staff 6.9K 2 15 19:19 WXCPbSCEnterVoiceRoomResp.h
-rw-r--r-- 1 n staff 1.1K 2 15 19:19 WXCPbSCExitVoiceRoomResp.h
-rw-r--r-- 1 n staff 1.2K 2 15 19:19 WXCPbSCModifyVoiceGroupInfoResp.h
-rw-r--r-- 1 n staff 872B 2 15 19:19 WXCPbSCSubscribeLargeVideoResp.h
-rw-r--r-- 1 n staff 867B 2 15 19:19 WXCPbSCSubscribeVideoResp.h
-rw-r--r-- 1 n staff 2.0K 2 15 19:19 WXCPbSCVoiceClientSceneReportResp.h
-rw-r--r-- 1 n staff 864B 2 15 19:19 WXCPbSCVoiceGetGroupInfoBatchResp.h
-rw-r--r-- 1 n staff 637B 2 15 19:19 WXCPbSCVoiceMemberWhisperResp.h
-rw-r--r-- 1 n staff 5.9K 2 15 19:19 WXCPbSCVoiceRedirectResp.h
-rw-r--r-- 1 n staff 1.1K 2 15 19:19 WXCPbSCVoiceRoomHelloResp.h
-rw-r--r-- 1 n staff 904B 2 15 19:19 WXCPbSKBuiltinBuffer_t.h
-rw-r--r-- 1 n staff 686B 2 15 19:19 WXCPbSubscribeVideoMember.h
-rw-r--r-- 1 n staff 2.7K 2 15 19:19 WXCPbSwitchVideoGroupResp.h
-rw-r--r-- 1 n staff 1.4K 2 15 19:19 WXCPbVideoGroupMember.h
-rw-r--r-- 1 n staff 671B 2 15 19:19 WXCPbVoiceClientScene.h
-rw-r--r-- 1 n staff 1.2K 2 15 19:19 WXCPbVoiceClientSceneExt.h
-rw-r--r-- 1 n staff 2.9K 2 15 19:19 WXCPbVoiceConf.h
Narrowing it down
Reading all of those one by one is hopeless. But developers name things sensibly — the vendor’s developers certainly don’t name objects abc. The client’s core function is sending messages, and “message” is “Message”. Filter on that:
# n @ localhost in ~/vscodewsp/wechat/dump [20:58:22]
$ ll |wc -l
4922
# n @ localhost in ~/vscodewsp/wechat/dump [20:58:29]
$ ll -l |grep Message|wc -l
157
4,922 files down to 157. Now apply naming habits — business logic usually lands in service / controller classes:
# n @ localhost in ~/vscodewsp/wechat/dump [20:58:42]
$ ll -l |grep Message|grep Service|wc -l
9
# n @ localhost in ~/vscodewsp/wechat/dump [21:02:13]
$ ll -l |grep Message|grep Service
-rw-r--r-- 1 n staff 5.1K 2 15 19:19 FTSFileMessageService.h
-rw-r--r-- 1 n staff 382B 2 15 19:19 IMessageServiceAppExt-Protocol.h
-rw-r--r-- 1 n staff 980B 2 15 19:19 IMessageServiceFileExt-Protocol.h
-rw-r--r-- 1 n staff 381B 2 15 19:19 IMessageServiceFileReTransferExt-Protocol.h
-rw-r--r-- 1 n staff 755B 2 15 19:19 IMessageServiceImageExt-Protocol.h
-rw-r--r-- 1 n staff 780B 2 15 19:19 IMessageServiceVideoExt-Protocol.h
-rw-r--r-- 1 n staff 407B 2 15 19:19 IMessageServiceVideoReTransferExt-Protocol.h
-rw-r--r-- 1 n staff 3.1K 2 15 19:19 MMFTSMessageService.h
-rw-r--r-- 1 n staff 20K 2 15 19:19 MessageService.h
Nine files — readable one by one. MessageService.h is the jackpot:
- (id)SendLocationMsgFromUser:(id)arg1 toUser:(id)arg2 withLatitude:(double)arg3 longitude:(double)arg4 poiName:(id)arg5 label:(id)arg6;
- (id)SendNamecardMsgFromUser:(id)arg1 toUser:(id)arg2 containingContact:(id)arg3;
- (id)SendStickerStoreEmoticonMsgFromUsr:(id)arg1 toUsrName:(id)arg2 md5:(id)arg3 productID:(id)arg4;
- (id)SendEmoticonMsgFromUsr:(id)arg1 toUsrName:(id)arg2 md5:(id)arg3 emoticonType:(unsigned int)arg4;
- (id)SendImgMessage:(id)arg1 toUsrName:(id)arg2 thumbImgData:(id)arg3 midImgData:(id)arg4 imgData:(id)arg5 imgInfo:(id)arg6;
- (id)SendTextMessage:(id)arg1 toUsrName:(id)arg2 msgText:(id)arg3 atUserList:(id)arg4;
- (id)SendAppMusicMessageFromUser:(id)arg1 toUsrName:(id)arg2 withTitle:(id)arg3 url:(id)arg4 description:(id)arg5 thumbnailData:(id)arg6;
- (id)SendAppURLMessageFromUser:(id)arg1 toUsrName:(id)arg2 withTitle:(id)arg3 url:(id)arg4 description:(id)arg5 thumbnailData:(id)arg6;
- (id)SendAppURLMessageFromUser:(id)arg1 toUsrName:(id)arg2 withTitle:(id)arg3 url:(id)arg4 description:(id)arg5 thumbUrl:(id)arg6 sourceUserName:(id)arg7 sourceDisplayName:(id)arg8;
Everything prefixed Send is a message-send function. The hard part is just finding the right header; the rest is mechanical.
Verifying with Frida
Confirm the guess with frida-trace:
$ frida-trace -m "-[MessageService Send*]" 微信
Instrumenting...
-[MessageService SendTextMessageWithString:toUser:]: Auto-generated handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/SendTextMessageWithString_toUser_.js"
-[MessageService SendAppURLMessageFromUser:toUsrName:withTitle:url:description:thumbUrl:sourceUserName:sourceDisplayName:]: Auto-generated handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/SendAppURLMessageFromUser_toUsrN_eaefd0af.js"
------------------------------------------------------------------------------
-[MessageService SendNamecardMsgFromUser:toUser:containingContact:]: Auto-generated handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/SendNamecardMsgFromUser_toUser_c_b5899e8d.js"
Started tracing 18 functions. Press Ctrl+C to stop.
frida-trace drops auto-generated handler scripts into __handlers__/. Send a message and the terminal prints:
195323 ms -[MessageService SendTextMessage:0x600000b6fae0 toUsrName:0x6503cfa934d442eb msgText:0x6000002ec860 atUserList:0x600000a73570]
SendTextMessage — exactly the header we saw. Edit that handler to confirm the arguments:
2908 ms -[MessageService SendTextMessage:0x600000b6fae0 toUsrName:0x6503cfa934d442eb msgText:0x6722df8306c2767b atUserList:0x6000009c2760]
The signature is:
- (id)SendTextMessage:(id)arg1 toUsrName:(id)arg2 msgText:(id)arg3 atUserList:(id)arg4;
Four arguments. arg1 unknown, toUsrName is the recipient, msgText is the message body, arg4 unknown. Print all four (note the ObjC self/_cmd shift — args[2] is the first declared parameter):
onEnter(log, args, state) {
console.log(`-[MessageService SendTextMessage:${args[2]} toUsrName:${args[3]} msgText:${args[4]} atUserList:${args[5]}]`);
console.log("arg[1] -> " + new ObjC.Object(args[2]))
console.log("arg[2] -> " + new ObjC.Object(args[3]))
console.log("arg[3] -> " + new ObjC.Object(args[4]))
console.log("arg[4] -> " + new ObjC.Object(args[5]))
},
Run frida-trace -m "-[MessageService Send*]" 微信 and send a message:

arg[1] -> wxid_[redacted]
arg[2] -> filehelper
arg[3] -> this is a message test
arg[4] ->
/* TID 0x307 */
14534 ms -[MessageService SendTextMessage:0x600000b6fae0 toUsrName:0x6503cfa934d442eb msgText:0x600000adefd0 atUserList:0x600000add470]
So arg1 is my own id, arg2 is the recipient, arg3 is the text. To tamper with the outbound body:
args[4] = ObjC.classes.NSString.stringWithString_("MacOS WeChat analysis")
Now whatever I type, the other side receives “MacOS WeChat analysis”. That pins down the send-text function — but how do we call it ourselves?
Reading the code in Hopper
Sending needs four parameters: (1) my own id, (2) the recipient id, (3) the message, (4) null. Drag WeChat.app/Contents/MacOS/WeChat into Hopper and search SendTextMessage.
MMMessageSendLogic:
/* @class MMMessageSendLogic */
-(unsigned char)sendTextMessageWithString:(void *)arg2 mentionedUsers:(void *)arg3 {
r14 = self;
r15 = [arg2 retain];
r12 = [arg3 retain];
r13 = [[CUtility filterStringForTextMessage:r15] retain];
[r15 release];
if ([r13 length] != 0x0) {
stack[-64] = r12;
rax = [r13 lengthOfBytesUsingEncoding:0x4];
rbx = rax;
if (rax >= 0x4001) {
rax = [[NSString alloc] initWithFormat:@"ERROR: Text too long, length: %lu, utf8 length: %lu", [r13 length], rbx];
stack[0] = "-[MMMessageSendLogic sendTextMessageWithString:mentionedUsers:]";
[MMLogger logWithMMLogLevel:0x2 module:"ComposeInputView" file:0x103e0e162 line:0x112 func:stack[0] message:rax];
[rax release];
rax = [NSBundle mainBundle];
rax = [rax retain];
stack[-72] = rax;
r15 = [[rax localizedStringForKey:@"Message.Input.Too.Long.Title" value:@"" table:0x0] retain];
rax = [NSBundle mainBundle];
rax = [rax retain];
r14 = rax;
rax = [rax localizedStringForKey:@"Message.Input.Too.Long.Content" value:@"" table:0x0];
rax = [rax retain];
[NSAlert showAlertSheetWithTitle:r15 message:rax completion:0x0];
[rax release];
[r14 release];
[r15 release];
[stack[-72] release];
r14 = 0x0;
r12 = stack[-64];
}
else {
rax = [WeChat sharedInstance];
rax = [rax retain];
r15 = [[rax CurrentUserName] retain];
[rax release];
rax = [r14 currnetChatContact];
rax = [rax retain];
r14 = [[rax m_nsUsrName] retain];
[rax release];
r12 = [[MMServiceCenter defaultCenter] retain];
objc_unsafeClaimAutoreleasedReturnValue([[[r12 getService:[MessageService class]] retain] SendTextMessage:r15 toUsrName:r14 msgText:r13 atUserList:stack[-64]]);
[rax release];
[r12 release];
[r14 release];
[r15 release];
r14 = 0x1;
r12 = stack[-64];
r13 = r13;
}
}
else {
rax = [[NSString alloc] initWithFormat:@"ERROR: Text is empty, can't send"];
stack[0] = "-[MMMessageSendLogic sendTextMessageWithString:mentionedUsers:]";
[MMLogger logWithMMLogLevel:0x2 module:"ComposeInputView" file:0x103e0e162 line:0x10c func:stack[0] message:rax];
[rax release];
r14 = 0x0;
}
[r13 release];
[r12 release];
rax = r14 & 0xff;
return rax;
}
The key line:
objc_unsafeClaimAutoreleasedReturnValue([[[r12 getService:[MessageService class]] retain] SendTextMessage:r15 toUsrName:r14 msgText:r13 atUserList:stack[-64]]);
The first argument is r15, traced back to:
r15 = [[rax CurrentUserName] retain];
And CurrentUserName:
-(void *)CurrentUserName {
if ([self isLoggedIn] != 0x0) {
rdi = [[CUtility GetCurrentUserName] retain];
}
else {
rdi = 0x0;
}
rax = [rdi autorelease];
return rax;
}
It checks login state, then calls CUtility’s GetCurrentUserName. That’s the first argument; the other three we can construct by hand.
A Frida script to send
console.log("init success");
function SendTextMessage(wxid, msg) {
var message = ObjC.chooseSync(ObjC.classes.MessageService)[0]
var username = ObjC.classes.CUtility.GetCurrentUserName();
console.log(username)
console.log("Type of arg[0] -> " + message)
var toUsrName = ObjC.classes.NSString.stringWithString_(wxid);
var msgText = ObjC.classes.NSString.stringWithString_(msg);
message["- SendTextMessage:toUsrName:msgText:atUserList:"](username, toUsrName, msgText, null);
}
SendTextMessage("filehelper", "active call to send a message!")
Save it and run:
frida 微信 --debug --runtime=v8 --no-pause -l test.js
The client sends a message on its own:

Listening to incoming messages
We’ve tampered with outbound and sent actively; now the inbound side. When a message arrives, the client shows a notification — and “notification” is “notify”. In MessageService we find - (void)notifyAddMsgOnMainThread:(id)arg1 msgData:(id)arg2;. Verify with frida:
frida-trace -m "-[MessageService notify*]" 微信
$ frida-trace -m "-[MessageService notify*]" 微信
Instrumenting...
-[MessageService notifyModMsgOnMainThread:msgData:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyModMsgOnMainThread_msgData_.js"
-[MessageService notifyAppMsgUploadProgress:msgData:uploadedBytes:totalBytes:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyAppMsgUploadProgress_msgDa_9b03499e.js"
-[MessageService notifyVideoMsgUploadProgress:msgData:uploadedBytes:totalBytes:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyVideoMsgUploadProgress_msg_e1db5f92.js"
-[MessageService notifyNewMsgNotificationOnMainThread:msgData:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyNewMsgNotificationOnMainTh_d56d83b5.js"
-[MessageService notifyChatSyncMsgsOnMainThread:msgList:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyChatSyncMsgsOnMainThread_msgList_.js"
-[MessageService notifyChatSyncMessagesMergedOnMainThread:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyChatSyncMessagesMergedOnMainThread_.js"
-[MessageService notifyRevokePatMsgOnMainThread:n64MsgId:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyRevokePatMsgOnMainThread_n64MsgId_.js"
-[MessageService notifyAddRevokePromptMsgOnMainThread:msgData:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyAddRevokePromptMsgOnMainTh_81637ebf.js"
-[MessageService notifyDelMsgOnMainThread:msgData:isRevoke:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyDelMsgOnMainThread_msgData_5bbc2297.js"
-[MessageService notifyMsgDeletedForSessionOnMainThread:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyMsgDeletedForSessionOnMainThread_.js"
-[MessageService notifyDelAllMsgOnMainThread:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyDelAllMsgOnMainThread_.js"
-[MessageService notifyAddMsgListForSessionOnMainThread:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyAddMsgListForSessionOnMainThread_.js"
-[MessageService notifyUnreadCntChangeOnMainThread:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyUnreadCntChangeOnMainThread_.js"
-[MessageService notifyMsgResendOnMainThread:msgData:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyMsgResendOnMainThread_msgData_.js"
-[MessageService notifyImgMsgUploadProgress:msgData:uploadedBytes:totalBytes:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyImgMsgUploadProgress_msgDa_e4e0cd43.js"
-[MessageService notifyAppMsgDownloadProgress:msgData:downloadedBytes:totalBytes:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyAppMsgDownloadProgress_msg_4e191704.js"
-[MessageService notifyUIAndSessionOnMainThread:withMsg:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyUIAndSessionOnMainThread_withMsg_.js"
-[MessageService notifyAddMsgOnMainThread:msgData:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyAddMsgOnMainThread_msgData_.js"
Started tracing 18 functions. Press Ctrl+C to stop.
Send a message to yourself and watch the output:
/* TID 0x307 */
157082 ms -[MessageService notifyAddMsgOnMainThread:0x6503cfa934d442eb msgData:0x7fd903c9fa00]
/* TID 0x31e17 */
157092 ms -[MessageService notifyUnreadCntChangeOnMainThread:0x6503cfa934d442eb]
/* TID 0xb5c27 */
157228 ms -[MessageService notifyModMsgOnMainThread:0x6503cfa934d442eb msgData:0x7fd903c9fa00]
notifyAddMsgOnMainThread looks like the receive method, with msgData as the payload. Check the parameter types:
frida-trace -m "-[MessageService notifyAddMsgOnMainThread*]" 微信
$ frida-trace -m "-[MessageService notifyAddMsgOnMainThread*]" 微信
Instrumenting...
-[MessageService notifyAddMsgOnMainThread:msgData:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyAddMsgOnMainThread_msgData_.js"
Started tracing 1 function. Press Ctrl+C to stop.
Type of arg[2] -> NSTaggedPointerString
Type of arg[3] -> MessageData
/* TID 0x307 */
2170 ms -[MessageService notifyAddMsgOnMainThread:0x6503cfa934d442eb msgData:0x7fd90401c960]
First is a String, second is MessageData. Find the struct in the headers:
$ ll -l|grep MessageData
-rw-r--r-- 1 n staff 2.5K 2 15 19:19 FTSFileMessageData.h
-rw-r--r-- 1 n staff 2.0K 2 15 19:19 FTSMessageData.h
-rw-r--r-- 1 n staff 794B 2 15 19:19 IMessageDataExt-Protocol.h
-rw-r--r-- 1 n staff 6.2K 2 15 19:19 MMChatMessageDataSource.h
-rw-r--r-- 1 n staff 25K 2 15 19:19 MessageData.h
-rw-r--r-- 1 n staff 550B 2 15 19:19 MessageDataGroup.h
-rw-r--r-- 1 n staff 2.9K 2 15 19:19 MessageDataPackedInfo.h
-rw-r--r-- 1 n staff 262B 2 15 19:19 NSPasteboard-MessageData.h
MessageData.h:
@interface MessageData : NSObject <NSPasteboardItemDataProvider, IAppMsgPathMgr, IMsgExtendOperation, NSCopying, WCTTableCoding, WCTColumnCoding>
{
unsigned int mesLocalID;
long long mesSvrID;
NSString *fromUsrName;
NSString *toUsrName;
unsigned int messageType;
NSString *msgContent;
NSString *msgVoiceText;
unsigned int m_uiVoiceToTextStatus;
unsigned int msgStatus;
unsigned int msgImgStatus;
NSString *msgRealChatUsr;
NSString *msgPushContent;
unsigned int m_uiTranslateStatus;
NSString *msgSource;
unsigned int mesDes;
unsigned int msgSeq;
BOOL bForward;
NSData *m_dtThumbnail;
unsigned int msgCreateTime;
unsigned int m_uiSendTime;
unsigned int m_uiDownloadStatus;
id <IMsgExtendOperation> m_extendInfoWithMsgType;
id <IMsgExtendOperation> m_extendInfoWithFromUsr;
BOOL isAutoIncrement;
BOOL m_bShouldShowAll;
BOOL m_bIsMultiForwardMessage;
BOOL m_shouldReloadOriginal;
BOOL m_bHasOriginalMessage;
unsigned int IntRes1;
unsigned int IntRes2;
unsigned int m_uiFileUploadStatus;
unsigned int m_uiOriginalImgHeight;
unsigned int m_uiOriginalImgWidth;
unsigned int m_uiSrcCreateTime;
unsigned int _m_nsMsgCrc32;
unsigned int _m_uiUploadedBytes;
unsigned int _m_uiDownloadedBytes;
unsigned int _m_uiTotalBytes;
int _m_nCdnServerRetCode;
unsigned int _m_uiResendMessageCount;
long long lastInsertedRowID;
NSString *StrRes1;
NSString *StrRes2;
MMTranslateResult *m_nsTranslationResult;
NSString *m_nsFilePath;
NSString *m_nsVideoPath;
NSString *m_nsVideoThumbPath;
NSString *dataMd5;
MessageData *m_refMessageData;
MessageDataPackedInfo *m_packedInfo;
NSString *m_nsSrcUserName;
NSString *m_nsSrcNickName;
NSString *m_nsAtUserList;
NSString *_m_nsImgFileName;
NSString *_m_nsBigFileErrMsg;
SecondMsgNode *_secondMsgNode;
MessageData *_referHostMsg;
}
The field names line up. Print them:
var MessageData = new ObjC.Object(args[3]).$ivars;
console.log("fromUsrName -> " + MessageData.fromUsrName)
console.log("toUsrName -> " + MessageData.toUsrName)
console.log("msgContent -> " + MessageData.msgContent)
$ frida-trace -m "-[MessageService notifyAddMsgOnMainThread*]" 微信
-[MessageService notifyAddMsgOnMainThread:msgData:]: Loaded handler at "/Users/n/vscodewsp/wechat/__handlers__/MessageService/notifyAddMsgOnMainThread_msgData_.js"
Started tracing 1 function. Press Ctrl+C to stop.
Type of arg[2] -> NSTaggedPointerString
Type of arg[3] -> MessageData
fromUsrName -> wxid_[redacted]
toUsrName -> filehelper
msgContent -> message listening test
/* TID 0x307 */
14909 ms -[MessageService notifyAddMsgOnMainThread:0x6503cfa934d442eb msgData:0x7fd904426980]
Inbound messages captured, sender and content intact.
Wrap-up
This walks the full loop against a desktop IM client: dump headers with class-dump, narrow the field by naming convention, confirm the candidate with frida-trace, trace the parameter sources in Hopper, then call the send path directly and hook the receive path. The reusable moves:
- class-dump →
grepby domain word →grepby layer suffix is a fast way to collapse thousands of headers into the handful that matter. frida-trace -m "-[Class prefix*]"gives you ready-made handler skeletons — edit, don’t write from scratch.- Obtain the “self” id by calling the client’s own
GetCurrentUserNamerather than hardcoding it. - The receive side is a
notify*method carrying aMessageDatastruct; dump its$ivarsto get sender, recipient, and body.
Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).
Get in touch