欢乐书客加密算法及 360 加固脱壳方式研究
source link: https://violarulan.github.io/blog/hbooker-encryption-360-jiagu-reverse-engineering/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
换到 Hugo 作为新程序写文章,感觉省事了很多。
书客是近一两年来增长很快的小说站,主要是宅文比较多,有时候也会看点书在上面;自己也有一个聚合了所有小说站的追书爬虫,需要接口抓书客数据。
其实一看这前端像是线上的测试版直接发上来的,然而书客的加密是我见过的小说站做的最好难搞的
网页版有前辈分析出来了,在 http://blog.konge.pw/archives/10/
这里就不说了,本文主要进行的是 Android App 的逆向
而且网页版的更新一下让爬虫失效简直太容易了
Android App
App 使用 API 接口 http://app.hbooker.com/ 和服务器通信,但是直接抓包只能抓到这样的东西
很显然是加密的,简单尝试下 Param 的参数也没法解密。
于是尝试解包 Apk 分析加密算法
解包后看是 360 加固过的,真 classes.dex
被封装在 .so 里面,自己又不会 IDA,陷入江局
直到我注意到了
dex 文件在 ART 模式上运行需要转换为 oat 格式,因此不管是什么壳在还原代码时都少不了要将解密后的 dex 文件利用 dex2oat 进行还原
原代码 https://android.googlesource.com/platform/art/+/kitkat-release/dex2oat/dex2oat.cc#924
// art/dex2oat/dex2oat.cc L924
// Ensure opened dex files are writable for dex-to-dex transformations.
for (const auto& dex_file : dex_files) {
if (!dex_file->EnableWrite()) {
PLOG(ERROR) << "Failed to make .dex file writeable '" << dex_file->GetLocation() << "'\n";
}
}
改写成这样
// art/dex2oat/dex2oat.cc L924
// Ensure opened dex files are writable for dex-to-dex transformations.
for (const auto& dex_file : dex_files) {
if (!dex_file->EnableWrite()) {
PLOG(ERROR) << "Failed to make .dex file writeable '" << dex_file->GetLocation() << "'\n";
}
// begin 360 jiagu dump
std::string dex_name = dex_file->GetLocation();
LOG(INFO) << "harvey:dex file name-->" << dex_name;
if(dex_name.find(".jiagu")!=std::string::npos){
int len = dex_file->Size();
char filename[150] = {0};
sprintf(filename,"%s_%d.dex",dex_name.c_str(),len);
int fd = open(filename,O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
if(fd>0){
if(write(fd,(char*)dex_file->Begin(),len)<=0){
LOG(INFO) << "harvey:write target dex file failed-->" << filename;
}
LOG(INFO) << "harvey:write target dex file successfully-->" << filename;
close(fd);
}else{
LOG(INFO) << "harvey:open target dex file failed-->" << filename;
}
}
// end
}
因为 360 存在 .jiagu
目录下,我们可以使用 .jiagu
进行过滤,如果是 360 加固,则将未还原成 oat 的 classes.dex 写到 app 目录的 .jiagu
文件夹内
编译创建虚拟机,安装运行书客 app,成功解包
dex2jar
有了 dex 就方便了,老生常谈使用 dex2jar 还原成 jar
jd-gui
使用 jd-gui 反编译 jar 为 java 代码
有很多无关代码,我们只关心 com.kuangxiang.novel
搜索 javax.crypto
发现在 utils/ParseKsy.class
中进行了 AES 加密
观察相关调用方法
得知先实例化 ParseKsy
然后调用 decrypt()
方法
ParseKsy
如下
decrypt
如下,使用 Base64 解码,然后 cipher 解密
阅读代码,使用 AES 加密,模式为 CBC,使用 PKCS7 作为填充算法,IV 为长度 16 的 0x00 Byte Array
阅读上面实例化 ParseKsy
的代码,密钥为 sha256(zG2nSeEfSHfvTCHy5LCcqtBbQehKNLXn)
的结果取前 32 位
经过测试,书客本地存储的 txt 小说文件也可通过此方法解密
以下代码在 Windows 10, Go v1.8.1 x86_64 下通过
package main
import (
"fmt"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"crypto/sha256"
)
const (
Encrypt_Key = "zG2nSeEfSHfvTCHy5LCcqtBbQehKNLXn"
)
var (
IV = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
test = "c1SR02T7X+xmq37zfs0U8NAj73eedAs3tnXMQKDNUPlI2vcaNRXpKA3JktMoffp3EYPCsvCjzeCJUynjDISbNP4D5HjaCp6tMrOsBBfQzVI="
)
func SHA256(data []byte) []byte {
ret := sha256.Sum256(data)
return ret[:]
}
func Base64Decode(encoded string) ([]byte, error) {
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
return decoded, err
}
return decoded, nil
}
func LoadKey() []byte {
Key := SHA256([]byte(Encrypt_Key))
return Key[:32]
}
func AESDecrypt(ciphertext []byte) ([]byte, error) {
key := LoadKey()
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// Generally use first 16 bytes cipher text as IV
// in this case they use 16 bytes 0x00
blockModel := cipher.NewCBCDecrypter(block, IV)
plainText := make([]byte, len(ciphertext))
blockModel.CryptBlocks(plainText, ciphertext)
plainText = PKCS7UnPadding(plainText)
return plainText, nil
}
func PKCS7UnPadding(plainText []byte) []byte {
length := len(plainText)
unpadding := int(plainText[length-1])
return plainText[:(length - unpadding)]
}
func main(){
decoded, err := Base64Decode(test)
if err != nil {
panic(err)
}
raw, err := AESDecrypt(decoded)
if err != nil {
panic(err)
}
fmt.Println("raw byte", raw)
fmt.Println("string()", string(raw))
}
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK