cr4n5 / XiaoYuanKouSuan

小猿口算_已达到0.00s
1.39k stars 179 forks source link

包加密的 #71

Open Jaffe2718 opened 1 month ago

Jaffe2718 commented 1 month ago
721ae3e15d0ea58ca96370bcf04c4d2

好像现在Server发向Client的响应包已经加密了,如果逆向工程没有什么进一步的突破,那改包方案就到此为止了

模拟器MuMu 抓包软件Reqable(安卓+Windows协同抓包)

xperiavnc commented 1 month ago

寄了

ZeroQing89 commented 1 month ago

很蓝的呐

sd0ric4 commented 1 month ago

在逆了

ZeroQing89 commented 1 month ago

在逆了

逆向大佬竟在我身边

sd0ric4 commented 1 month ago
import { Base64 } from 'js-base64'
import { addFrog, runUniqueApi } from '@solar/webview'

export const encryptRequestBody = (data: any): Promise<ArrayBuffer> => {
  const dataJson = JSON.stringify(data)
  return new Promise((resolve, reject) => {
    runUniqueApi('dataEncrypt', {
      base64: Base64.encode(dataJson),
      trigger: async (status: any, data: any) => {
        if (data && data.result) {
          const res = base64ToUint8Array(data.result).buffer
          resolve(res)
        } else {
          reject(Error('encrypt data fail'))
          addFrog({
            url: '/debug/oralPK/dataEncryptFailed',
            params: {
              status: status,
              dataJson: dataJson
            },
            flushFrog: false
          })
        }
      }
    }, 'LeoSecure')
  })
}

const base64ToUint8Array = (base64String: string): Uint8Array => {
  const padding = '='.repeat((4 - base64String.length % 4) % 4)
  const base64 = (base64String + padding)
    .replace(/-/g, '+')
    .replace(/_/g, '/')
  const rawData = Base64.atob(base64)
  const outputArray = new Uint8Array(rawData.length)
  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i)
  }
  return outputArray
}

/**
 * 解密 ResponseBody
 */
export const DecryptData = (target: any, _key: string, descriptor: PropertyDescriptor) => {
  const oldMethod = descriptor.value
  const newMethod = async (...args: any) => {
    return oldMethod.apply(target, args).then(async (res: any) => {
      // @ts-ignore
      const buffer = btoa(String.fromCharCode.apply(null, new Uint8Array(res)))
      return await dataDecrypt(buffer)
    }).catch((err: any) => {
      throw err
    })
  }
  descriptor.value = newMethod
  return descriptor
}

const dataDecrypt = (result: any) => {
  return new Promise(resolve => {
    runUniqueApi('dataDecrypt', {
      base64: result,
      trigger: (status: any, data: any) => {
        const decryptedData = JSON.parse(Base64.decode(data.result))
        resolve(decryptedData)
        if (process.env.VUE_APP_CONFIG === 'test') {
          console.log('decrypted data: ', decryptedData)
        }
      }
    }, 'LeoSecure')
  })
}
sd0ric4 commented 1 month ago

翻出来一个

sd0ric4 commented 1 month ago

不知道是不是

ZeroQing89 commented 1 month ago

不知道是不是

先测试一下

GSQZ commented 1 month ago

在逆了

66666

GSQZ commented 1 month ago

在逆了

66666

ZeroQing89 commented 1 month ago

Base64加密???

sd0ric4 commented 1 month ago

啊有大佬写了笔记,观摩中,但大佬好像还没写完 https://github.com/xmexg/xyks

ZeroQing89 commented 1 month ago

啊有大佬写了笔记,观摩中,但大佬好像还没写完 https://github.com/xmexg/xyks

那个大佬似乎也没能全部解完

Jaffe2718 commented 1 month ago

在逆了

66666

我倒是有个不用逆的思路,不清楚行不行

image

你看看这个PUT请求,是不是有costTime字段,我估计单位是毫秒,但是无所谓,直接改成0即可。 如果成功了我感觉效果是做题依然是手动做题,但是用时会被强行改为0,你可以试试

cr4n5 commented 1 month ago

在逆了

66666

我倒是有个不用逆的思路,不清楚行不行 image 你看看这个PUT请求,是不是有costTime字段,我估计单位是毫秒,但是无所谓,直接改成0即可。 如果成功了我感觉效果是做题依然是手动做题,但是用时会被强行改为0,你可以试试

