Shamrock: 临时修补rkey缺失导致的qqnt图片无法获取 #236

This commit is contained in:
白池 2024-02-17 19:39:26 +08:00
parent 62385d6f62
commit 2c8094c8c8
15 changed files with 295 additions and 22 deletions

View File

@ -0,0 +1,19 @@
package com.tencent.guild.api.transfile;
import androidx.annotation.Nullable;
import com.tencent.mobileqq.qroute.QRouteApi;
import com.tencent.qqnt.kernel.nativeinterface.BigDataTicket;
public interface IGuildTransFileApi extends QRouteApi {
//@Nullable
//ArrayList<ServerAddress> getBigDataIpList(boolean z, @Nullable IpType ipType);
@Nullable
BigDataTicket getBigDataTicket();
//@Nullable
//ArrayList<ServerAddress> getIpDirectList(@Nullable String str, @Nullable IpType ipType);
void pullConfigIfNeed();
}

View File

@ -0,0 +1,26 @@
package com.tencent.libra.download;
import androidx.annotation.NonNull;
import com.tencent.libra.request.Option;
public interface ILibraDownloader {
class PicDownLoadListener {
Option mOption;
public PicDownLoadListener(@NonNull Option option) {
this.mOption = option;
}
public void onResult(boolean success, int code) {
}
}
boolean canDownload(Option option);
void cancel(Option option);
void downLoad(Option option, PicDownLoadListener picDownLoadListener);
boolean needDownloadOnWorkThread();
}

View File

@ -0,0 +1,4 @@
package com.tencent.libra.request;
public class Option {
}

View File

@ -0,0 +1,11 @@
package com.tencent.qqnt.aio.api;
import com.tencent.libra.download.ILibraDownloader;
import com.tencent.mobileqq.qroute.QRouteApi;
import org.jetbrains.annotations.NotNull;
public interface IAIOPicDownloaderProvider extends QRouteApi {
@NotNull
ILibraDownloader provideDownloader();
}

View File

@ -3,6 +3,7 @@ package com.tencent.qqnt.kernel.api.impl;
import com.tencent.qqnt.kernel.nativeinterface.IGetTempChatInfoCallback;
import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgListener;
import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback;
import com.tencent.qqnt.kernel.nativeinterface.RichMediaElementGetReq;
import com.tencent.qqnt.kernel.nativeinterface.RichMediaFilePathInfo;
import com.tencent.qqnt.kernel.nativeinterface.TempChatPrepareInfo;
@ -10,6 +11,10 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class MsgService {
void getRichMediaElement(@NotNull RichMediaElementGetReq req) {
}
public void addMsgListener(IKernelMsgListener listener) {
}

View File

@ -0,0 +1,30 @@
package com.tencent.qqnt.kernel.nativeinterface;
public final class BigDataTicket {
public String sessionKey;
public String sessionSig;
public BigDataTicket() {
this.sessionSig = "";
this.sessionKey = "";
}
public String getSessionKey() {
return this.sessionKey;
}
public String getSessionSig() {
return this.sessionSig;
}
public String toString() {
return "BigDataTicket{sessionSig=" + this.sessionSig + ",sessionKey=" + this.sessionKey + ",}";
}
public BigDataTicket(String str, String str2) {
this.sessionSig = "";
this.sessionKey = "";
this.sessionSig = str;
this.sessionKey = str2;
}
}

View File

@ -264,6 +264,10 @@ public final class PicElement implements IKernelModel {
this.transferStatus = num;
}
public int getStoreID() {
return 0;
}
public String toString() {
return "PicElement{picSubType=" + this.picSubType + ",fileName=" + this.fileName + ",fileSize=" + this.fileSize + ",picWidth=" + this.picWidth + ",picHeight=" + this.picHeight + ",original=" + this.original + ",md5HexStr=" + this.md5HexStr + ",sourcePath=" + this.sourcePath + ",thumbPath=" + this.thumbPath + ",transferStatus=" + this.transferStatus + ",progress=" + this.progress + ",picType=" + this.picType + ",invalidState=" + this.invalidState + ",fileUuid=" + this.fileUuid + ",fileSubId=" + this.fileSubId + ",thumbFileSize=" + this.thumbFileSize + ",fileBizId=" + this.fileBizId + ",downloadIndex=" + this.downloadIndex + ",summary=" + this.summary + ",emojiFrom=" + this.emojiFrom + ",emojiWebUrl=" + this.emojiWebUrl + ",emojiAd=" + this.emojiAd + ",emojiMall=" + this.emojiMall + ",emojiZplan=" + this.emojiZplan + ",originImageMd5=" + this.originImageMd5 + ",originImageUrl=" + this.originImageUrl + ",importRichMediaContext=" + this.importRichMediaContext + ",isFlashPic=" + this.isFlashPic + ",}";
}

View File

@ -0,0 +1,118 @@
package com.tencent.qqnt.kernel.nativeinterface;
public final class RichMediaElementGetReq implements IKernelModel {
public int chatType;
public int downSourceType;
public int downloadType;
public long elementId;
public long fileModelId;
public String filePath;
public long msgId;
public String peerUid;
public int thumbSize;
public int triggerType;
public RichMediaElementGetReq() {
this.peerUid = "";
this.filePath = "";
}
public int getChatType() {
return this.chatType;
}
public int getDownSourceType() {
return this.downSourceType;
}
public int getDownloadType() {
return this.downloadType;
}
public long getElementId() {
return this.elementId;
}
public long getFileModelId() {
return this.fileModelId;
}
public String getFilePath() {
return this.filePath;
}
public long getMsgId() {
return this.msgId;
}
public String getPeerUid() {
return this.peerUid;
}
public int getThumbSize() {
return this.thumbSize;
}
public int getTriggerType() {
return this.triggerType;
}
public void setChatType(int i2) {
this.chatType = i2;
}
public void setDownSourceType(int i2) {
this.downSourceType = i2;
}
public void setDownloadType(int i2) {
this.downloadType = i2;
}
public void setElementId(long j2) {
this.elementId = j2;
}
public void setFileModelId(long j2) {
this.fileModelId = j2;
}
public void setFilePath(String str) {
this.filePath = str;
}
public void setMsgId(long j2) {
this.msgId = j2;
}
public void setPeerUid(String str) {
this.peerUid = str;
}
public void setThumbSize(int i2) {
this.thumbSize = i2;
}
public void setTriggerType(int i2) {
this.triggerType = i2;
}
public String toString() {
return "RichMediaElementGetReq{msgId=" + this.msgId + ",peerUid=" + this.peerUid + ",chatType=" + this.chatType + ",elementId=" + this.elementId + ",downloadType=" + this.downloadType + ",thumbSize=" + this.thumbSize + ",filePath=" + this.filePath + ",fileModelId=" + this.fileModelId + ",downSourceType=" + this.downSourceType + ",triggerType=" + this.triggerType + ",}";
}
public RichMediaElementGetReq(long j2, String str, int i2, long j3, int i3, int i4, String str2, long j4, int i5, int i6) {
this.peerUid = "";
this.filePath = "";
this.msgId = j2;
this.peerUid = str;
this.chatType = i2;
this.elementId = j3;
this.downloadType = i3;
this.thumbSize = i4;
this.filePath = str2;
this.fileModelId = j4;
this.downSourceType = i5;
this.triggerType = i6;
}
}

View File

@ -13,8 +13,11 @@ import tencent.im.oidb.oidb_sso
internal object TicketSvc: BaseSvc() {
object SigType {
const val WLOGIN_A2 = 64
const val WLOGIN_A5 = 2
const val WLOGIN_RESERVED = 16
const val WLOGIN_STWEB = 32 // TLV 103
const val WLOGIN_A2 = 64
const val WLOGIN_ST = 128
const val WLOGIN_AQSIG = 2097152
const val WLOGIN_D2 = 262144
const val WLOGIN_DA2 = 33554432
@ -26,14 +29,17 @@ internal object TicketSvc: BaseSvc() {
const val WLOGIN_PSKEY = 1048576
const val WLOGIN_PT4Token = 134217728
const val WLOGIN_QRPUSH = 67108864
const val WLOGIN_RESERVED = 16
const val WLOGIN_SID = 524288
const val WLOGIN_SIG64 = 8192
const val WLOGIN_SKEY = 4096
const val WLOGIN_ST = 128
const val WLOGIN_STWEB = 32 // TLV 103
const val WLOGIN_TOKEN = 32768
const val WLOGIN_VKEY = 131072
val ALL_TICKET = arrayOf(
WLOGIN_A5, WLOGIN_RESERVED, WLOGIN_STWEB, WLOGIN_A2, WLOGIN_ST, WLOGIN_AQSIG, WLOGIN_D2, WLOGIN_DA2,
WLOGIN_LHSIG, WLOGIN_LSKEY, WLOGIN_OPENKEY, WLOGIN_PAYTOKEN, WLOGIN_PF, WLOGIN_PSKEY, WLOGIN_PT4Token,
WLOGIN_QRPUSH, WLOGIN_SID, WLOGIN_SIG64, WLOGIN_SKEY, WLOGIN_TOKEN, WLOGIN_VKEY
)
}
fun getUin(): String {

View File

@ -141,8 +141,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
//LogCenter.log(image.toString())
val originalUrl = image.originImageUrl ?: ""
LogCenter.log("receive image: $image", Level.DEBUG)
//LogCenter.log({ "receive image: $image" }, Level.DEBUG)
return MessageSegment(
type = "image",

View File

@ -164,10 +164,13 @@ internal object RichProtoSvc: BaseSvc() {
fun getGroupPicDownUrl(
originalUrl: String,
md5: String
md5: String,
): String {
val domain = if (originalUrl.contains("rkey=")) GPRO_PIC_NT else GPRO_PIC
val domain = if (originalUrl.startsWith("/download")) GPRO_PIC_NT else GPRO_PIC
if (originalUrl.isNotEmpty()) {
if (!originalUrl.contains("rkey=")) {
return "https://$domain$originalUrl&rkey=CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
}
return "https://$domain$originalUrl"
}
return "https://$domain/gchatpic_new/0/0-0-${md5.uppercase()}/0?term=2"
@ -187,7 +190,9 @@ internal object RichProtoSvc: BaseSvc() {
originalUrl: String,
md5: String
): String {
val domain = if (originalUrl.contains("rkey=")) GPRO_PIC_NT else GPRO_PIC
val domain = if (originalUrl.startsWith("/download") ||
originalUrl.contains("rkey=")) GPRO_PIC_NT
else GPRO_PIC
if (originalUrl.isNotEmpty()) {
return "https://$domain$originalUrl"
}

View File

@ -1,9 +1,12 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import com.tencent.guild.api.transfile.IGuildTransFileApi
import com.tencent.mobileqq.qroute.QRoute
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.remote.service.data.BigDataTicket
import moe.fuqiuluo.shamrock.remote.service.data.Credentials
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.symbols.OneBotHandler
@ -17,10 +20,20 @@ internal object GetCookies: IActionHandler() {
}
operator fun invoke(echo: JsonElement = EmptyJsonString): String {
return ok(Credentials(cookie = TicketSvc.getCookie()), echo)
return ok(Credentials(
cookie = TicketSvc.getCookie(),
bigDataTicket = QRoute.api(IGuildTransFileApi::class.java).bigDataTicket?.let {
BigDataTicket(it.sessionKey, it.sessionSig)
}
), echo)
}
suspend operator fun invoke(domain: String, echo: JsonElement = EmptyJsonString): String {
return ok(Credentials(cookie = TicketSvc.getCookie(domain)), echo)
return ok(Credentials(
cookie = TicketSvc.getCookie(domain),
bigDataTicket = QRoute.api(IGuildTransFileApi::class.java).bigDataTicket?.let {
BigDataTicket(it.sessionKey, it.sessionSig)
}
), echo)
}
}

View File

@ -5,7 +5,9 @@ import moe.fuqiuluo.qqinterface.servlet.TicketSvc
import io.ktor.server.application.call
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.shamrock.remote.action.handlers.*
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
import moe.fuqiuluo.shamrock.remote.structures.Status
import moe.fuqiuluo.shamrock.tools.*
@ -44,20 +46,36 @@ fun Routing.ticketActions() {
}
}
fun getTicket(uin: String, id: Int, debug: Boolean = false) = TicketSvc.getTicket(uin, id)?.let {
mutableMapOf(
"sig" to (it._sig?.toHexString() ?: "null"),
"key" to (it._sig_key?.toHexString() ?: "null")
).also { map ->
if (debug)
map["content"] = ((it._sig?.decodeToString() ?: "") + ":" + (it._sig_key?.decodeToString() ?: "null"))
}.json.asJsonObject
} ?: EmptyJsonObject
getOrPost("/get_ticket") {
val uin = fetchOrThrow("uin")
val ticket = when(val id = fetchOrThrow("id").toInt()) {
32 -> TicketSvc.getStWeb(uin)
else -> {
respond(true, Status.Ok, data = TicketSvc.getTicket(uin, id)?.let {
mapOf(
"sig" to (it._sig?.toHexString() ?: "null"),
"key" to (it._sig_key?.toHexString() ?: "null")
).json.asJsonObject
} ?: EmptyJsonObject)
respond(true, Status.Ok, data = getTicket(uin, id))
return@getOrPost
}
}
respond(true, Status.Ok, data = ticket)
}
if (ShamrockConfig.isDev()) getOrPost("/get_all_ticket") {
val uin = fetchOrThrow("uin")
val ticketMap = mutableMapOf<Int, JsonElement>()
TicketSvc.SigType.ALL_TICKET.forEach {
ticketMap[it] = getTicket(uin, it, true)
}
respond(true, Status.Ok, data = ticketMap)
}
}

View File

@ -6,5 +6,12 @@ import kotlinx.serialization.Serializable
@Serializable
internal data class Credentials(
@SerialName("token") val bkn: String = "",
@SerialName("cookies") val cookie: String = ""
@SerialName("cookies") val cookie: String = "",
@SerialName("bigdata_ticket") val bigDataTicket: BigDataTicket? = null
)
@Serializable
data class BigDataTicket(
var key: String? = null,
var sig: String? = null
)

View File

@ -5,11 +5,14 @@ package moe.fuqiuluo.shamrock.xposed.hooks
import android.content.Context
import com.tencent.mobileqq.perf.block.BinderMethodProxy
import com.tencent.mobileqq.qmmkv.MMKVOptionEntity
import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qqnt.aio.api.IAIOPicDownloaderProvider
import de.robv.android.xposed.XposedBridge
import epic.EIPCClient
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.beforeHook
import moe.fuqiuluo.shamrock.tools.hookMethod
import moe.fuqiuluo.shamrock.tools.toInnerValuesString
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader
import moe.fuqiuluo.symbols.Process
import moe.fuqiuluo.symbols.XposedHook
@ -18,7 +21,15 @@ import java.lang.reflect.Modifier
@XposedHook(priority = -1, process = Process.ALL)
internal class HookForDebug: IAction {
override fun invoke(ctx: Context) {
/*val NtDnsManager = LuoClassloader.load("com.tencent.qqnt.dns.NtDnsManager")!!
//val LibraDownloader = QRoute.api(IAIOPicDownloaderProvider::class.java).provideDownloader().javaClass
//LibraDownloader.hookMethod("downLoad").before {
// val option = it.args[0]
// LogCenter.log("LibraDownloader.downLoad(${option.toInnerValuesString()})")
//}
}
}
/*val NtDnsManager = LuoClassloader.load("com.tencent.qqnt.dns.NtDnsManager")!!
val NtDnsInternal = NtDnsManager.declaredMethods.first {
!Modifier.isStatic(it.modifiers) && it.parameterCount == 0
}.returnType
@ -33,9 +44,6 @@ internal class HookForDebug: IAction {
LogCenter.log("NtDnsManager: reqDomain2IpList($domain, $type)")
LogCenter.log(Exception().stackTraceToString())
})*/
}
}
/*
val httpEngineService = AppRuntimeFetcher.appRuntime
.getRuntimeService(IHttpEngineService::class.java, "all")