From 07ad7b3c2306b785a8763bdbf964287c9218adbc Mon Sep 17 00:00:00 2001 From: ACh Sulfate Date: Fri, 14 Jul 2023 21:35:54 +0800 Subject: [PATCH] fix: RevokeMsgHook group msg for NT 4194 --- app/src/main/cpp/CMakeLists.txt | 2 + app/src/main/cpp/ntkernel/NtRecallMsgHook.cc | 373 +++++++++++------- app/src/main/cpp/utils/AobScanUtils.cc | 87 ++++ app/src/main/cpp/utils/AobScanUtils.h | 114 ++++++ app/src/main/cpp/utils/ElfScan.h | 2 + app/src/main/cpp/utils/arch_utils.cc | 30 ++ app/src/main/cpp/utils/arch_utils.h | 10 + .../java/cc/ioctl/hook/msg/RevokeMsgHook.java | 134 +++++-- .../io/github/qauxv/bridge/ContactUtils.java | 44 +++ 9 files changed, 626 insertions(+), 170 deletions(-) create mode 100644 app/src/main/cpp/utils/AobScanUtils.cc create mode 100644 app/src/main/cpp/utils/AobScanUtils.h create mode 100644 app/src/main/cpp/utils/arch_utils.cc create mode 100644 app/src/main/cpp/utils/arch_utils.h diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 83f3f01210..d726ebf6cd 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -67,6 +67,8 @@ add_library(qauxv SHARED utils/MemoryUtils.cc utils/ConfigManager.cc utils/ElfScan.cc + utils/AobScanUtils.cc + utils/arch_utils.cc ntkernel/NtRecallMsgHook.cc diff --git a/app/src/main/cpp/ntkernel/NtRecallMsgHook.cc b/app/src/main/cpp/ntkernel/NtRecallMsgHook.cc index c5b6d98351..f63b57b332 100644 --- a/app/src/main/cpp/ntkernel/NtRecallMsgHook.cc +++ b/app/src/main/cpp/ntkernel/NtRecallMsgHook.cc @@ -11,10 +11,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -26,70 +28,31 @@ #include "utils/ProcessView.h" #include "utils/ThreadUtils.h" #include "utils/TextUtils.h" -#include "utils/ElfScan.h" +#include "utils/AobScanUtils.h" #include "utils/MemoryUtils.h" +#include "utils/arch_utils.h" #include "qauxv_core/natives_utils.h" +#ifndef STACK_GUARD +// for debug purpose only +#define STACK_GUARD ((void) 0) +#endif + namespace ntqq::hook { using namespace qauxv; using namespace utils; -/** - void RecallC2cSysMsg(void * param_1, void ** param_2, void * param_3) - - a01604234 fd 7b ba a9 stp x29,x30,[sp, #local_60]! - a01604238 91 16 00 94 bl _prologue_save_regs_a01609c7c - a0160423c ff c3 0f d1 sub sp,sp,#0x3f0 - a01604240 54 d0 3b d5 mrs x20,tpidr_el0 - a01604244 88 16 40 f9 ldr x8,[x20, #0x28] - a01604248 a8 03 1f f8 stur x8,[x29, #-0x10]=>DAT_fffffffffffffff0 - a0160424c 48 00 40 f9 ldr x8,[x2] - a01604250 48 52 00 b4 cbz x8,LAB_a01604c98 - TAG_C2C_KEY_START - a01604254 09 8d 40 f8 ldr x9,[x8, #0x8]! - a01604258 f5 03 00 aa mov x21,x0 - a0160425c 21 00 80 52 mov w1,#0x1 - a01604260 f3 03 02 aa mov x19,x2 - a01604264 29 8d 40 f9 ldr x9,[x9, #0x118] - TAG_C2C_KEY_END - [ 09 8d 40 f8 f5 03 00 aa 21 00 80 52 f3 03 02 aa 29 8d 40 f9 ] - */ -static constexpr uint8_t kTraitRecallC2cSysMsg[] = - {0x09, 0x8d, 0x40, 0xf8, 0xf5, 0x03, 0x00, 0xaa, 0x21, 0x00, 0x80, 0x52, 0xf3, 0x03, 0x02, 0xaa, 0x29, 0x8d, 0x40, 0xf9}; -static constexpr uint64_t kTraitOffsetRecallC2cSysMsg = 4 * 8; - -/** - void __cdecl RecallGroupSysMsg(void * param_1, void * param_2, void * param_3) - - a01605904 fd 7b ba a9 stp x29,x30,[sp, #local_60]! - a01605908 dd 10 00 94 bl _prologue_save_regs_a01609c7c - a0160590c ff 83 0d d1 sub sp,sp,#0x360 - a01605910 bc 12 00 94 bl FUN_a0160a400 - a01605914 c6 12 00 94 bl FUN_a0160a42c - a01605918 a8 03 1f f8 stur x8,[x29, #-0x10]=>DAT_fffffffffffffff0 - TAG_GROUP_KEY_START - a0160591c 28 00 40 f9 ldr x8,[x1] - a01605920 61 00 80 52 mov w1,#0x3 - a01605924 09 8d 40 f8 ldr x9,[x8, #0x8]! - a01605928 29 8d 40 f9 ldr x9,[x9, #0x118] - TAG_GROUP_KEY_END - a0160592c 53 12 00 94 bl __call__FUN_a0160a278 - msg_common::Msg::kBody - a01605930 00 04 00 36 tbz w0,#0x0,LAB_a016059b0 - - [ 28 00 40 f9 61 00 80 52 09 8d 40 f8 29 8d 40 f9 ] - */ -static constexpr uint8_t kTraitRecallGroupSysMsg[] = - {0x28, 0x00, 0x40, 0xf9, 0x61, 0x00, 0x80, 0x52, 0x09, 0x8d, 0x40, 0xf8, 0x29, 0x8d, 0x40, 0xf9}; -static constexpr uint64_t kTraitOffsetRecallGroupSysMsg = 4 * 6; - static bool sIsHooked = false; EXPORT extern "C" void* gLibkernelBaseAddress = nullptr; jclass klassRevokeMsgHook = nullptr; -jmethodID handleC2cRecallMsgFromNtKernel = nullptr; +jobject gInstanceRevokeMsgHook = nullptr; +jmethodID handleRecallSysMsgFromNtKernel = nullptr; + +uintptr_t gOffsetGetDecoderSp = 0; + uint64_t ThunkGetInt64Property(const void* thiz, int property) { // vtable @@ -106,7 +69,7 @@ uint32_t ThunkGetInt32Property(const void* thiz, int property) { void* thisp8 = reinterpret_cast(reinterpret_cast(thiz) + 8); uintptr_t vtable = *reinterpret_cast(thisp8); void* func = *reinterpret_cast(vtable + 0x38); - return reinterpret_cast(func)(thisp8, property); + return reinterpret_cast(func)(thisp8, property); } std::string ThunkGetStringProperty(void* thiz, int property) { @@ -118,6 +81,54 @@ std::string ThunkGetStringProperty(void* thiz, int property) { return reinterpret_cast(func)(thisp8, property); } +template +requires((std::is_same_v || std::is_integral_v || std::is_pointer_v) + && ((std::is_integral_v || std::is_pointer_v) && ...)) +ReturnType vcall(void* thiz, ArgTypes... args) { + // vtable + // [[this+thizOff]+offsetVT] + void* thisp8 = reinterpret_cast(reinterpret_cast(thiz) + thizOffset); + uintptr_t vtable = *reinterpret_cast(thisp8); + void* func = *reinterpret_cast(vtable + vtableOffset); + if constexpr (std::is_same_v) { + reinterpret_cast(func)(thisp8, args...); + return; + } else { + return reinterpret_cast(func)(thisp8, args...); + } +} + +template +requires(((std::is_integral_v || std::is_pointer_v) && ...)) +void vcall_x8(void* thiz, void* x8, ArgTypes... args) { + // vtable + // [[this+thizOff]+offsetVT] + void* thisp8 = reinterpret_cast(reinterpret_cast(thiz) + thizOffset); + uintptr_t vtable = *reinterpret_cast(thisp8); + void* func = *reinterpret_cast(vtable + vtableOffset); + static_assert(sizeof...(args) <= 3); + std::array argArray = {reinterpret_cast(args)...}; + call_func_with_x8(func, x8, thisp8, argArray[0], argArray[1], argArray[2]); +} + +// helper for uintptr_t as this +template +requires((std::is_same_v || std::is_integral_v || std::is_pointer_v) + && ((std::is_integral_v || std::is_pointer_v) && ...)) +static inline ReturnType vcall(uintptr_t thiz, ArgTypes... args) { + if constexpr (std::is_same_v) { + vcall(reinterpret_cast(thiz), args...); + return; + } else { + return vcall(reinterpret_cast(thiz), args...); + } +} + +template requires((std::is_integral_v || std::is_pointer_v) && ...) +static inline void vcall_x8(uintptr_t thiz, void* x8, ArgTypes... args) { + vcall_x8(reinterpret_cast(thiz), x8, args...); +} + //void ThunkCallAPI(void* x0, uintptr_t api_caller_id, int x2, int x3, int& x4, std::string& x5) { // // 4160. 0x00cc0750 // // "CallAPI" @@ -136,28 +147,16 @@ class RevokeMsgInfoAccess { }; -void (* sOriginHandleGroupRecallSysMsgCallback)(void*, void*, void*) = nullptr; -void HandleGroupRecallSysMsgCallback(void* p1, void* p2, void* p3) { - if (p3 == nullptr || *(void**) p3 == nullptr) { - LOGE("HandleRecallGroupSysMsgCallback p3 == null, todo, wip, return"); - return; - } - // TODO: get group id, etc -} - -void (* sOriginHandleC2cRecallSysMsgCallback)(void*, void*, void*) = nullptr; - -void NotifyRecallMsgEventForC2c(const std::string& fromUid, const std::string& toUid, - uint64_t random64, uint64_t timeSeconds, - uint64_t msgUid, uint64_t msgSeq, uint32_t msgClientSeq) { +void NotifyRecallSysMsgEvent(int chatType, const std::string& peerUid, const std::string& recallOpUid, const std::string& toUid, + uint64_t random64, uint64_t timeSeconds, uint64_t msgUid, uint64_t msgSeq, uint32_t msgClientSeq) { JavaVM* vm = HostInfo::GetJavaVM(); if (vm == nullptr) { - LOGE("NotifyRecallMsgEventForC2c fatal vm == null"); + LOGE("NotifyRecallSysMsgEvent fatal vm == null"); return; } if (klassRevokeMsgHook == nullptr) { - LOGE("NotifyRecallMsgEventForC2c fatal klassRevokeMsgHook == null"); + LOGE("NotifyRecallSysMsgEvent fatal klassRevokeMsgHook == null"); return; } // check if current thread is attached to jvm @@ -166,20 +165,19 @@ void NotifyRecallMsgEventForC2c(const std::string& fromUid, const std::string& t jint err = vm->GetEnv((void**) &env, JNI_VERSION_1_6); if (err == JNI_EDETACHED) { if (vm->AttachCurrentThread(&env, nullptr) != JNI_OK) { - LOGE("NotifyRecallMsgEventForC2c fatal AttachCurrentThread failed"); + LOGE("NotifyRecallSysMsgEvent fatal AttachCurrentThread failed"); return; } isAttachedManually = true; } else if (env == nullptr) { - LOGE("NotifyRecallMsgEventForC2c fatal GetEnv failed, err = {}", err); + LOGE("NotifyRecallSysMsgEvent fatal GetEnv failed, err = {}", err); return; } // call java method - env->CallStaticVoidMethod(klassRevokeMsgHook, handleC2cRecallMsgFromNtKernel, - env->NewStringUTF(fromUid.c_str()), - env->NewStringUTF(toUid.c_str()), - (jlong) random64, (jlong) timeSeconds, (jlong) msgUid, - (jlong) msgSeq, (jint) msgClientSeq); + env->CallStaticVoidMethod(klassRevokeMsgHook, handleRecallSysMsgFromNtKernel, + jint(chatType), env->NewStringUTF(peerUid.c_str()), env->NewStringUTF(recallOpUid.c_str()), + env->NewStringUTF(toUid.c_str()), jlong(random64), jlong(timeSeconds), + jlong(msgUid), jlong(msgSeq), jint(msgClientSeq)); // check if exception occurred if (env->ExceptionCheck()) { env->ExceptionDescribe(); @@ -191,7 +189,111 @@ void NotifyRecallMsgEventForC2c(const std::string& fromUid, const std::string& t } } -void HandleC2cRecallSysMsgCallback(void* p1, void* p2, void* p3) { +void NotifyRecallMsgEventForC2c(const std::string& fromUid, const std::string& toUid, + uint64_t random64, uint64_t timeSeconds, + uint64_t msgUid, uint64_t msgSeq, uint32_t msgClientSeq) { + NotifyRecallSysMsgEvent(1, fromUid, fromUid, toUid, random64, timeSeconds, msgUid, msgSeq, msgClientSeq); +} + +void NotifyRecallMsgEventForGroup(const std::string& peerUid, const std::string& recallOpUid, + uint64_t random64, uint64_t timeSeconds, uint64_t msgSeq) { + NotifyRecallSysMsgEvent(2, peerUid, recallOpUid, peerUid, random64, timeSeconds, 0, msgSeq, 0); +} + + +void (* sOriginHandleGroupRecallSysMsgCallback)(void*, void*, void*) = nullptr; + +void HandleGroupRecallSysMsgCallback([[maybe_unused]] void* x0, void* x1, [[maybe_unused]] void* x2) { + // LOGD("HandleGroupRecallSysMsgCallback start p1={:p}, p2={:p}, p3={:p}", x0, x1, x2); + // we can still do it... hitherto p3 == null... we need to decode the message manually... + uintptr_t base = (uintptr_t) gLibkernelBaseAddress; + void* pVar1 = *(void**) x1; + if ((vcall(pVar1, 3) & 1) == 0) { + LOGE("msg_recall: HandleRecallSysMsg: on recall group sys msg! hasn't msg_common::Msg::kBody"); + return; + } + void* pVar2 = *(void**) x1; + STACK_GUARD; + std::array objVar1 = {}; // actual size unknown, maybe 0x90 + STACK_GUARD; + vcall_x8<0xe8, 0, int>(pVar2, &objVar1, 3); + auto pVar3 = *(void**) (objVar1.data()); + if (pVar3 == nullptr) { + LOGE("msg_recall: HandleRecallSysMsg: on recall group sys msg! msg_common::Msg::kBody = null"); + return; + } + if ((vcall(pVar3, 2) & 1) == 0) { + LOGE("msg_recall: HandleRecallSysMsg: on recall group sys msg! hasn't im_msg_body::MsgBody::kBytesMsgContent"); + return; + } + STACK_GUARD; + std::vector msgContentBytes; + STACK_GUARD; + vcall_x8<0x78, 8, int>(pVar3, &msgContentBytes, 2); + if (msgContentBytes.size() < 8) { + LOGE("msg_recall: HandleRecallSysMsg: on recall group sys msg! im_msg_body::MsgBody::kBytesMsgContent is error"); + return; + } + std::vector content = {msgContentBytes.begin() + 7, msgContentBytes.end()}; + STACK_GUARD; + std::array objVar3 = {}; // actual size 0x10, maybe shared_ptr, but we don't have dtor + STACK_GUARD; + call_func_with_x8((void*) (base + gOffsetGetDecoderSp), &objVar3, 0, 0, 0, 0); + void* notifyMsgBody = objVar3[0]; + if ((vcall*>(notifyMsgBody, &content) & 1) == 0) { + LOGE("on recall group sys msg! decode kBytesMsgContent fail"); + return; + } + uint64_t groupCode = vcall(notifyMsgBody, 4); + if (groupCode == 0) { + LOGE("on recall group sys msg! group code is 0"); + return; + } + uint64_t opType = vcall(notifyMsgBody, 1); + if (opType != 7) { + LOGW("HandleGroupRecallSysMsgCallback: on recall group sys msg! no Prompt_MsgRecallReminder op_type:{}", opType); + return; + } + if ((vcall(notifyMsgBody, 0xb) & 1) == 0) { + LOGE("HandleGroupRecallSysMsgCallback: on recall group sys msg! no NotifyMsgBody::opt_msg_recall"); + return; + } + STACK_GUARD; + std::array optMsgRecall = {}; // actual size 0x10, maybe shared_ptr, but we don't have dtor + STACK_GUARD; + vcall_x8<0xe8, 0, int>(notifyMsgBody, &optMsgRecall, 0xb); + if (optMsgRecall[0] == nullptr) { + LOGE("HandleGroupRecallSysMsgCallback: on recall group sys msg! NotifyMsgBody::opt_msg_recall == null"); + return; + } + if ((vcall(optMsgRecall[0], 3) & 1) == 0) { + LOGE("HandleGroupRecallSysMsgCallback: on recall group sys msg! on recall group sys msg! no msg_infos"); + return; + } + std::array vectorResultStub = {nullptr, nullptr, nullptr}; + vcall_x8<0xf0, 8, int>(optMsgRecall[0], &vectorResultStub, 3); + const auto& msgInfoList = *reinterpret_cast*>(&vectorResultStub); + if (msgInfoList.empty()) { + LOGE("HandleGroupRecallSysMsgCallback: on recall group sys msg! no any msg info"); + return; + } + std::string peerUid = fmt::format("{}", groupCode); + for (const auto& msgInfo: msgInfoList) { + uint32_t msgSeq = ThunkGetInt32Property(msgInfo._unk0_8, 1); + uint32_t random = ThunkGetInt32Property(msgInfo._unk0_8, 3); + uint64_t time = ThunkGetInt64Property(msgInfo._unk0_8, 2); + std::string recallOpUid = ThunkGetStringProperty(msgInfo._unk0_8, 6); + + // Unfortunately, I didn't find a way to find the origMsgSenderUid. + // The only thing we can do is to get message by msgSeq, and get senderUid from it, iff we have the message. + + NotifyRecallMsgEventForGroup(peerUid, recallOpUid, random, time, msgSeq); + } +} + +void (* sOriginHandleC2cRecallSysMsgCallback)(void*, void*, void*) = nullptr; + +void HandleC2cRecallSysMsgCallback([[maybe_unused]] void* p1, [[maybe_unused]] void* p2, void* p3) { if (p3 == nullptr || *(void**) p3 == nullptr) { LOGE("HandleC2cGroupSysMsgCallback BUG !!! *p3 = null, this should not happen!!!"); return; @@ -257,83 +359,74 @@ bool InitInitNtKernelRecallMsgHook() { return false; } sIsHooked = true; - bool hasError = false; gLibkernelBaseAddress = reinterpret_cast(baseAddress); - // LOGD("baseAddress = {}", baseAddress); - auto c2cCandidates = FindByteSequenceForLoadedImage(gLibkernelBaseAddress, kTraitRecallC2cSysMsg, true, 4); - auto groupCandidates = FindByteSequenceForLoadedImage(gLibkernelBaseAddress, kTraitRecallGroupSysMsg, true, 4); - uint64_t offsetC2c = 0; - uint64_t offsetGroup = 0; - if (c2cCandidates.size() != 1) { - std::string logStr = "InitInitNtKernelRecallMsgHook failed, c2cCandidates.size()=" + std::to_string(c2cCandidates.size()); - logStr += ", ["; - for (auto& item: c2cCandidates) { - logStr += std::to_string(item) + ","; - } - logStr += "]"; - LOGE("{}", logStr); - hasError = true; - } else { - uintptr_t offset = c2cCandidates[0] - kTraitOffsetRecallC2cSysMsg; - const uint32_t* p = reinterpret_cast(baseAddress + offset); - uint32_t inst = *p; - // expect fd 7b ba a9 stp x29,x30,[sp, #???]! - if ((inst & ((0b11111111u << 24) | (0b11000000u << 16u) | (0b01111111u << 8u) | 0xFF)) - == ((0b10101001u << 24u) | (0b10000000u << 16u) | (0x7b << 8u) | 0xfd)) { - offsetC2c = offset; - LOGD("c2cCandidates = [{:x}], offsetC2c = {:x}", c2cCandidates[0], offsetC2c); - } else { - LOGE("c2c: inst = {:x} not match, expect 'stp x29,x30,[sp, #???]!'", inst); - } - } - if (groupCandidates.size() != 1) { - std::string logStr = "InitInitNtKernelRecallMsgHook failed, groupCandidates.size()=" + std::to_string(groupCandidates.size()); - logStr += ", ["; - for (auto& item: groupCandidates) { - logStr += std::to_string(item) + ","; - } - logStr += "]"; - LOGE("{}", logStr); - hasError = true; - } else { - uintptr_t offset = groupCandidates[0] - kTraitOffsetRecallGroupSysMsg; - const uint32_t* p = reinterpret_cast(baseAddress + offset); - uint32_t inst = *p; - // expect fd 7b ba a9 stp x29,x30,[sp, #???]! - if ((inst & ((0b11111111u << 24) | (0b11000000u << 16u) | (0b01111111u << 8u) | 0xFF)) - == ((0b10101001u << 24u) | (0b10000000u << 16u) | (0x7b << 8u) | 0xfd)) { - offsetGroup = offset; - LOGD("groupCandidates = [{:x}], offsetGroup = {:x}", groupCandidates[0], offsetGroup); - } else { - LOGE("group: inst = {:x} not match, expect 'stp x29,x30,[sp, #???]!'", inst); + + auto targetRecallC2cSysMsg = AobScanTarget() + .WithName("RecallC2cSysMsg") + .WithSequence({0x09, 0x8d, 0x40, 0xf8, 0xf5, 0x03, 0x00, 0xaa, 0x21, 0x00, 0x80, 0x52, 0xf3, 0x03, 0x02, 0xaa, 0x29, 0x8d, 0x40, 0xf9}) + .WithStep(4) + .WithExecMemOnly(true) + .WithOffsetForResult(-4 * 8) + .WithResultValidator(CommonAobScanValidator::kArm64StpX29X30SpImm); + + auto targetRecallGroupSysMsg = AobScanTarget() + .WithName("RecallGroupSysMsg") + .WithSequence({0x28, 0x00, 0x40, 0xf9, 0x61, 0x00, 0x80, 0x52, 0x09, 0x8d, 0x40, 0xf8, 0x29, 0x8d, 0x40, 0xf9}) + .WithStep(4) + .WithExecMemOnly(true) + .WithOffsetForResult(-4 * 6) + .WithResultValidator(CommonAobScanValidator::kArm64StpX29X30SpImm); + + auto targetGetDecoder = AobScanTarget() + .WithName("GetDecoder") + .WithSequence({0x3f, 0x8d, 0x01, 0xf8, 0xf4, 0x03, 0x00, 0xaa, 0x1f, 0x10, 0x00, 0xf9}) + .WithStep(4) + .WithExecMemOnly(true) + .WithOffsetForResult(-0x78) + .WithResultValidator(CommonAobScanValidator::kArm64StpX29X30SpImm); + + std::vector errorMsgList; + if (!SearchForAllAobScanTargets({&targetRecallC2cSysMsg, &targetRecallGroupSysMsg, &targetGetDecoder}, + gLibkernelBaseAddress, true, errorMsgList)) { + LOGE("InitInitNtKernelRecallMsgHook SearchForAllAobScanTargets failed"); + // sth went wrong + for (const auto& msg: errorMsgList) { + // report error to UI somehow + TraceError(nullptr, gInstanceRevokeMsgHook, msg); } + return false; } + + uint64_t offsetC2c = targetRecallC2cSysMsg.GetResultOffset(); + uint64_t offsetGroup = targetRecallGroupSysMsg.GetResultOffset(); + uint64_t offsetGetDecoder = targetGetDecoder.GetResultOffset(); + + LOGD("offsetC2c={:x}, offsetGroup={:x}, offsetGetDecoder={:x}", offsetC2c, offsetGroup, offsetGetDecoder); + + gOffsetGetDecoderSp = offsetGetDecoder; if (offsetC2c != 0) { void* c2c = (void*) (baseAddress + offsetC2c); if (CreateInlineHook(c2c, (void*) &HandleC2cRecallSysMsgCallback, (void**) &sOriginHandleC2cRecallSysMsgCallback) != 0) { - LOGE("InitInitNtKernelRecallMsgHook failed, DobbyHook c2c failed"); + TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, DobbyHook c2c failed"); return false; } } else { - LOGE("InitInitNtKernelRecallMsgHook failed, offsetC2c == 0"); + TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, offsetC2c == 0"); } if (offsetGroup != 0) { void* group = (void*) (baseAddress + offsetGroup); if (CreateInlineHook(group, (void*) &HandleGroupRecallSysMsgCallback, (void**) &sOriginHandleGroupRecallSysMsgCallback) != 0) { - LOGE("InitInitNtKernelRecallMsgHook failed, DobbyHook group failed"); + TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, DobbyHook group failed"); return false; } } else { - LOGE("InitInitNtKernelRecallMsgHook failed, offsetGroup == 0"); - } - if (hasError) { - LOGE("InitInitNtKernelRecallMsgHook failed, hasError"); + TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, offsetGroup == 0"); } return true; }; ProcessView self; if (int err;(err = self.readProcess(getpid())) != 0) { - LOGE("InitInitNtKernelRecallMsgHook failed, readProcess failed: {}", err); + TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, readProcess failed: {}", err); return false; } std::optional libkernel; @@ -366,7 +459,7 @@ bool InitInitNtKernelRecallMsgHook() { // get base address ProcessView self2; if (int err;(err = self2.readProcess(getpid())) != 0) { - LOGE("InitInitNtKernelRecallMsgHook failed, readProcess failed: {}", err); + TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, readProcess failed: {}", err); return; } std::optional libkernel2; @@ -379,10 +472,10 @@ bool InitInitNtKernelRecallMsgHook() { if (libkernel2.has_value()) { // hook now if (!fnHookProc(libkernel2->baseAddress)) { - LOGE("InitInitNtKernelRecallMsgHook failed, fnHookProc failed"); + TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, fnHookProc failed"); } } else { - LOGE("InitInitNtKernelRecallMsgHook failed, but it was loaded"); + TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, but it was loaded"); } } }); @@ -396,7 +489,8 @@ bool InitInitNtKernelRecallMsgHook() { extern "C" JNIEXPORT jboolean JNICALL Java_cc_ioctl_hook_msg_RevokeMsgHook_nativeInitNtKernelRecallMsgHook(JNIEnv* env, jobject thiz) { using ntqq::hook::klassRevokeMsgHook; - using ntqq::hook::handleC2cRecallMsgFromNtKernel; + using ntqq::hook::gInstanceRevokeMsgHook; + using ntqq::hook::handleRecallSysMsgFromNtKernel; if (klassRevokeMsgHook == nullptr) { jclass clazz = env->GetObjectClass(thiz); if (clazz == nullptr) { @@ -404,9 +498,10 @@ Java_cc_ioctl_hook_msg_RevokeMsgHook_nativeInitNtKernelRecallMsgHook(JNIEnv* env return false; } klassRevokeMsgHook = (jclass) env->NewGlobalRef(clazz); - handleC2cRecallMsgFromNtKernel = env->GetStaticMethodID(clazz, "handleC2cRecallMsgFromNtKernel", - "(Ljava/lang/String;Ljava/lang/String;JJJJI)V"); - if (handleC2cRecallMsgFromNtKernel == nullptr) { + gInstanceRevokeMsgHook = env->NewGlobalRef(thiz); + handleRecallSysMsgFromNtKernel = env->GetStaticMethodID(clazz, "handleRecallSysMsgFromNtKernel", + "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;JJJJI)V"); + if (handleRecallSysMsgFromNtKernel == nullptr) { LOGE("InitInitNtKernelRecallMsgHook failed, GetStaticMethodID failed"); return false; } diff --git a/app/src/main/cpp/utils/AobScanUtils.cc b/app/src/main/cpp/utils/AobScanUtils.cc new file mode 100644 index 0000000000..50dcc621d2 --- /dev/null +++ b/app/src/main/cpp/utils/AobScanUtils.cc @@ -0,0 +1,87 @@ +// +// Created by sulfate on 2023-07-14. +// + +#include "AobScanUtils.h" + +#include + +#include "ElfScan.h" + +namespace utils { + +using Validator = AobScanTarget::Validator; + +static std::string bytes2hex(std::span bytes) { + std::string result; + result.reserve(bytes.size() * 2); + for (uint8_t byte: bytes) { + result += fmt::format("{:02x}", byte); + } + return result; +} + +bool SearchForAllAobScanTargets(std::vector targets, + const void* imageBase, bool isLoadedImage, + std::vector& errors) { + bool hasFailed = false; + for (auto* target: targets) { + std::string_view name = target->name; + std::span sequence = target->sequence; + int step = target->step; + bool execMemOnly = target->execMemOnly; + int64_t offsetForResult = target->offsetForResult; + auto validator = target->resultValidator; + auto rawResultSet = FindByteSequenceImpl(imageBase, isLoadedImage, sequence, execMemOnly, step); + if (rawResultSet.empty()) { + errors.emplace_back(fmt::format("AobScanUtils: failed to find target '{}' with sequence {}", name, bytes2hex(sequence))); + hasFailed = true; + continue; + } + if (rawResultSet.size() > 1) { + std::string msg = fmt::format("AobScanUtils: found {} targets '{}' with sequence {}", rawResultSet.size(), name, bytes2hex(sequence)); + for (auto result: rawResultSet) { + msg += fmt::format("offset: 0x{:x}, ", result); + } + errors.emplace_back(std::move(msg)); + hasFailed = true; + continue; + } + uint64_t result = uint64_t(int64_t(rawResultSet[0]) + offsetForResult); + if (validator.has_value()) { + // TODO: 2023-07-14 if the image is a mmaped file, the offset in file is not provided, currently we just set it to 0 + if (!validator.value()(imageBase, isLoadedImage, result, 0)) { + errors.emplace_back(fmt::format("AobScanUtils: validator failed for target '{}' with sequence {} for result 0x{:x}", + name, bytes2hex(sequence), result)); + hasFailed = true; + continue; + } + } + target->results.emplace_back(result); + } + return !hasFailed; +} + +const Validator CommonAobScanValidator::kArm64StpX29X30SpImm = [](const void* base, bool isLoaded, + uint64_t rva, uint64_t optOffsetInFile) -> bool { + if (!isLoaded) { + // TODO: 2023-07-14 if the image is a mmaped file, the offset in file is not provided, currently we just set it to 0 + // currently we only support loaded image + return false; + } + if (base == nullptr) { + return false; + } + const uint8_t* va = reinterpret_cast(base) + rva; + const uint32_t* p = reinterpret_cast(va); + uint32_t inst = *p; + // expect fd 7b ba a9 stp x29,x30,[sp, #???]! + if ((inst & ((0b11111111u << 24) | (0b11000000u << 16u) | (0b01111111u << 8u) | 0xFF)) + == ((0b10101001u << 24u) | (0b10000000u << 16u) | (0x7b << 8u) | 0xfd)) { + return true; + } else { + return false; + } +}; + +} diff --git a/app/src/main/cpp/utils/AobScanUtils.h b/app/src/main/cpp/utils/AobScanUtils.h new file mode 100644 index 0000000000..bb063c2934 --- /dev/null +++ b/app/src/main/cpp/utils/AobScanUtils.h @@ -0,0 +1,114 @@ +// +// Created by sulfate on 2023-07-14. +// + +#ifndef QAUXV_AOBSCANUTILS_H +#define QAUXV_AOBSCANUTILS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace utils { + +class AobScanTarget { +public: + /** + * A validator for the result of AOB scan. Note that the offsetForResult is applied before the validator. + * @param base the base address of the ELF image + * @param isLoaded true if the image is loaded in memory by a linker, false if it's a mmap-ed file + * @param rva the relative virtual address of the result, this is real result of the AOB scan + * @param optOffsetInFile the offset in the file, if the image is loaded in memory, this value is 0 + */ + using Validator = std::function; + + std::string name; // for debugging purpose only + std::vector sequence; + int step = 0; + bool execMemOnly = false; + int64_t offsetForResult = 0; + std::optional resultValidator; + + std::vector results; + + AobScanTarget() = default; + + AobScanTarget(std::string name, std::vector sequence, int step, bool execMemOnly, int64_t offsetForResult, + std::optional resultValidator) + : name(std::move(name)), sequence(std::move(sequence)), step(step), execMemOnly(execMemOnly), + offsetForResult(offsetForResult), resultValidator(std::move(resultValidator)) {} + + inline AobScanTarget& WithName(std::string newName) { + this->name = std::move(newName); + return *this; + } + + inline AobScanTarget& WithSequence(std::vector newSequence) { + this->sequence = std::move(newSequence); + return *this; + } + + inline AobScanTarget& WithStep(int newStep) { + this->step = newStep; + return *this; + } + + inline AobScanTarget& WithExecMemOnly(bool newExecMemOnly) { + this->execMemOnly = newExecMemOnly; + return *this; + } + + inline AobScanTarget& WithOffsetForResult(int64_t newOffsetForResult) { + this->offsetForResult = newOffsetForResult; + return *this; + } + + inline AobScanTarget& WithResultValidator(std::optional newResultValidator) { + this->resultValidator = std::move(newResultValidator); + return *this; + } + + inline AobScanTarget& WithResultValidator(Validator newResultValidator) { + this->resultValidator = std::move(newResultValidator); + return *this; + } + + inline uint64_t GetResultOffset() const noexcept { + if (results.size() != 1) { + return 0; + } + return results[0]; + } + + inline bool HasResult() const noexcept { + return results.size() == 1; + } +}; + +class CommonAobScanValidator { +public: + /** + * Require the result to be arm64 instruction "stp x29, x30, [sp, #imm]" or "stp x29, x30, [sp, #imm]!" + * This is used to find the function prologue. + */ + static const AobScanTarget::Validator kArm64StpX29X30SpImm; +}; + +/** + * Search for all AOB scan targets in the given image. + * @param targets an array of AOB scan targets + * @param imageBase the base address of the ELF image + * @param isLoadedImage true if the image is loaded in memory by a linker, false if it's a mmap-ed file + * @param errors a vector of error messages + * @return true if and only if every AOB scan target has one result + */ +bool SearchForAllAobScanTargets(std::vector targets, const void* imageBase, bool isLoadedImage, std::vector& errors); + +} + +#endif //QAUXV_AOBSCANUTILS_H diff --git a/app/src/main/cpp/utils/ElfScan.h b/app/src/main/cpp/utils/ElfScan.h index 8f23f4a9ff..50bbebda0a 100644 --- a/app/src/main/cpp/utils/ElfScan.h +++ b/app/src/main/cpp/utils/ElfScan.h @@ -16,6 +16,8 @@ std::vector FindByteSequenceForImageFile(const void* baseAddress, std: std::vector FindByteSequenceForLoadedImage(const void* baseAddress, std::span sequence, bool execMemOnly, int step); +std::vector FindByteSequenceImpl(const void* baseAddress, bool isLoaded, std::span sequence, bool execMemOnly, int step); + } #endif //QAUXV_ELFSCAN_H diff --git a/app/src/main/cpp/utils/arch_utils.cc b/app/src/main/cpp/utils/arch_utils.cc new file mode 100644 index 0000000000..731d048d92 --- /dev/null +++ b/app/src/main/cpp/utils/arch_utils.cc @@ -0,0 +1,30 @@ +// +// Created by sulfate on 2023-07-14. +// + +#include "arch_utils.h" + +#include + +#ifdef __aarch64__ + +extern "C" __attribute__((naked, visibility("default"))) void* call_func_with_x8(const void* func, void* x8, void* x0, void* x1, void* x2, void* x3) { + __asm volatile ( + "mov x16, x0\n" + "mov x8, x1\n" + "mov x0, x2\n" + "mov x1, x3\n" + "mov x2, x4\n" + "mov x3, x5\n" + "br x16\n" + ); +} + +#else // not __aarch64__ + +extern "C" void* call_func_with_x8(const void* func, void* x8, void* x0, void* x1, void* x2, void* x3) { + // not implemented + abort(); +} + +#endif // __aarch64__ diff --git a/app/src/main/cpp/utils/arch_utils.h b/app/src/main/cpp/utils/arch_utils.h new file mode 100644 index 0000000000..3d2aefa708 --- /dev/null +++ b/app/src/main/cpp/utils/arch_utils.h @@ -0,0 +1,10 @@ +// +// Created by sulfate on 2023-07-14. +// + +#ifndef QAUXV_ARCH_UTILS_H +#define QAUXV_ARCH_UTILS_H + +extern "C" void* call_func_with_x8(const void* func, void* x8, void* x0, void* x1, void* x2, void* x3); + +#endif //QAUXV_ARCH_UTILS_H diff --git a/app/src/main/java/cc/ioctl/hook/msg/RevokeMsgHook.java b/app/src/main/java/cc/ioctl/hook/msg/RevokeMsgHook.java index 5f8e488961..87127305ec 100644 --- a/app/src/main/java/cc/ioctl/hook/msg/RevokeMsgHook.java +++ b/app/src/main/java/cc/ioctl/hook/msg/RevokeMsgHook.java @@ -68,6 +68,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Random; import kotlin.Unit; import kotlin.jvm.functions.Function3; @@ -364,47 +365,54 @@ private void onRevokeMsg(Object revokeMsgInfo) throws Exception { } /** - * Called by NotifyRecallMsgEventForC2c in NtRecallMsgHook.cc from native. + * Called NtRecallMsgHook.cc from native. *

* We are not allowed to throw any exception in this method. */ @Keep - private static void handleC2cRecallMsgFromNtKernel(String fromUid, String toUid, long random64, - long timeSeconds, long msgUid, long msgSeq, int msgClientSeq) { + private static void handleRecallSysMsgFromNtKernel(int chatType, String peerUid, String recallOpUid, String toUid, + long random64, long timeSeconds, long msgUid, long msgSeq, int msgClientSeq) { SyncUtils.async(() -> { try { - RevokeMsgHook.INSTANCE.onRecallC2cMsgForNT(fromUid, toUid, random64, timeSeconds, msgUid, msgSeq, msgClientSeq); + RevokeMsgHook.INSTANCE.onRecallSysMsgForNT(chatType, peerUid, recallOpUid, toUid, random64, timeSeconds, + msgUid, msgSeq, msgClientSeq); } catch (Exception | LinkageError | AssertionError e) { RevokeMsgHook.INSTANCE.traceError(e); } }); } - private void onRecallC2cMsgForNT(String fromUid, String toUid, long random64, long timeSeconds, long msgUid, long msgSeq, int msgClientSeq) - throws ReflectiveOperationException { - String fromUin = RelationNTUinAndUidApi.getUinFromUid(fromUid); - String toUin = RelationNTUinAndUidApi.getUinFromUid(toUid); - String selfUin = AppRuntimeHelper.getAccount(); - String selfUid = RelationNTUinAndUidApi.getUidFromUin(selfUin); - if (TextUtils.isEmpty(fromUin)) { - Log.e("onRecallC2cMsg fatal: fromUin is empty"); + private void onRecallSysMsgForNT(int chatType, String peerUid, String recallOpUid, String toUid, + long random64, long timeSeconds, long msgUid, long msgSeq, int msgClientSeq) throws ReflectiveOperationException { + if (TextUtils.isEmpty(peerUid)) { + Log.e("onRecallSysMsgForNT fatal: peerUid is empty"); return; } - if (TextUtils.isEmpty(toUin)) { - Log.e("onRecallC2cMsg fatal: toUin is empty"); + if (chatType != 1 && chatType != 2) { + Log.e("onRecallSysMsgForNT fatal: chatType is not c2c or troop"); + return; + } + String operatorUin = RelationNTUinAndUidApi.getUinFromUid(recallOpUid); + String selfUin = AppRuntimeHelper.getAccount(); + String selfUid = RelationNTUinAndUidApi.getUidFromUin(selfUin); + if (TextUtils.isEmpty(operatorUin)) { + Log.e("onRecallSysMsgForNT fatal: recallOpUin is empty"); + // dump all available info + Log.e("onRecallSysMsgForNT dump: chatType=" + chatType + ", peerUid=" + peerUid + ", recallOpUid=" + recallOpUid + ", toUid=" + toUid + ", random64=" + random64 + ", timeSeconds=" + timeSeconds + ", msgUid=" + msgUid + ", msgSeq=" + msgSeq + ", msgClientSeq=" + msgClientSeq + ", operatorUin=" + operatorUin + ", selfUin=" + selfUin + ", selfUid=" + selfUid); return; } if (TextUtils.isEmpty(selfUid)) { - Log.e("onRecallC2cMsg fatal: selfUid is empty"); + Log.e("onRecallSysMsgForNT fatal: selfUid is empty"); return; } - // C2C PM - if (selfUid.equals(fromUid)) { + if (selfUid.equals(recallOpUid)) { // ignore msg revoked by self return; } - String friendUid = fromUid; - Contact contact = new Contact(ChatTypeConstants.C2C, friendUid, ""); + if ((chatType == 1 && !Objects.equals(selfUid, toUid)) || (chatType == 2 && !Objects.equals(peerUid, toUid))) { + Log.w("!!! onRecallSysMsgForNT potential bug: chatType=" + chatType + ", peerUid=" + peerUid + ", toUid=" + toUid + ", selfUid=" + selfUid); + } + Contact contact = new Contact(chatType, peerUid, ""); AppRuntime app = AppRuntimeHelper.getAppRuntime(); IKernelMsgService kmsgSvc = MsgServiceHelper.getKernelMsgService(app); // I don't know why, but... @@ -417,34 +425,98 @@ private void onRecallC2cMsgForNT(String fromUid, String toUid, long random64, lo if (queryResult == 0 && msgList != null && !msgList.isEmpty()) { msgObject = msgList.get(0); } else if (queryResult == 0) { - Log.d("onRecallC2cMsg: msg not found, msgUid=" + msgUid); + Log.d("onRecallSysMsgForNT: msg not found, msgUid=" + msgUid); } else { - Log.e("onRecallC2cMsg error: getMsgsByMsgId failed, result=" + queryResult + ", errMsg=" + errMsg); + Log.e("onRecallSysMsgForNT error: getMsgsByMsgId failed, result=" + queryResult + ", errMsg=" + errMsg); } - String revokerPron = selfUin.equals(fromUin) ? "你" : "对方"; - String summary; NtGrayTipHelper.NtGrayTipJsonBuilder builder = new NtGrayTipHelper.NtGrayTipJsonBuilder(); - if (msgObject != null) { - builder.appendText(revokerPron + "尝试撤回"); - builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.MsgRefItem("一条消息", msgSeq)); - summary = revokerPron + "尝试撤回一条消息"; + String summary; + if (chatType == ChatTypeConstants.C2C) { + String revokerPron = selfUid.equals(peerUid) ? "你" : "对方"; + if (msgObject != null && !(msgObject.getMsgType() == 5 && msgObject.getSubMsgType() == 4)) { + builder.appendText(revokerPron + "尝试撤回"); + builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.MsgRefItem("一条消息", msgSeq)); + summary = revokerPron + "尝试撤回一条消息"; + } else if (msgObject != null && (msgObject.getMsgType() == 5 && msgObject.getSubMsgType() == 4)) { + // C2C only: msg not actually received, system message: "对方撤回了一条消息" + // We don't need to do anything here, otherwise we will have duplicated gray tip. + return; + } else { + builder.appendText(revokerPron + "撤回了一条消息(没收到) [msgId=" + msgUid + ", seq=" + msgSeq + ", cseq=" + msgClientSeq + "]"); + summary = revokerPron + "撤回了一条消息(没收到)"; + } + } else if (chatType == ChatTypeConstants.GROUP) { + String operatorName = ContactUtils.getDisplayNameForUid(recallOpUid, peerUid); + // do we have the original message? + if (msgObject != null && !(msgObject.getMsgType() == 5 && msgObject.getSubMsgType() == 4)) { + // good, we have the original message + // then, is the original message sent by the operator? + String msgAuthorUid = msgObject.getSenderUid(); + long msgAuthorUin = msgObject.getSenderUin(); + String msgAuthorName = getMsgSenderReferenceName(msgObject, true); + if (recallOpUid.equals(msgAuthorUid)) { + // by themselves + builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.UserItem(String.valueOf(operatorUin), recallOpUid, operatorName)); + builder.appendText("尝试撤回"); + builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.MsgRefItem("一条消息", msgSeq)); + summary = operatorName + "尝试撤回一条消息"; + } else { + builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.UserItem(String.valueOf(operatorUin), recallOpUid, operatorName)); + builder.appendText("尝试撤回"); + builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.UserItem(String.valueOf(msgAuthorUin), msgAuthorUid, msgAuthorName)); + builder.appendText("的"); + builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.MsgRefItem("一条消息", msgSeq)); + summary = operatorName + "尝试撤回" + msgAuthorName + "的一条消息"; + } + } else { + // we don't have the original message, so we can't get the sender's name + builder.append(new NtGrayTipHelper.NtGrayTipJsonBuilder.UserItem(String.valueOf(operatorUin), recallOpUid, operatorName)); + builder.appendText("撤回了一条消息(没收到) [msgId=" + msgUid + ", seq=" + msgSeq + ", cseq=" + msgClientSeq + "]"); + summary = operatorName + "撤回了一条消息(没收到)"; + } } else { - builder.appendText(revokerPron + "撤回了一条消息(没收到) [msgId=" + msgUid + ", seq=" + msgSeq + ", cseq=" + msgClientSeq + "]"); - summary = revokerPron + "撤回了一条消息(没收到)"; + throw new AssertionError("onRecallSysMsgForNT chatType=" + chatType); } String jsonStr = builder.build().toString(); - JsonGrayElement jsonGrayElement = NtGrayTipHelper.createLocalJsonElement(NtGrayTipHelper.AIO_AV_C2C_NOTICE, jsonStr, summary); + int busiId = (chatType == ChatTypeConstants.C2C) ? NtGrayTipHelper.AIO_AV_C2C_NOTICE : NtGrayTipHelper.AIO_AV_GROUP_NOTICE; + JsonGrayElement jsonGrayElement = NtGrayTipHelper.createLocalJsonElement(busiId, jsonStr, summary); NtGrayTipHelper.addLocalJsonGrayTipMsg(AppRuntimeHelper.getAppRuntime(), contact, jsonGrayElement, true, true, (result, uin) -> { if (result != 0) { - Log.e("onRecallC2cMsg error: addLocalJsonGrayTipMsg failed, result=" + result); + Log.e("onRecallSysMsgForNT error: addLocalJsonGrayTipMsg failed, result=" + result); } }); } catch (Exception | LinkageError e) { + // dump all args to logcat + Log.e("error: onRecallSysMsgForNT failed, chatType=" + chatType + ", peerUid=" + peerUid + ", msgUid=" + msgUid + ", msgSeq=" + msgSeq + + ", msgClientSeq=" + msgClientSeq + ", recallOpUid=" + recallOpUid + ", selfUid=" + selfUid, e); traceError(e); } })); } + private static String getMsgSenderReferenceName(@NonNull MsgRecord msgRecord, boolean isInGroup) { + if (isInGroup) { + String memberName = msgRecord.getSendMemberName(); + if (!TextUtils.isEmpty(memberName)) { + return memberName; + } + } + String remarkName = msgRecord.getSendRemarkName(); + if (!TextUtils.isEmpty(remarkName)) { + return remarkName; + } + String nickName = msgRecord.getSendNickName(); + if (!TextUtils.isEmpty(nickName)) { + return nickName; + } + long uin = msgRecord.getSenderUin(); + if (uin > 0) { + return String.valueOf(uin); + } + // wtf? + return msgRecord.getSenderUid(); + } + private Bundle createTroopMemberHighlightItem(String memberUin) { Bundle bundle = new Bundle(); bundle.putInt("key_action", 5); diff --git a/app/src/main/java/io/github/qauxv/bridge/ContactUtils.java b/app/src/main/java/io/github/qauxv/bridge/ContactUtils.java index bc665d9033..e3d1f205a0 100644 --- a/app/src/main/java/io/github/qauxv/bridge/ContactUtils.java +++ b/app/src/main/java/io/github/qauxv/bridge/ContactUtils.java @@ -28,6 +28,7 @@ import com.tencent.common.app.AppInterface; import de.robv.android.xposed.XposedHelpers; import io.github.qauxv.base.annotation.DexDeobfs; +import io.github.qauxv.bridge.ntapi.RelationNTUinAndUidApi; import io.github.qauxv.util.Initiator; import io.github.qauxv.util.Log; import io.github.qauxv.util.dexkit.DexKit; @@ -161,4 +162,47 @@ public static String getTroopName(@NonNull String troopUin) { return troopUin; } } + + @NonNull + public static String getDisplayNameForUid(@NonNull String peerUid) { + return getDisplayNameForUid(peerUid, 0); + } + + @NonNull + public static String getDisplayNameForUid(@NonNull String peerUid, long groupNumber) { + try { + String uin = RelationNTUinAndUidApi.getUinFromUid(peerUid); + if (TextUtils.isEmpty(uin)) { + return peerUid; + } + return getDisplayNameForUin(uin, groupNumber); + } catch (RuntimeException e) { + return peerUid; + } + } + + @NonNull + public static String getDisplayNameForUid(@NonNull String peerUid, @Nullable String groupNumber) { + if (TextUtils.isEmpty(groupNumber)) { + return getDisplayNameForUid(peerUid); + } + return getDisplayNameForUid(peerUid, Long.parseLong(groupNumber)); + } + + @NonNull + public static String getDisplayNameForUin(@NonNull String uin) { + return getDisplayNameForUin(uin, 0); + } + + @NonNull + public static String getDisplayNameForUin(@NonNull String uin, long groupNumber) { + if (groupNumber > 0) { + return getTroopMemberNick(String.valueOf(groupNumber), uin); + } + String ret = getBuddyName(AppRuntimeHelper.getQQAppInterface(), uin); + if (ret != null) { + return ret; + } + return uin; + } }