社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  docker

某知笔记服务端docker镜像授权分析

看雪学院 • 3 年前 • 368 次点击  
本文为看雪论坛优秀文章
看雪论坛作者ID:HOWMP


1


启动docker镜像


根据其官网 https://www.wiz.cn/zh-cn/docker 说明,使用如下命令即可启动。
docker pull wiznote/wizserver:latestdocker run --name wiz --restart=always -it -d \   -v `pwd`/wizdata:/wiz/storage \   -v /etc/localtime:/etc/localtime \


    
   -p 80:80 -p 9269:9269/udp  wiznote/wizserver



2


初探授权


启动好之后,默认授权限制只能有5个用户。

进入docker容器,查看基本信息。

执行 docker exec -it wiz bash 进入容器,可以看到:

后端主要使用node实现并用pm2管理,nginx反代,数据库mysqlnode版本v8.11.2,v8版本6.2.414.54。

# ps -aefUID        PID  CMDroot         1  bash /wiz/app/entrypoint.shroot        33  /usr/bin/redis-server 127.0.0.1:6379mysql       53  /usr/sbin/mysqld


    
root        59  nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.confnginx       60  nginx: worker processnginx       61  nginx: worker processroot       119  PM2 v4.5.0: God Daemon (/root/.pm2)root       129  node /root/.pm2/modules/pm2-logrotate/node_modules/pm2-logrotate/app.jsroot       137  node /wiz/app/wizserver/app.jsroot       157  node /wiz/app/wizserver/app.jsroot       173  node /wiz/app/wizserver/app.jsroot       189  node /wiz/app/wizserver/app.jsroot       205  node /wiz/app/wizserver/app.jsroot       221  node /wiz/app/wizserver/app.jsroot       240  node /wiz/app/wizserver/app.jsroot       246  crond -nroot       256  bashroot       270  ps -aef# node -v


    
v8.11.2# node -p process.versions.v86.2.414.54# pm2 list┌─────┬──────────────────┬ id  │ name             │├─────┼──────────────────┼ 1   │ as               │ 6   │ cloud            │ 4   │ index            │ 2   │ note             │ 7   │ office           │ 5   │ search           │ 3   │ ws               │└─────┴──────────────────┴


分析代码发现后缀为jsc的文件:


# ll /wiz/app/wizserver/common/


    
total 536-rw-r--r-- 1 root root  8792 Dec 23  2020 alicloud_tools.jsc-rw-r--r-- 1 root root  9984 Dec 23  2020 client_tools.jsc-rw-r--r-- 1 root root  7024 Dec 23  2020 cookie_tools.jsc-rw-r--r-- 1 root root  2352 Dec 23  2020 date_tools.jsc-rw-r--r-- 1 root root 27528 Dec 23  2020 db_tools.jsc......

进一步分析发现,这是通过bytenodehttps://github.com/bytenode/bytenode工具编译成了v8的字节码。

在各种搜索之后发现,目前还没有相关的反汇编、反编译工具。唯一相关项目https://github.com/PositiveTechnologies/ghidra_nodejs是GHIDRA的插件,但node版本和我们不匹配。



3


反汇编v8字节码,改造d8


通过v8源码分析发现,v8本身是有反汇编功能,node可通过--print-bytecode参数开启。
 
但只能反汇编源码,无法反汇编jsc文件。为了反汇编字节码只能通过修改v8来实现。
 

d8 https://v8.dev/docs/d8 是v8的开发工具,为了简单起见决定对d8进行修改。


反汇编jsc思路

  • jsc实际是由v8::internal::CodeSerializer::Serialize方法生成
  • 反汇编需要调用v8::internal::BytecodeArray::Disassemble方法生成

反序列:

v8:internal::CodeSerializer::Deserialize原型:

MUST_USE_RESULT static MaybeHandle Deserialize(    Isolate* isolate, ScriptData* cached_data, Handle<String> source);

其中参数cached_data,可通过ScriptData的构造函数ScriptData(const byte* data, int length);构造。
 
其中返回值SharedFunctionInfo对象有bytecode_array方法,可以获得BytecodeArray来进行反汇编。

BytecodeArray* SharedFunctionInfo::bytecode_array() const {  DCHECK(HasBytecodeArray());  return BytecodeArray::cast(function_data());}

于是思路自然而然就有了:
  • 读取jsc,构造ScriptData对象
  • 反序列化,获取SharedFunctionInfo对象
  • 反汇编,通过bytecode_array获取BytecodeArray,并调用Disassemble反汇编


