初亦泽 发表于 2023-10-14 21:14

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)

lucklys 发表于 2023-10-15 10:31

淘宝上都是软件破解,不知道怎么弄得

初亦泽 发表于 2023-10-15 11:35

本帖最后由 初亦泽 于 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

xiaoniu88 发表于 2023-10-14 21:35

这种肯定是服务端控制的了。没账号是没办法的··

mygrace 发表于 2023-10-14 21:59

厉害厉害,马上使用起来......

jinzhu160 发表于 2023-10-15 09:24

看完了也没懂,逆向以后,我是可以直接免费用vip功能吗?

猫子1992 发表于 2023-10-15 09:24

https://zhile.io/2023/09/09/github-got-banned.html#more-468楼主试试这个 Github Copliot 插件破解工具

gagmeng 发表于 2023-10-16 09:13

初亦泽 发表于 2023-10-15 11:35
这个本质上也是内置账号,作者替换了copilot的token请求接口,返回正确的token

求万能账号

yasenhacker 发表于 2023-10-16 14:01

Javaassist   用这个修改类和方法的教程多发一些吧,,,上次修改成功了,可是某些方法体始终无法修改。。。最后通过一个字节码修改工具进行了修改,好像smali代码什么的,也是破解成功。。。。不过还是觉得Javaassist特别好,

fnckyon2014 发表于 2023-10-16 16:33

{:1_921:} 定制化改造是个好思路
页: [1] 2 3 4
查看完整版本: Jadx + Javassist 逆向Github Copliot插件