Jadx + Javassist 逆向Github Copliot插件
本帖最后由 初亦泽 于 2023-10-14 21:23 编辑提前声明:由于GitHub copilot是服务端下发的代码补全,因此如果不内置合法账号,则无法通过逆向本地文件获取copilot代码补全能力,本文仅讨论本地插件的改造
# 1 、前言
最近GitHub copilot过期了,之前也未想过jetbrains插件的逆向。于是乎研究了一下GitHub copilot插件的的激活机制,遗憾的是最后发现GitHub copilot的提示都是云端下发的,**本地的修改是不能通过服务端校验**。
不过也许会有人想对插件进行定制化改造,这时候本篇文章或许可以提供一些有用的参考
# 2、开发环境
Jadx:https://github.com/skylot/jadx/releases 下载最新版或者爱盘里的版本均可
Idea: 2023.2.3 自行寻找下载
GitHub copilot 下载地址:https://plugins.jetbrains.com/plugin/17718-github-copilot/versions
Javassist :3.29.2-GA可以新建gradle工程从maven仓库引入依赖
# 3、分析
## 3.1 目标
我的目标是将copilot插件的状态mock为登录态
## 3.2 插件的结构
从插件官网下载插件到本地后,我们可以发现插件是个zip文件,目录结构之这样的
打开lib目录,我们发现都是由jar包组成的,于是乎插件的逆向就简化为对jar的逆向
## 3.3 定位关键点
由于是jar包的逆向,我们无法用x64DBG、OD这类工具进行调试和修改了,我们可以用Jadx打开jar包
打开后发现,插件没有混淆,通过搜索关键词 signed in 我们可以快速定位到关键类AuthStatusResult
AuthStatusResult中保存了账号信息,包含user和status信息,其内部枚举类Status更为清晰,我们只需要将AuthStatusResult获取status的方法返回枚举类型Status.Ok,将user返回一个非空字符串就能达到 mock登陆状态的目的
```java
public enum Status {
Ok("OK"),
MaybeOk("MaybeOK"),
NotSignedIn("NotSignedIn"),
NotAuthorized("NotAuthorized"),
FailedToGetToken("FailedToGetToken"),
TokenInvalid("TokenInvalid");
private final String id;
Status(String id) {
this.id = id;
}
...
}
```
## 3.4 修改关键类
我们无法用x64DBG和OD来修改jar包,因此我们需要专业的字节码操作库Javassist来实现字节码的修改,Javassist拥有方法体替换、构造函数替换等一系列能力
我们要做的就是用它把AuthStatusResult改造成我们想要的样子
我们的目标是 修改class文件,并将其打入修改后的jar包
### 3.4.1 新建工程
Javassist是一个第三方库,我们可以用Idea新建一个gradle 构建的Kotlin工程,调用相关的方法
### 3.4.2 包装工具类
首先包装一个工具,帮助我们更好的来修改class文件,下面代码的作用就是替换方法体和构造函数.
**注意替换代码文本中如果涉及到类,需要类的全名,例如com.github.copilot.lang.agent.commands.AuthStatusResult,如果是内部类,则需要用 外部类全名$内部类名称,例如:com.github.copilot.lang.agent.commands.AuthStatusResult$Status**
···java
fun modifyClass(
jarPath: String,
className: String,
patchClass: PatchClass
): PatchedClass {
ClassPool.getDefault().insertClassPath(jarPath)
val classToModify = ClassPool.getDefault().getCtClass(className)
patchClass.patchConstructors.forEach { data ->
val paramsTypes = data.parameterTypes.map {
ClassPool.getDefault().get(it)
}.toTypedArray()
var cont = try {
classToModify.getDeclaredConstructor(paramsTypes)
} catch (_: Exception) {
null
}
if (cont == null) {
cont = CtNewConstructor.make(paramsTypes, null, "{ ${data.body} }", classToModify)
classToModify.addConstructor(cont)
} else {
cont.setBody("{ ${data.body} }")
}
}
patchClass.patchMethods.forEach { data ->
val methodToModify = classToModify.getDeclaredMethod(data.name)
methodToModify.setBody(data.body)
}
val exportClassFolder = File(jarPath).parent + File.separator + "crack"
classToModify.writeFile(exportClassFolder)
val path = exportClassFolder + File.separator + className.replace(".", File.separator) + ".class"
return PatchedClass(className, path)
}
···
### 3.4.3 替换代码
调用工具类替换方法体
```java
fun mockLoginStatus(jarPath: String): PatchedClass {
val className = "com.github.copilot.lang.agent.commands.AuthStatusResult"
return ByteCodeAssist.modifyClass(
jarPath = jarPath,
className = className,
patchClass = PatchClass(
patchMethods = listOf(
PatchMethod("setStatus", "this.status = ${makeClassName(className, "Status")}.Ok;"),
PatchMethod(
"setUser"
),
PatchMethod("setErrorMessage"),
PatchMethod("getErrorMessage", "return null;"),
PatchMethod("isSignedIn", "return true;"),
PatchMethod("isUnauthorized", "return false;"),
PatchMethod("getStatus", "return ${makeClassName(className, "Status")}.Ok;"),
PatchMethod(
"getUser", """
return "yize";
""".trimIndent()
),
PatchMethod("isError", "return false;"),
PatchMethod(
"forFailedToGetToken", """
return new com.github.copilot.lang.agent.commands.AuthStatusResult(${
makeClassName(
className,
"Status"
)
}.Ok, "yize", null);
""".trimIndent()
),
PatchMethod(
"forError", """
return new com.github.copilot.lang.agent.commands.AuthStatusResult(${
makeClassName(
className,
"Status"
)
}.Ok, "yize", null);
""".trimIndent()
)
),
patchConstructors = listOf(
PatchConstructor(
parameterTypes = listOf(
makeClassName(className, "Status"),
String::class.java.name,
String::class.java.name
),
body = """
this.user = "yize";
this.status = ${makeClassName(className, "Status")}.Ok;
this.errorMessage = null;
""".trimIndent()
),
PatchConstructor(
body = """
this.user = "yize";
this.status = ${makeClassName(className, "Status")}.Ok;
this.errorMessage = null;
""".trimIndent()
)
)
)
)
}
```
### 3.4.4 将修改后的class文件打包进修改版的jar包
```java
fun packageClassToJar(originalJarPath: String, patchedList: List<PatchedClass>): String? {
val newJarPath = "$originalJarPath.crack.jar"
try {
val entrySet = patchedList.map { it.entryName }.toSet()
// Create a new JAR file
val newJar = JarOutputStream(File(newJarPath).outputStream())
// Copy entries from the original JAR to the new JAR
ZipInputStream(File(originalJarPath).inputStream()).use { originalZip ->
while (true) {
val entry = originalZip.nextEntry ?: break
if (!entrySet.contains(entry.name)) {
newJar.putNextEntry(ZipEntry(entry.name))
newJar.write(originalZip.readBytes())
newJar.closeEntry()
}
}
}
// Add the new class to the new JAR
patchedList.forEach { data ->
newJar.putNextEntry(ZipEntry(data.entryName)) // 文件路径名,例如com/github/copilot/lang/agent/commands/AuthStatusResult.class
File(data.classFilePath).inputStream().use { input ->
newJar.write(input.readBytes())
}
}
newJar.closeEntry()
// Close the new JAR file
newJar.close()
} catch (e: Exception) {
return null
}
return newJarPath
}
```
我们填一些必要信息,测试一下
然后用jadx打开我们修改后的jar文件,可以看到已经修改成功
我们再用修改后的jar包替换原有的core.jar,在Idea中选择从本地导入插件,重启后看idea底部的GitHub copliot图标已变为登陆状态
但是我们仍然无法拥有GitHub copilot的代码补全能力,因为代码补全完全是服务端通过jsonrpc下发的,除非有能通过服务端校验的正式会员信息。可以通过使用Javaassist修改,内置正确的账号信息来实现有实际代码补全能力的插件
# 4 结束
问 :为什么要给Javaassist封装一层调用接口呢?
答:实现自动化修改的打包,假如jar文件中有很多类需要修改,不封装的话一个一个的手动改太麻烦了
完整版工程代码已开源到GitHub,不止可以用于copilot插件,也可根据实际情况适当修改,用于其它Java程序。
bestyize/ByteMate (github.com)
淘宝上都是软件破解,不知道怎么弄得 本帖最后由 初亦泽 于 2023-10-15 13:39 编辑
猫子1992 发表于 2023-10-15 09:24
https://zhile.io/2023/09/09/github-got-banned.html#more-468楼主试试这个 Github Copliot 插件破解工 ...
这个本质上也是内置账号,作者替换了copilot的token请求接口,返回正确的token
这种肯定是服务端控制的了。没账号是没办法的·· 厉害厉害,马上使用起来...... 看完了也没懂,逆向以后,我是可以直接免费用vip功能吗? https://zhile.io/2023/09/09/github-got-banned.html#more-468楼主试试这个 Github Copliot 插件破解工具 初亦泽 发表于 2023-10-15 11:35
这个本质上也是内置账号,作者替换了copilot的token请求接口,返回正确的token
求万能账号 Javaassist 用这个修改类和方法的教程多发一些吧,,,上次修改成功了,可是某些方法体始终无法修改。。。最后通过一个字节码修改工具进行了修改,好像smali代码什么的,也是破解成功。。。。不过还是觉得Javaassist特别好, {:1_921:} 定制化改造是个好思路