南强小屋 Design By 杰米

某国产“自主”操作系统专属 Electron 应用“移植”

某国产“自主”操作系统专属 Electron 应用“移植”

2020-12-30_16-53.png

最近在 Linux 相关群组内看到某个国产软件给某个国产系统提供了专版应用,这个专版应用在别的系统上都不可以正常运行,但是有老哥发现这个 xxxx 程序读取了 lsb-release, libyyosdevicea.so 和 os-release,在替换这三个文件成 yy 系统的文件之后,就可以直接登陆了...所以我就进行了分析和学习。

自主规避模式,将某操作系统的名字换成了 yy,而某国产软件的名字换成了 xxxx。请不要无端联想。

分析

首先先解压 deb 包,看到的是一个 Electron 程序的正常架构。

~/d/r/u/v2 > tree.├── control├── control.tar.gz├── data.tar.xz├── debian-binary├── md5sums├── opt│   └── apps│       └── com.xxxx│           ├── entries│           │   ├── applications│           │   │   └── com.xxxx.desktop│           │   ├── doc│           │   │   └── xxxx│           │   │       └── copyright│           │   ├── icons│           │   │   └── hicolor│           │   │       ├── 128x128│           │   │       │   └── apps│           │   │       │       └── xxxx.png│           │   │       ├── 16x16│           │   │       │   └── apps│           │   │       │       └── xxxx.png│           │   │       ├── 256x256│           │   │       │   └── apps│           │   │       │       └── xxxx.png│           │   │       ├── 48x48│           │   │       │   └── apps│           │   │       │       └── xxxx.png│           │   │       └── 64x64│           │   │           └── apps│           │   │               └── xxxx.png│           │   ├── lintian│           │   │   └── overrides│           │   │       └── xxxx│           │   └── pixmaps│           │       └── xxxx.png│           ├── files│           │   ├── blink_image_resources_200_percent.pak│           │   ├── content_resources_200_percent.pak│           │   ├── content_shell.pak│           │   ├── icudtl.dat│           │   ├── libffmpeg.so│           │   ├── libnode.so│           │   ├── LICENSES.chromium.html│           │   ├── locales│           │   │   ├── zh-CN.pak│           │   │   └── zh-TW.pak│           │   ├── natives_blob.bin│           │   ├── pdf_viewer_resources.pak│           │   ├── resources│           │   │   ├── app.asar│           │   │   ├── electron.asar│           │   │   ├── sae.dat│           │   │   └── wcs.node│           │   ├── snapshot_blob.bin│           │   ├── ui_resources_200_percent.pak│           │   ├── version│           │   ├── views_resources_200_percent.pak│           │   └── xxxx│           └── info├── postinst├── postrm├── sign└── usr    ├── lib    │   └── license    │       └── libyyosdevicea.so    └── share        └── doc            └── com.xxxx                ├── changelog.Debian.gz                └── copyright32 directories, 106 files

这里我们直入正题,看下 opt/apps/com.xxxx/files/ 的 xxxx 文件,发现这个文件有点大,IDA 分析不动...所以就先照着别的老哥说的,看下 opt/apps/com.xxxx/files/resources 下的 app.asar。这个文件直接用 asar e app.asar $解压到的目录 就解压开了。比较关键的是下面的函数,应该是通过 node.js 的 module 进行生成 token,然后将这个 token 塞入 http 请求的头部,然后服务器进行特殊校验就可以判断是否允许通过了。

function getToken() {    let pathname = path.dirname(__dirname);        let wcs = require(path.join(pathname, './wcs.node'));        let kp = path.join(pathname, 'sae.dat');        let buf = wcs.get_cc_data(1, kp);        return buf.toString('ascii');}function bindToken(session) {        session.webRequest.onBeforeSendHeaders(                { urls: urlFilters },                (details, callback) => {                        if (details.url.indexOf('/cgi-bin/mmwebxxxx-bin/webxxxxnewloginpage') > -1) {                                details.requestHeaders['extspam'] = getToken();                                details.requestHeaders['client-version'] = app.getVersion();                        }                }        );}