搭建v8编译环境

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.gitexport PATH=`pwd`/depot_tools:"$PATH"export HTTPS_PROXY=http://172.31.0.1:18080  # 设置代理gclient sync                                # 更新工具链fetch v8                                    # 获取v8代码cd v8git checkout 6.2.414.46                     # 切换至需要的分支gclient sync                                # 根据分支再次更新工具链tools/dev/v8gen.py x64.release -- \  v8_enable_disassembler=true \  v8_enable_object_print=true               # 配置编译选项ninja -C out.gn/x64.release d8              # 编译d8

v8_enable_disassembler和v8_enable_object_print一定要开启,否则反汇编时不显示常量内容。


修改d8代码(d8.cc)


在Shell类增加LoadJSC方法:

static void Disassemble(v8::internal::BytecodeArray* bytecode) {  internal::OFStream os(stdout);  bytecode->Disassemble(os);  auto consts = bytecode->constant_pool();


    
  for (int i = 0; i < consts->length(); i++) {    auto obj = consts->get(i);    if (obj->IsSharedFunctionInfo()) {      auto shared = v8::internal::SharedFunctionInfo::cast(obj);      os << "Function name " << shared->name()->ToCString().get() << "\n";      Disassemble(shared->bytecode_array());    }  }}void Shell::LoadJSC(const v8::FunctionCallbackInfo<:value>& args) {  auto isolate = reinterpret_cast<:isolate>(args.GetIsolate());  for (int i = 0; i < args.Length(); i++) {    String::Utf8Value filename(args.GetIsolate(), args[i]);    if (*filename == NULL) {      Throw(args.GetIsolate(), "Error loading file");      return;


    
    }    int length = 0;    auto filedata = reinterpret_cast<uint8_t*>(ReadChars(*filename, &length));    if (filedata == NULL) {      Throw(args.GetIsolate(), "Error reading file");      return;    }    auto scriptdata = new i::ScriptData(filedata, length);    auto source = isolate->factory()                      ->NewStringFromUtf8(i::CStrVector("source"))                      .ToHandleChecked();    auto fun = i::CodeSerializer::Deserialize(isolate, scriptdata, source)                   .ToHandleChecked();    Disassemble(fun->bytecode_array());  }}

注册为全局函数,在Shell::CreateGlobalTemplate中添加代码:

global_template->Set(


    
    String::NewFromUtf8(isolate, "loadjsc", NewStringType::kNormal)        .ToLocalChecked(),    FunctionTemplate::New(isolate, LoadJSC));

由于目标v8版本为6.2.414.54,但v8的git仓库中并没有这个版本。所以用了最接近的版本6.2.414.46,但反序列时一些校验无法通过。

  • SerializedCodeData::SanityCheck

  • Deserializer::Initialize

需要修改以上两个方法,代码过于丑陋就不贴了。
 
修改后使用ninja -C out.gn/x64.release d8命令重新编译即可。


使用loadjsc进行反汇编


使用命令v8/out.gn/x64.release/d8 -e "loadjsc('xxx.jsc')" 即可反汇编jsc文件。
 
例如源码test.js:

function xxx(){    console.log("asdasd")}


    
xxx()

其通过wiz的docker里bytenode编译为test.jsc命令/wiz/app/wizserver/node_modules/.bin/bytenode -c test.js再通过d8反汇编,结果如下:

 
命令 ./d8 -e "loadjsc('test.jsc')"

Parameter count 1Frame size 8    0 E> 0x17c42dd2c522 @    0 : 91                StackCheck    0 S> 0x17c42dd2c523 @    1 : 6e 00 00 00       CreateClosure [0], [0], #0         0x17c42dd2c527 @    5 : 1e fb             Star r0  115 S> 0x17c42dd2c529 @    7 : 95                ReturnConstant pool (size = 1)0x17c42dd2c531: [FixedArray] in OldSpace - map = 0x1b27f0f82309  - length: 1           0: 0x17c42dd2c549 Handler Table (size = 16)


    
Function nameParameter count 6Frame size 8         0x17c42dd2c742 @    0 : 6e 00 00 02       CreateClosure [0], [0], #2         0x17c42dd2c746 @    4 : 1e fb             Star r0   10 E> 0x17c42dd2c748 @    6 : 91                StackCheck  106 S> 0x17c42dd2c749 @    7 : 4f fb 01          CallUndefinedReceiver0 r0, [1]         0x17c42dd2c74c @   10 : 04                LdaUndefined  113 S> 0x17c42dd2c74d @   11 : 95                ReturnConstant pool (size = 1)0x17c42dd2c751: [FixedArray] in OldSpace - map = 0x1b27f0f82309  - length: 1           0: 0x17c42dd2c769 Handler Table (size = 16)


    
Function name xxxParameter count 1Frame size 24   74 E> 0x17c42dd2c862 @    0 : 91                StackCheck   82 S> 0x17c42dd2c863 @    1 : 0a 00 02          LdaGlobal [0], [2]         0x17c42dd2c866 @    4 : 1e fa             Star r1   90 E> 0x17c42dd2c868 @    6 : 20 fa 01 04       LdaNamedProperty r1, [1], [4]         0x17c42dd2c86c @   10 : 1e fb             Star r0         0x17c42dd2c86e @   12 : 09 02             LdaConstant [2]         0x17c42dd2c870 @   14 : 1e f9             Star r2   90 E> 0x17c42dd2c872 @   16 : 4c fb fa f9 00    CallProperty1 r0, r1, r2, [0]         0x17c42dd2c877 @   21 : 04                LdaUndefined  104 S> 0x17c42dd2c878 @   22 : 95                ReturnConstant pool (size = 3)0x17c42dd2c881: [FixedArray] in OldSpace


    
 - map = 0x1b27f0f82309  - length: 3           0: 0x2e3b60d34a19            1: 0x2e3b60d08e41            2: 0x17c42dd2c8e9 Handler Table (size = 16)



