某国产“自主”操作系统专属 Electron 应用“移植”
最近在 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
└── copyright
32 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 Text
Down p sub_7B180+31 call _dlopen
Down 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 idautils
import idc
def 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 s
def 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_active
49c020 1f 10 yy_get_hwserial
49c140 4 13 basic_string::erase
49bef0 17 21 /usr/lib/license/libyydevicea.so
49c160 17 36 %s: __pos (which is %zu) > this->size() (which is %zu)
49bf8d 1e 7 unknown
49bff0 17 26 Failed to load symbol "get_mac" : %s.
49c0b0 5 14 yy_get_licensetoken
49bf40 1b 11 yy_get_hddsninfo
49bf95 14 d yy_get_mb_sn
49bfdd 1f b yy_get_mac
49c040 6 2f Failed to load symbol "yy_get_hwserial" : %s.
49c070 11 d yy_get_osver
49c0d0 17 2f Failed to load symbol "get_licensetoken" : %s.
49bec0 1d 10 /etc/lsb-release
49c110 7 28 Failed to load symbol "is_active" : %s.
49c080 15 28 Failed to load symbol "get_osver" : %s.
49bedd 14 e DISTRIB_ID=yy
49bed1 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
然后让汇编中指向 0x49bef0 的地方指向新的字符串。
.text:0000000000080189 lea rdi, yy_new_so ; "/opt/apps/com.xxxx/misc/libyydevi"...
.text:0000000000080190 mov esi, 2 ; mode
.text:0000000000080195 call _dlopen
同理
.text:000000000007FD94 lea rsi, lsb_new ; Keypatch modified this from:
.text:000000000007FD94 ; lea rsi, lsb_release
.text:000000000007FD9B lea rdi, [rbp+var_250]
.text:000000000007FDA2 mov edx, 8
然后 patch 下 libyydevicea.so
.rodata:000000000000651E ; char filename[]
.rodata:000000000000651E filename db '/opt/apps/com.xxxx/misc/os-release',0
然后就能登上了...
Patch 结果
~/d/r/u/v/o/a/c/f/resources > radiff2 -u wcs.node wcs.node.bak
-0x0007fd97:0d bf 1a "\r\xbf\x1a"
+0x0007fd97:25 c1 41 "%\xc1A"
-0x0008018c:e8 ba 1a "\xe8\xba\x1a"
+0x0008018c:60 bd 41 "`\xbdA"
-0x0022bc78:2f 6f 70 74 2f 61 70 70 73 2f 63 6f 6d 2e 71 71 2e 77 65 69 78 69 6e 2f 6d 69 73 63 2f 6c 69 62 75 6f 73 64 65 76 69 63 65 61 2e 73 6f 00 00 00 2f 6f 70 74 2f 61 70 70 73 2f 63 6f 6d 2e 71 71 2e 77 65 69 78 69 6e 2f 6d 69 73 63 2f 6c 73 62 2d 72 65 6c 65 61 73 65 00 "/opt/apps/com.xxxx/misc/libyydevicea.so"
+0x0022bc78:14 00 00 00 00 00 00 00 01 7a 52 00 01 78 10 01 1b 0c 07 08 90 01 00 00 24 00 00 00 1c 00 00 00 e8 b5 e4 ff 50 36 00 00 00 0e 10 46 0e 18 4a 0f 0b 77 08 80 00 3f 1a 3b 2a 33 24 22 00 00 00 00 14 00 00 00 44 00 00 00 10 ec e4 ff a8 00 00 00 00 00 00 00 00 00 00 00 1c "\x14"
~/d/r/u/v/o/a/c/misc > radiff2 -u libyydevicea.so libyydevicea.so.bak
-0x0000651f:6f 70 74 "opt/apps/com.xxxx/misc/os-release"
+0x0000651f:65 74 63 "etc/os-release"
-0x00006523:61 70 70 73 2f 63 6f 6d 2e 71 71 2e 77 "apps/com.xxxx/misc/os-release"
+0x00006523:6f 73 2d 72 65 6c 65 61 73 65 00 6f 70 "os-release"
-0x00006531:69 78 69 6e 2f 6d 69 73 63 2f "ixin/misc/os-release"
+0x00006531:6e 20 6f 73 20 76 65 72 73 69 "n os version file error"
-0x0000653c:73 2d 72 65 "s-release"
+0x0000653c:6e 20 66 69 "n file error"
-0x00006542:61 73 65 00 00 00 "ase"
+0x00006542:20 65 72 72 6f 72 " error"
其他所需文件请前往AUR自行下载。