没用的,这只会在本地显示,服务器上未有任何更改

ZeroQing89 commented 1 month ago

在逆了

66666

我倒是有个不用逆的思路,不清楚行不行 image 你看看这个PUT请求,是不是有costTime字段,我估计单位是毫秒,但是无所谓,直接改成0即可。 如果成功了我感觉效果是做题依然是手动做题,但是用时会被强行改为0,你可以试试

找不到接口感觉没用啊

Jaffe2718 commented 1 month ago

@ZeroQing89 感觉本地也够装一波了,但是目前不清楚怎么改

ZeroQing89 commented 1 month ago

@ZeroQing89 感觉本地也够装一波了,但是目前不清楚怎么改

本地就没意思了,这和F12调试侠有什么两样啊😂😂😂

ZeroQing89 commented 1 month ago

现在最大的问题就是逆向成本有点高

xingyuxinyuan commented 1 month ago

很难的啦

masknull commented 1 month ago
import { Base64 } from 'js-base64'
import { addFrog, runUniqueApi } from '@solar/webview'

export const encryptRequestBody = (data: any): Promise<ArrayBuffer> => {
  const dataJson = JSON.stringify(data)
  return new Promise((resolve, reject) => {
    runUniqueApi('dataEncrypt', {
      base64: Base64.encode(dataJson),
      trigger: async (status: any, data: any) => {
        if (data && data.result) {
          const res = base64ToUint8Array(data.result).buffer
          resolve(res)
        } else {
          reject(Error('encrypt data fail'))
          addFrog({
            url: '/debug/oralPK/dataEncryptFailed',
            params: {
              status: status,
              dataJson: dataJson
            },
            flushFrog: false
          })
        }
      }
    }, 'LeoSecure')
  })
}

const base64ToUint8Array = (base64String: string): Uint8Array => {
  const padding = '='.repeat((4 - base64String.length % 4) % 4)
  const base64 = (base64String + padding)
    .replace(/-/g, '+')
    .replace(/_/g, '/')
  const rawData = Base64.atob(base64)
  const outputArray = new Uint8Array(rawData.length)
  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i)
  }
  return outputArray
}

/**
 * 解密 ResponseBody
 */
export const DecryptData = (target: any, _key: string, descriptor: PropertyDescriptor) => {
  const oldMethod = descriptor.value
  const newMethod = async (...args: any) => {
    return oldMethod.apply(target, args).then(async (res: any) => {
      // @ts-ignore
      const buffer = btoa(String.fromCharCode.apply(null, new Uint8Array(res)))
      return await dataDecrypt(buffer)
    }).catch((err: any) => {
      throw err
    })
  }
  descriptor.value = newMethod
  return descriptor
}

const dataDecrypt = (result: any) => {
  return new Promise(resolve => {
    runUniqueApi('dataDecrypt', {
      base64: result,
      trigger: (status: any, data: any) => {
        const decryptedData = JSON.parse(Base64.decode(data.result))
        resolve(decryptedData)
        if (process.env.VUE_APP_CONFIG === 'test') {
          console.log('decrypted data: ', decryptedData)
        }
      }
    }, 'LeoSecure')
  })
}
res = flow.response.content
    # 将字节数组转换为 Base64 编码的字符串
    base64_encoded = base64.b64encode(res).decode('utf-8')
    print(base64_encoded)

输出:y7KptyngpGugGocxaq6J8I5IOevoCyCJyfSyBRCvsA9fVeNKQdBN1FMynCeJODBM4fxyyvxunnezVxMQZV1oJhy9RBBg/j5bZjibxUFzdU0VwIuvZJLOQTrnyiHkQTiFMN1RBE3GhiNrsaJvYCjjC0h...

最终跟到webpack://leo-web-oral-pk/node_modules/@solar/solar-web-bridge/lib/native.js messageHandler[name]=dataDecrypt,传入encodeParam参数后跟不动了

微信截图_20241011181602

encodeParam参数格式:base64 {"arguments":[{"base64":"y7KptyngpGugGocxaq6J8I5IOevoCyCJyfSyBRCvsA9fVeNKQdBN1FMynCeJODBM4fxyyvxunnezVxMQZV1oJhy9RBBg/j5bZjibxUFzdU0VwIuvZJLOQTrnyiHkQTiFMN1RBE3GhiNrsaJvYCjjC0h...","trigger":"dataDecrypt_1728641257271_14"}],"callback":"dataDecrypt_callback_1728641257271_15"}

