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.IGetTempChatInfoCallback;
import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgListener; import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgListener;
import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback; 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.RichMediaFilePathInfo;
import com.tencent.qqnt.kernel.nativeinterface.TempChatPrepareInfo; import com.tencent.qqnt.kernel.nativeinterface.TempChatPrepareInfo;
@ -10,6 +11,10 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public class MsgService { public class MsgService {
void getRichMediaElement(@NotNull RichMediaElementGetReq req) {
}
public void addMsgListener(IKernelMsgListener listener) { 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; this.transferStatus = num;
} }
public int getStoreID() {
return 0;
}
public String toString() { 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 + ",}"; 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() { internal object TicketSvc: BaseSvc() {
object SigType { object SigType {
const val WLOGIN_A2 = 64
const val WLOGIN_A5 = 2 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_AQSIG = 2097152
const val WLOGIN_D2 = 262144 const val WLOGIN_D2 = 262144
const val WLOGIN_DA2 = 33554432 const val WLOGIN_DA2 = 33554432
@ -26,14 +29,17 @@ internal object TicketSvc: BaseSvc() {
const val WLOGIN_PSKEY = 1048576 const val WLOGIN_PSKEY = 1048576
const val WLOGIN_PT4Token = 134217728 const val WLOGIN_PT4Token = 134217728
const val WLOGIN_QRPUSH = 67108864 const val WLOGIN_QRPUSH = 67108864
const val WLOGIN_RESERVED = 16
const val WLOGIN_SID = 524288 const val WLOGIN_SID = 524288
const val WLOGIN_SIG64 = 8192 const val WLOGIN_SIG64 = 8192
const val WLOGIN_SKEY = 4096 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_TOKEN = 32768
const val WLOGIN_VKEY = 131072 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 { fun getUin(): String {

View File

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

View File

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

View File

@ -1,9 +1,12 @@
package moe.fuqiuluo.shamrock.remote.action.handlers 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 kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.TicketSvc import moe.fuqiuluo.qqinterface.servlet.TicketSvc
import moe.fuqiuluo.shamrock.remote.action.ActionSession import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler 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.remote.service.data.Credentials
import moe.fuqiuluo.shamrock.tools.EmptyJsonString import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.symbols.OneBotHandler import moe.fuqiuluo.symbols.OneBotHandler
@ -17,10 +20,20 @@ internal object GetCookies: IActionHandler() {
} }
operator fun invoke(echo: JsonElement = EmptyJsonString): String { 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 { 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.application.call
import io.ktor.server.response.respondText import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing import io.ktor.server.routing.Routing
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.shamrock.remote.action.handlers.* 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.remote.structures.Status
import moe.fuqiuluo.shamrock.tools.* 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") { getOrPost("/get_ticket") {
val uin = fetchOrThrow("uin") val uin = fetchOrThrow("uin")
val ticket = when(val id = fetchOrThrow("id").toInt()) { val ticket = when(val id = fetchOrThrow("id").toInt()) {
32 -> TicketSvc.getStWeb(uin) 32 -> TicketSvc.getStWeb(uin)
else -> { else -> {
respond(true, Status.Ok, data = TicketSvc.getTicket(uin, id)?.let { respond(true, Status.Ok, data = getTicket(uin, id))
mapOf(
"sig" to (it._sig?.toHexString() ?: "null"),
"key" to (it._sig_key?.toHexString() ?: "null")
).json.asJsonObject
} ?: EmptyJsonObject)
return@getOrPost return@getOrPost
} }
} }
respond(true, Status.Ok, data = ticket) 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 @Serializable
internal data class Credentials( internal data class Credentials(
@SerialName("token") val bkn: String = "", @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 android.content.Context
import com.tencent.mobileqq.perf.block.BinderMethodProxy import com.tencent.mobileqq.perf.block.BinderMethodProxy
import com.tencent.mobileqq.qmmkv.MMKVOptionEntity 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 de.robv.android.xposed.XposedBridge
import epic.EIPCClient import epic.EIPCClient
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.beforeHook import moe.fuqiuluo.shamrock.tools.beforeHook
import moe.fuqiuluo.shamrock.tools.hookMethod import moe.fuqiuluo.shamrock.tools.hookMethod
import moe.fuqiuluo.shamrock.tools.toInnerValuesString
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader
import moe.fuqiuluo.symbols.Process import moe.fuqiuluo.symbols.Process
import moe.fuqiuluo.symbols.XposedHook import moe.fuqiuluo.symbols.XposedHook
@ -18,6 +21,14 @@ import java.lang.reflect.Modifier
@XposedHook(priority = -1, process = Process.ALL) @XposedHook(priority = -1, process = Process.ALL)
internal class HookForDebug: IAction { internal class HookForDebug: IAction {
override fun invoke(ctx: Context) { override fun invoke(ctx: Context) {
//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 NtDnsManager = LuoClassloader.load("com.tencent.qqnt.dns.NtDnsManager")!!
val NtDnsInternal = NtDnsManager.declaredMethods.first { val NtDnsInternal = NtDnsManager.declaredMethods.first {
!Modifier.isStatic(it.modifiers) && it.parameterCount == 0 !Modifier.isStatic(it.modifiers) && it.parameterCount == 0
@ -33,9 +44,6 @@ internal class HookForDebug: IAction {
LogCenter.log("NtDnsManager: reqDomain2IpList($domain, $type)") LogCenter.log("NtDnsManager: reqDomain2IpList($domain, $type)")
LogCenter.log(Exception().stackTraceToString()) LogCenter.log(Exception().stackTraceToString())
})*/ })*/
}
}
/* /*
val httpEngineService = AppRuntimeFetcher.appRuntime val httpEngineService = AppRuntimeFetcher.appRuntime
.getRuntimeService(IHttpEngineService::class.java, "all") .getRuntimeService(IHttpEngineService::class.java, "all")