4


分析授权逻辑


前台获取授权信息的api接口


返回数据:

{


    
    "returnCode": 200,    "returnMessage": "OK",    "externCode": "",    "result": {        "start": 1571192332036,        "end": 4733432332036,        "count": 5,        "oem": "wiz",        "type": "license_free",        "version": 1,        "key": "4bc0cc40-efbb-11e9-bef0-0faf68675f7c",        "ext": {}    }}


定位接口对应的文件


根据上述url,http://127.0.0.1/as/admin/licence?clientType=web&clientVersion=4.0&lang=zh-cn
 
结合后端的目录结构,首先对admin_router.jsc文件进行反汇编分析:

# pwd/wiz/app/wizserver/as/admin# lltotal 128-rw-r--r-- 1 root root  7408 Dec 23  2020 admin_file_utils.jsc-rw-r--r-- 1 root root 80720 Dec 23  2020 admin_router.jsc-rw-r--r-- 1 root root  1680 Dec 23  2020 admin_static_files.jsc-rw-r--r-- 1 root root  3384 Dec 23  2020 ldap_settings.jscdrwxr-xr-x 4 root root  4096 Dec 23  2020 meta_files-rw-r--r-- 1 root root  3304 Dec 23  2020 middleware.jsc-rw-r--r-- 1 root root 15240 Dec 23  2020 oem_utils.jsc-rw-r--r-- 1 root root  6304 Dec 23  2020 wizbox_search.jsc

admin_router.jsc:

粗略分析,发现admin_router调用了addLicence和getLicence两个方法,而同时包含这两个方法的文件是wiz_name_utils.jsc。


wiz_name_utils.jsc

发现wiz_name_utils.jsc应该是调用了node-rsa库,通过RSA算法来计算相关授权。

node-rsa


为了验证猜测,通过修改/wiz/app/wizserver/node_modules/node-rsa/src/NodeRSA.js的构造函数,来打印一下公钥。

pm2 restart as  #pm2重启as服务,并访问网页授权页面tail -f /root/.pm2/logs/as-out.log #查看as日志

为了进一步验证,我们修改解密函数,打印返回值。

再次重启as服务,并访问网页授权页面。

2021-06-29 18:15 +08:00: {"start":1571192332036,"end":4733432332036,"count":5,"oem":"wiz","type":"license_free","version":1,"key":"4bc0cc40-efbb-11e9-bef0-0faf68675f7c","ext":{}}2021-06-29 18:15 +08:00: e58678339769ba7a9139202655ab345f

解密后的数据与网页接口返回的数据一致,证明猜测位置正确。


5


解除授权封印


通过上面的分析,思路就很清楚了。我们只要在特定的时候修改NodeRSA.prototype.decryptPublic的返回值可以控制授权。




    
NodeRSA.prototype.decryptPublic = function (buffer, encoding) {    var data = this.$$decryptKey(true, buffer, encoding);    try{        var v = JSON.parse(data);        if(v.count == 5){            v.count = 99999;            v.type = 'license_vip';            data = Buffer.from(JSON.stringify(v));        }    }catch(e){    }    return data;};

最终结果:


 


看雪ID:HOWMP

https://bbs.pediy.com/user-home-353809.htm

  *本文由看雪论坛 HOWMP 原创,转载请注明来自看雪社区





# 往期推荐

1. angr学习(三)一道自己模仿着出的简单题和angr-ctf符号化输入相关题目

2. 记一次Word宏Downloader样本分析

3. XX之NTDLL随机化“逆向”(XP系统)

4. Frida分析违法应用Native层算法

5. 关于一次在pwnable.kr中input题目的经历(python3)

6. Pwn堆利用学习——Unsortedbin Attack——HITCON_Training_lab14_magicheap



公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



球分享

球点赞

球在看



点击“阅读原文”,了解更多!

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/117446
 
368 次点击