那么现在就去分析下 wcs.node 了。看了下 wcs.node 的 strings 结果,发现没有什么关键的字符串,并且在 lld 的结果中没有发现 libyyosdevicea.so,猜测用了 dlopen 进行动态加载,所以查了下 dlopen 的交叉引用

Direction        Type        Address        TextDown        p        sub_7B180+31        call    _dlopenDown        p        sub_800D0+C5        call    _dlopen

然后再看了下 sub_800D0 的引用,发现字符串似乎被加密了

.text:0000000000080189                 lea     rdi, weird_str.text:0000000000080190                 mov     esi, 2          ; mode.text:0000000000080195                 call    _dlopen

查了下 weird_str 的交叉引用,看来就是 xor 了下字符串

.text:00000000000845EC                 mov     [rsp+var_8], 0.text:00000000000845F5                 lea     rax, weird_str.text:00000000000845FC                 nop     dword ptr [rax+00h].text:0000000000084600.text:0000000000084600 loc_84600:                              ; CODE XREF: sub_844D0+146↓j.text:0000000000084600                 mov     rcx, [rsp+var_8].text:0000000000084605                 xor     byte ptr [rcx+rax], 17h.text:0000000000084609                 add     rcx, 1.text:000000000008460D                 mov     [rsp+var_8], rcx.text:0000000000084612                 cmp     rcx, 21h.text:0000000000084616                 jb      loc_84600

写个 IDAPython 脚本解密下

import idautilsimport idcdef get_str(addr, str_len):    s = ''    for i in range(str_len):        temp = idaapi.get_byte(addr + i)        if temp == 0:            break        s += chr(temp)    return sdef xor_again(func):    op, xor_num, str_len = None, None, None    for curr_addr in idautils.FuncItems(func):        mnem = idc.print_insn_mnem(curr_addr)        if mnem == 'lea':            op = idc.get_operand_value(curr_addr, 1)        if mnem == 'xor':            xor_num = idc.get_operand_value(curr_addr, 1)        if mnem == 'cmp':            str_len = idc.get_operand_value(curr_addr, 1)            enc_s = get_str(op, str_len)            dec_s = ''            for i in enc_s:                dec_s += chr(ord(i) ^ xor_num)            print("%x %x %x %s" % (op, xor_num, str_len, dec_s))xor_again(0x844D0)

输出如下

49bf60 1 2c Failed to load symbol "get_hddsninfo" : %s.49bf20 11 13 Failed to load %s.49bfb0 19 2c Failed to load symbol "yy_get_mb_sn" : %s.49c100 14 d yy_is_active49c020 1f 10 yy_get_hwserial49c140 4 13 basic_string::erase49bef0 17 21 /usr/lib/license/libyydevicea.so49c160 17 36 %s: __pos (which is %zu) > this->size() (which is %zu)49bf8d 1e 7 unknown49bff0 17 26 Failed to load symbol "get_mac" : %s.49c0b0 5 14 yy_get_licensetoken49bf40 1b 11 yy_get_hddsninfo49bf95 14 d yy_get_mb_sn49bfdd 1f b yy_get_mac49c040 6 2f Failed to load symbol "yy_get_hwserial" : %s.49c070 11 d yy_get_osver49c0d0 17 2f Failed to load symbol "get_licensetoken" : %s.49bec0 1d 10 /etc/lsb-release49c110 7 28 Failed to load symbol "is_active" : %s.49c080 15 28 Failed to load symbol "get_osver" : %s.49bedd 14 e DISTRIB_ID=yy49bed1 2 b DISTRIB_ID=

移植

那目的很明确了,将汇编中指向 0x49bef0(/usr/lib/license/libyydevicea.so) 和 0x49bec0(/etc/lsb-release) 的地方,指向我们自定义的字符串。

借助 Keypatch 就好。

我们先在 .eh_frame 上加上自定义字符串。

.eh_frame:000000000022BC78 yy_new_so      db '/opt/apps/com.xxxx/misc/libyydevicea.so',0.eh_frame:000000000022BC78                                         ; DATA XREF: sub_800D0:loc_80189↑o.eh_frame:000000000022BCA6                 db    0.eh_frame:000000000022BCA7                 db    0.eh_frame:000000000022BCA8 lsb_new         db '/opt/apps/com.xxxx/misc/lsb-release',0