首页
社区
课程
招聘
[原创]如何用纯猜的方式逆向喜马拉雅xm文件加密(wasm部分)
发表于: 2023-8-29 17:05 10473

[原创]如何用纯猜的方式逆向喜马拉雅xm文件加密(wasm部分)

2023-8-29 17:05
10473

原文链接 here

chatgpt翻译.jpg

在我之前的文章中,我留下了一个关于 wasm 内部解密方法的疑问。今天,让我们再次深入逆向工程的世界,揭开代码下的神秘面纱。

现在,你们中的一些人可能会想知道为什么这篇文章的标题是纯粹的猜测。好吧,那是因为我在处理这个*'逆向'*挑战时,实际上并没有进行任何真正的逆向工程。

如果存在一个解密算法,那么必然存在一个相应的加密机制。在这种情况下,webassembly 中导出的函数 h 就是我想要的加密方法。

与其解密对应部分相似,它需要两个参数:加密的数据和 trackId。

让我们探索这种加密是如何工作的。

首先,我们需要一个脚本来测试不同的参数如何影响加密结果:

从结果中,我们可以观察到,当 track_id 达到0x18字节的长度时,结果保持不变。这意味着超过0x18的 track_id 的长度不影响结果。

0x18 是一个耐人寻味的值,正好是 192 位。这让我立刻联想到 AES 192 加密。事实上,如果你在谷歌上搜索 "192 位加密",第一个结果通常指向 AES。

但我们如何确定这确实是 AES 加密呢?虽然我们可以手动验证,但还需要确定加密模式及其 IV(初始化向量)。

我首先尝试了 CBC 模式,这主要是因为它很常用。此外,由于 CBC 模式的性质(详见 维基百科),使用初始 16 字节作为 IV 来验证它也很简单。

图片描述

图片描述

嗯,它确实是CBC模式(真酷)。现在,任务是确定IV。

由于加密函数没有明确要求使用 IV,因此有两种可能性。IV 可以根据 trackId 生成(因为数据最终会被加密),也可以随机生成,然后附加到返回值中。

后者很快就会被排除,因为返回值的长度似乎容不下附加的 IV。这就指向了前者--IV 可能来自于 trackId。但如何派生呢?

为了解决这个问题,我从最简单的假设入手:将 trackId 的前 16 个字节用作 IV。

图片描述

笑嘻了,还真是

但随后又出现了另一个挑战。xm_encryptor "也可以处理长度小于24字节的 "trackId"。由于 AES-192 无法处理长度小于 24 字节的密钥,我断言该算法必须以某种方式在 trackId 中添加一些额外的字符。

我们现在的任务是确定填充字符及其填充方法。由于加密需要支持可变的 "trackId "长度,而且填充是根据我们提供的 "trackId "进行的,所以最直接的解决方案就是填充一些常量字符。

最简单的填充方法也有两种,一种是在 trackId 后面填充,另一种是在前面填充。

现在是时候进行一些简单但有效的方法 - 暴力破解。一个字节接一个字节。我们只需要运行256*24=6144次迭代。甚至不到10k。

从结果中我们可以清晰地看出填充 (前面填充):

为了验证填充方法的准确性,我还可以采用内存转储技术。虽然调试也可以,但我懒得调试 WebAssembly。

为此,我在 encrypt 函数中添加了几行:

By examining the output, we can clearly identify the padding:

The presence of sequences like 123456781AAAAAAAAAAAAAAA in the dumped data suggests that our assumption regarding the padding is indeed accurate.

通过所有的拼图部分,很明显 wasm 加密遵循以下步骤:

function f_h(a:{ a:byte, b:byte }, b:int, c:long_ptr, d:int, e:int) {
  var m:int;
  ...
function f_h(a:{ a:byte, b:byte }, b:int, c:long_ptr, d:int, e:int) {
  var m:int;
  ...
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from wasmer import engine,Store, Module, Instance,Memory,Uint8Array,Int32Array
import io,sys,pathlib
import re,base64
 
 
xm_encryptor = Instance(Module(
    Store(),
    pathlib.Path("./xm_encryptor.wasm").read_bytes()
))
 
def encrypt(data, key):
    stack_pointer = xm_encryptor.exports.a(-16)
    assert isinstance(stack_pointer, int)
    de_data_offset = xm_encryptor.exports.c(len(data))
    assert isinstance(de_data_offset,int)
    track_id_offset = xm_encryptor.exports.c(len(key))
    assert isinstance(track_id_offset, int)
    memory_i = xm_encryptor.exports.i
    memview_unit8:Uint8Array = memory_i.uint8_view(offset=de_data_offset)
    for i,b in enumerate(data):
        memview_unit8[i] = b
    memview_unit8: Uint8Array = memory_i.uint8_view(offset=track_id_offset)
    for i,b in enumerate(key):
        memview_unit8[i] = b
    xm_encryptor.exports.h(stack_pointer,de_data_offset,len(data),track_id_offset,len(key))
    memview_int32: Int32Array = memory_i.int32_view(offset=stack_pointer // 4)
    result_pointer = memview_int32[0]
    result_length = memview_int32[1]
    assert memview_int32[2] == 0, memview_int32[3] == 0
    result_data = bytearray(memory_i.buffer)[result_pointer:result_pointer+result_length].decode()
    return result_data
 
for i in range(0x20):
    data = b'A'*0x10+b"CCCCCCCC"+b"DDDDDDDD"
    # track_id = b'E'*0x8+b'F'*0x8+b'G'*0x7 # max 24
    track_id = b'\x41' * i
    # track_id = b'E'*0x8
    print(hex(i),encrypt(data,track_id))
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from wasmer import engine,Store, Module, Instance,Memory,Uint8Array,Int32Array
import io,sys,pathlib
import re,base64
 
 
xm_encryptor = Instance(Module(
    Store(),
    pathlib.Path("./xm_encryptor.wasm").read_bytes()
))
 
def encrypt(data, key):
    stack_pointer = xm_encryptor.exports.a(-16)
    assert isinstance(stack_pointer, int)
    de_data_offset = xm_encryptor.exports.c(len(data))
    assert isinstance(de_data_offset,int)
    track_id_offset = xm_encryptor.exports.c(len(key))
    assert isinstance(track_id_offset, int)
    memory_i = xm_encryptor.exports.i
    memview_unit8:Uint8Array = memory_i.uint8_view(offset=de_data_offset)
    for i,b in enumerate(data):
        memview_unit8[i] = b
    memview_unit8: Uint8Array = memory_i.uint8_view(offset=track_id_offset)
    for i,b in enumerate(key):
        memview_unit8[i] = b
    xm_encryptor.exports.h(stack_pointer,de_data_offset,len(data),track_id_offset,len(key))
    memview_int32: Int32Array = memory_i.int32_view(offset=stack_pointer // 4)
    result_pointer = memview_int32[0]
    result_length = memview_int32[1]
    assert memview_int32[2] == 0, memview_int32[3] == 0
    result_data = bytearray(memory_i.buffer)[result_pointer:result_pointer+result_length].decode()
    return result_data
 
for i in range(0x20):
    data = b'A'*0x10+b"CCCCCCCC"+b"DDDDDDDD"
    # track_id = b'E'*0x8+b'F'*0x8+b'G'*0x7 # max 24
    track_id = b'\x41' * i
    # track_id = b'E'*0x8
    print(hex(i),encrypt(data,track_id))
0x16 NrVlG9gtu3MmpUlXK8gIxHD0Kh07iORGc6Dz5tLaLSUBSffF0/FU1vB8OmX921rP
0x17 2HiMLe5mRt4yHMs3WUtr7L0Zt6MG/lLaeK/0rSiTeUwlTEYF2e/Y7w+S3v75Kw65
0x18 DzljUl9dgiE6eex/8OeN/DXnw0roUS9t00zDBXylzFphQ4/yMCxxOJuOPxifYEVS
0x19 DzljUl9dgiE6eex/8OeN/DXnw0roUS9t00zDBXylzFphQ4/yMCxxOJuOPxifYEVS
0x1a DzljUl9dgiE6eex/8OeN/DXnw0roUS9t00zDBXylzFphQ4/yMCxxOJuOPxifYEVS
0x16 NrVlG9gtu3MmpUlXK8gIxHD0Kh07iORGc6Dz5tLaLSUBSffF0/FU1vB8OmX921rP
0x17 2HiMLe5mRt4yHMs3WUtr7L0Zt6MG/lLaeK/0rSiTeUwlTEYF2e/Y7w+S3v75Kw65

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 2
支持
分享
最新回复 (5)
雪    币: 5491
活跃值: (390193)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2023-8-29 17:21
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2023-9-26 09:20
0
雪    币: 3573
活跃值: (31026)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2023-9-26 09:25
1
雪    币: 261
活跃值: (547)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
别逆向喜马了 我在喜马直播间做大哥
2023-9-26 18:00
0
雪    币: 204
活跃值: (54)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
膜拜大佬.
4天前
0
游客
登录 | 注册 方可回帖
返回
//