这里处理完会返回一个base64的数据,最终解码得到明文 eyJwa0lkU3RyIjoiNjA5NDM0NDc...

补充:解密函数dataDecrypt似乎在LeoSecureWebViewApi.java里,不太懂java

xperiavnc commented 1 month ago

抖音上已经有人研究出跳过答题了

ZQBCWG commented 1 month ago
import { Base64 } from 'js-base64'
import { addFrog, runUniqueApi } from '@solar/webview'

export const encryptRequestBody = (data: any): Promise<ArrayBuffer> => {
  const dataJson = JSON.stringify(data)
  return new Promise((resolve, reject) => {
    runUniqueApi('dataEncrypt', {
      base64: Base64.encode(dataJson),
      trigger: async (status: any, data: any) => {
        if (data && data.result) {
          const res = base64ToUint8Array(data.result).buffer
          resolve(res)
        } else {
          reject(Error('encrypt data fail'))
          addFrog({
            url: '/debug/oralPK/dataEncryptFailed',
            params: {
              status: status,
              dataJson: dataJson
            },
            flushFrog: false
          })
        }
      }
    }, 'LeoSecure')
  })
}

const base64ToUint8Array = (base64String: string): Uint8Array => {
  const padding = '='.repeat((4 - base64String.length % 4) % 4)
  const base64 = (base64String + padding)
    .replace(/-/g, '+')
    .replace(/_/g, '/')
  const rawData = Base64.atob(base64)
  const outputArray = new Uint8Array(rawData.length)
  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i)
  }
  return outputArray
}

/**
 * 解密 ResponseBody
 */
export const DecryptData = (target: any, _key: string, descriptor: PropertyDescriptor) => {
  const oldMethod = descriptor.value
  const newMethod = async (...args: any) => {
    return oldMethod.apply(target, args).then(async (res: any) => {
      // @ts-ignore
      const buffer = btoa(String.fromCharCode.apply(null, new Uint8Array(res)))
      return await dataDecrypt(buffer)
    }).catch((err: any) => {
      throw err
    })
  }
  descriptor.value = newMethod
  return descriptor
}

const dataDecrypt = (result: any) => {
  return new Promise(resolve => {
    runUniqueApi('dataDecrypt', {
      base64: result,
      trigger: (status: any, data: any) => {
        const decryptedData = JSON.parse(Base64.decode(data.result))
        resolve(decryptedData)
        if (process.env.VUE_APP_CONFIG === 'test') {
          console.log('decrypted data: ', decryptedData)
        }
      }
    }, 'LeoSecure')
  })
}
res = flow.response.content
    # 将字节数组转换为 Base64 编码的字符串
    base64_encoded = base64.b64encode(res).decode('utf-8')
    print(base64_encoded)

输出:y7KptyngpGugGocxaq6J8I5IOevoCyCJyfSyBRCvsA9fVeNKQdBN1FMynCeJODBM4fxyyvxunnezVxMQZV1oJhy9RBBg/j5bZjibxUFzdU0VwIuvZJLOQTrnyiHkQTiFMN1RBE3GhiNrsaJvYCjjC0h...

最终跟到webpack://leo-web-oral-pk/node_modules/@solar/solar-web-bridge/lib/native.js messageHandler[name]=dataDecrypt,传入encodeParam参数后跟不动了

微信截图_20241011181602

encodeParam参数格式:base64 {"arguments":[{"base64":"y7KptyngpGugGocxaq6J8I5IOevoCyCJyfSyBRCvsA9fVeNKQdBN1FMynCeJODBM4fxyyvxunnezVxMQZV1oJhy9RBBg/j5bZjibxUFzdU0VwIuvZJLOQTrnyiHkQTiFMN1RBE3GhiNrsaJvYCjjC0h...","trigger":"dataDecrypt_1728641257271_14"}],"callback":"dataDecrypt_callback_1728641257271_15"}

这里处理完会返回一个base64的数据,最终解码得到明文 eyJwa0lkU3RyIjoiNjA5NDM0NDc...

补充:解密函数dataDecrypt似乎在LeoSecureWebViewApi.java里,不太懂java

在这呢com.fenbi.android.leo.webapp.secure.commands.DataDecryptCommand$execute$1$decryptData$1.invokeSuspend

