活学活用?(x)

本文Diaclaimer:(从别处抄来的)首先明确,反编译别人apk是一件不厚道的事情。代码是程序员辛苦工作的成果,想通过这种手段不劳而获,是不对的。这也说明,代码混淆是非常重要的。本文抱着学习的态度,研究在一些特殊的情况下如果有需要,该怎么反编译apk。

以下是正文。

Too Long; Didn't Read
将验证函数中返回判断指令的操作数由 jz 修改为 jmp

(记录一哈免得明天忘了,毕竟刚才就差点忘记几个步骤了。老了老了 :-I)

因为有一些功能测试需要用到 Mattermost 交流软件的企业版功能(主要是分布式和集群处理的功能),奈何商业软件毕竟是付费的,用来做实验也划不来。
Mattermost 聊天软件提供了开源版本(Go语言编写),授权激活方式也在开源代码里写得明明白白,就是一个简单的验证公钥信息加密数据。

授权方式

从官网填写企业版 Trial 信息后,可以收到企业版全功能的安装包和一个授权 License 文件。License 文件内容为一个 Base64 后的串,经过比对项目检验授权部分的代码和解码 base64 串,可以得知是由一个 JSON 字符串、一个"."点符号和 RSA-SHA512(PKCS1v15) 私钥加密后的密文。

0c9d79f34c74ac2b4e216862822230d.png

文件位置:mattermost/mattermost-server/utils/license.go

  1. L24: 定义公钥内容
var publicKey []byte = []byte(`-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyZmShlU8Z8HdG0IWSZ8r
...(省略一部分公钥)...
hwIDAQAB
-----END PUBLIC KEY-----`)
  1. L34: 验证授权信息、返回具体授权数据(用户数量限制、功能限制等信息)的方法
func ValidateLicense(signed []byte) (bool, string) {...}
  1. L70-76: 用公钥加密授权数据,验证后若不成功,则结束验证。验证成功,则返回授权数据 json
70 err = rsa.VerifyPKCS1v15(rsaPublic, crypto.SHA512, d, signature)
71    if err != nil {
72        mlog.Error("Invalid signature", mlog.Err(err))
73        return false, ""
74    }
75
76    return true, string(plaintext)

因为自己生成的密钥一定会报错,故只需修改拼凑数据让 71 行的条件修改为 if true 即可。

功能定位

将企业版的二进制文件拖入 IDA,定位到 ValidateLicense(signed []byte) (bool, string) 函数。编译后的函数名为 github.com_mattermost_mattermost_server_v5_utils.ValidateLicense

1.png

跳转到这个函数的 IDA View,通过定位源代码中 70 行的系列指令来确定下一步跳转的指令。


可以看到,关键的判断指令即为 jz loc_9FAF32。在这里使用的汇编语言中,jz 即不满足条件则跳转的操作数。接下来就是将 jz 操作数修改为 jmp 操作数,来实现无论什么判断结果,都跳转到程序中 76 行的 return true, string(plaintext)

在 Hex View 中查看到这条指令的 Hex 值:

|jz|loc_9FAF32 = 0F 84 2B 01 00 00
|0F|84 .... 00 

另查询到 jmp 操作数的 Hex:

jmp = E9

添加一个 Patch, 将地址 0x09FAE00 的第一字节修改为 E9

修改好之后,可以看到操作数已经变为 jmp

保存二进制文件,放入服务器可以正常运行。

基于原始 License 生成新 license 的程序

import base64
import json
from datetime import datetime

encoded = "eyJ...(原始 license)...MVPg=="

decoded = base64.b64decode(encoded)

plaintext = decoded[:len(decoded) - 256]

plaintext_obj = json.loads(plaintext)

plaintext_obj['expires_at'] = datetime.strptime('2050-12-31 00:00:00,00', '%Y-%m-%d %H:%M:%S,%f').timestamp()
plaintext_obj['features']['users'] = 10000

new_encoded = base64.b64encode(bytes(json.dumps(plaintext_obj), encoding='utf-8') + decoded[len(decoded) - 256:])

print(new_encoded)
Last modification:June 9th, 2020 at 07:50 pm