活学活用?(x)
本文Diaclaimer:(从别处抄来的)首先明确,反编译别人apk是一件不厚道的事情。代码是程序员辛苦工作的成果,想通过这种手段不劳而获,是不对的。这也说明,代码混淆是非常重要的。本文抱着学习的态度,研究在一些特殊的情况下如果有需要,该怎么反编译apk。
以下是正文。
Too Long; Didn't Read
将验证函数中返回判断指令的操作数由 jz
修改为 jmp
。
(记录一哈免得明天忘了,毕竟刚才就差点忘记几个步骤了。老了老了 :-I)
因为有一些功能测试需要用到 Mattermost 交流软件的企业版功能(主要是分布式和集群处理的功能),奈何商业软件毕竟是付费的,用来做实验也划不来。
Mattermost 聊天软件提供了开源版本(Go语言编写),授权激活方式也在开源代码里写得明明白白,就是一个简单的验证公钥信息加密数据。
授权方式
从官网填写企业版 Trial 信息后,可以收到企业版全功能的安装包和一个授权 License 文件。License 文件内容为一个 Base64 后的串,经过比对项目检验授权部分的代码和解码 base64 串,可以得知是由一个 JSON 字符串、一个"."点符号和 RSA-SHA512(PKCS1v15) 私钥加密后的密文。
文件位置:mattermost/mattermost-server/utils/license.go
- L24: 定义公钥内容
var publicKey []byte = []byte(`-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyZmShlU8Z8HdG0IWSZ8r
...(省略一部分公钥)...
hwIDAQAB
-----END PUBLIC KEY-----`)
- L34: 验证授权信息、返回具体授权数据(用户数量限制、功能限制等信息)的方法
func ValidateLicense(signed []byte) (bool, string) {...}
- 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
。
跳转到这个函数的 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)