masknull commented 1 month ago
import { Base64 } from 'js-base64'
import { addFrog, runUniqueApi } from '@solar/webview'

export const encryptRequestBody = (data: any): Promise<ArrayBuffer> => {
  const dataJson = JSON.stringify(data)
  return new Promise((resolve, reject) => {
    runUniqueApi('dataEncrypt', {
      base64: Base64.encode(dataJson),
      trigger: async (status: any, data: any) => {
        if (data && data.result) {
          const res = base64ToUint8Array(data.result).buffer
          resolve(res)
        } else {
          reject(Error('encrypt data fail'))
          addFrog({
            url: '/debug/oralPK/dataEncryptFailed',
            params: {
              status: status,
              dataJson: dataJson
            },
            flushFrog: false
          })
        }
      }
    }, 'LeoSecure')
  })
}

const base64ToUint8Array = (base64String: string): Uint8Array => {
  const padding = '='.repeat((4 - base64String.length % 4) % 4)
  const base64 = (base64String + padding)
    .replace(/-/g, '+')
    .replace(/_/g, '/')
  const rawData = Base64.atob(base64)
  const outputArray = new Uint8Array(rawData.length)
  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i)
  }
  return outputArray
}

/**
 * 解密 ResponseBody
 */
export const DecryptData = (target: any, _key: string, descriptor: PropertyDescriptor) => {
  const oldMethod = descriptor.value
  const newMethod = async (...args: any) => {
    return oldMethod.apply(target, args).then(async (res: any) => {
      // @ts-ignore
      const buffer = btoa(String.fromCharCode.apply(null, new Uint8Array(res)))
      return await dataDecrypt(buffer)
    }).catch((err: any) => {
      throw err
    })
  }
  descriptor.value = newMethod
  return descriptor
}

const dataDecrypt = (result: any) => {
  return new Promise(resolve => {
    runUniqueApi('dataDecrypt', {
      base64: result,
      trigger: (status: any, data: any) => {
        const decryptedData = JSON.parse(Base64.decode(data.result))
        resolve(decryptedData)
        if (process.env.VUE_APP_CONFIG === 'test') {
          console.log('decrypted data: ', decryptedData)
        }
      }
    }, 'LeoSecure')
  })
}
res = flow.response.content
    # 将字节数组转换为 Base64 编码的字符串
    base64_encoded = base64.b64encode(res).decode('utf-8')
    print(base64_encoded)

输出:y7KptyngpGugGocxaq6J8I5IOevoCyCJyfSyBRCvsA9fVeNKQdBN1FMynCeJODBM4fxyyvxunnezVxMQZV1oJhy9RBBg/j5bZjibxUFzdU0VwIuvZJLOQTrnyiHkQTiFMN1RBE3GhiNrsaJvYCjjC0h... 最终跟到webpack://leo-web-oral-pk/node_modules/@solar/solar-web-bridge/lib/native.js messageHandler[name]=dataDecrypt,传入encodeParam参数后跟不动了 微信截图_20241011181602 encodeParam参数格式:base64 {"arguments":[{"base64":"y7KptyngpGugGocxaq6J8I5IOevoCyCJyfSyBRCvsA9fVeNKQdBN1FMynCeJODBM4fxyyvxunnezVxMQZV1oJhy9RBBg/j5bZjibxUFzdU0VwIuvZJLOQTrnyiHkQTiFMN1RBE3GhiNrsaJvYCjjC0h...","trigger":"dataDecrypt_1728641257271_14"}],"callback":"dataDecrypt_callback_1728641257271_15"} 这里处理完会返回一个base64的数据,最终解码得到明文 eyJwa0lkU3RyIjoiNjA5NDM0NDc... 补充:解密函数dataDecrypt似乎在LeoSecureWebViewApi.java里,不太懂java

在这呢com.fenbi.android.leo.webapp.secure.commands.DataDecryptCommand$execute$1$decryptData$1.invokeSuspend

最终看调用了com.fenbi.android.leo.imgsearch.sdk.utils.e.c System.loadLibrary("ContentEncoder")加载了本地库更不知道咋整了,师傅有思路嘛

Jaffe2718 commented 1 month ago

各位大佬逆向完了能不能封装成python库呀,这样方便偷懒,要是真的有这个库就舒服了 类似这样:

pip install xiaoyuan_toolkit
import xiaoyuan_tookit

# 抓包...
packet_json: dict = xiaoyuan_tookit.decrypt(b'被加密的内容')

# 解析并改包...
enc = xiaoyuan_toolkit.encrypt(packet_json)  # 重新加密
# 再把改后的包给客户端
ZQBCWG commented 1 month ago
import { Base64 } from 'js-base64'
import { addFrog, runUniqueApi } from '@solar/webview'

export const encryptRequestBody = (data: any): Promise<ArrayBuffer> => {
  const dataJson = JSON.stringify(data)
  return new Promise((resolve, reject) => {
    runUniqueApi('dataEncrypt', {
      base64: Base64.encode(dataJson),
      trigger: async (status: any, data: any) => {
        if (data && data.result) {
          const res = base64ToUint8Array(data.result).buffer
          resolve(res)
        } else {
          reject(Error('encrypt data fail'))
          addFrog({
            url: '/debug/oralPK/dataEncryptFailed',
            params: {
              status: status,
              dataJson: dataJson
            },
            flushFrog: false
          })
        }
      }
    }, 'LeoSecure')
  })
}

const base64ToUint8Array = (base64String: string): Uint8Array => {
  const padding = '='.repeat((4 - base64String.length % 4) % 4)
  const base64 = (base64String + padding)
    .replace(/-/g, '+')
    .replace(/_/g, '/')
  const rawData = Base64.atob(base64)
  const outputArray = new Uint8Array(rawData.length)
  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i)
  }
  return outputArray
}

/**
 * 解密 ResponseBody
 */
export const DecryptData = (target: any, _key: string, descriptor: PropertyDescriptor) => {
  const oldMethod = descriptor.value
  const newMethod = async (...args: any) => {
    return oldMethod.apply(target, args).then(async (res: any) => {
      // @ts-ignore
      const buffer = btoa(String.fromCharCode.apply(null, new Uint8Array(res)))
      return await dataDecrypt(buffer)
    }).catch((err: any) => {
      throw err
    })
  }
  descriptor.value = newMethod
  return descriptor
}

const dataDecrypt = (result: any) => {
  return new Promise(resolve => {
    runUniqueApi('dataDecrypt', {
      base64: result,
      trigger: (status: any, data: any) => {
        const decryptedData = JSON.parse(Base64.decode(data.result))
        resolve(decryptedData)
        if (process.env.VUE_APP_CONFIG === 'test') {
          console.log('decrypted data: ', decryptedData)
        }
      }
    }, 'LeoSecure')
  })
}
res = flow.response.content
    # 将字节数组转换为 Base64 编码的字符串
    base64_encoded = base64.b64encode(res).decode('utf-8')
    print(base64_encoded)

输出:y7KptyngpGugGocxaq6J8I5IOevoCyCJyfSyBRCvsA9fVeNKQdBN1FMynCeJODBM4fxyyvxunnezVxMQZV1oJhy9RBBg/j5bZjibxUFzdU0VwIuvZJLOQTrnyiHkQTiFMN1RBE3GhiNrsaJvYCjjC0h... 最终跟到webpack://leo-web-oral-pk/node_modules/@solar/solar-web-bridge/lib/native.js messageHandler[name]=dataDecrypt,传入encodeParam参数后跟不动了 微信截图_20241011181602 encodeParam参数格式:base64 {"arguments":[{"base64":"y7KptyngpGugGocxaq6J8I5IOevoCyCJyfSyBRCvsA9fVeNKQdBN1FMynCeJODBM4fxyyvxunnezVxMQZV1oJhy9RBBg/j5bZjibxUFzdU0VwIuvZJLOQTrnyiHkQTiFMN1RBE3GhiNrsaJvYCjjC0h...","trigger":"dataDecrypt_1728641257271_14"}],"callback":"dataDecrypt_callback_1728641257271_15"} 这里处理完会返回一个base64的数据,最终解码得到明文 eyJwa0lkU3RyIjoiNjA5NDM0NDc... 补充:解密函数dataDecrypt似乎在LeoSecureWebViewApi.java里,不太懂java

在这呢com.fenbi.android.leo.webapp.secure.commands.DataDecryptCommand$execute$1$decryptData$1.invokeSuspend

最终看调用了com.fenbi.android.leo.imgsearch.sdk.utils.e.c System.loadLibrary("ContentEncoder")加载了本地库更不知道咋整了,师傅有思路嘛

已经搞完了