百度网盘MAC客户端netdisk_service服务浅析小记


前言

前几日出了百度云Windows客户端YunDetectService.exe服务的10000端口的命令注入致RCE漏洞后,今日得闲,看了下MAC客户端是否存在同样的服务,发现也存在同样的服务,就粗略的看了下,献丑了。

正文

在没有打开百度网盘之前,通过lsof -i:10000 命令没有发现任何服务,打开百度网盘后,发现了存在监听10000端口的netdisk_service服务,因此粗略的审计一番。

然后通过系统自带的获得监视器找到了netdisk_service服务

使用ghidra打开/Applications/BaiduNetdisk.app/Contents/Frameworks/netdisk_service文件发现了如下几个API相关的初始化

  • METHOD_GET_VERSION 为 "GetVersion"
  • METHOD_GET_PCCODE 为 "GetPCCode"
  • METHOD_DOWNLOAD_SHARE 为 "DownloadShareItems"
  • METHOD_DOWNLOAD_SELFOWN 为 "DownloadSelfOwnItems"
  • METHOD_OPEN_SAFEBOX 为 "OpenSafebox"
  • METHOD_WEB_UPLOAD 为 "WebUpload"
  • METHOD_HTTPS_DOWNLOAD 为 "/downloadpc" (这看起来像一个URL路径)
  • METHOD_HTTPS_WEB_GET_LOGIN_TOKEN 为 "WebGetLoginToken"

在本地使用curl测试获取登录token

curl 'http://127.0.0.1:10000?method=WebGetLoginToken'

可以成功获取到当前已经登录的百度云账户用户名(展示名displayname)、头像连接(portrait)以及最重要的authToken

拿到authToken就可以直接登录百度云盘等涉及百度账户的验证环境。

同样想看下OpenSafebox是否也存在类似Windows客户端的命令注入漏洞,

因此可利用交叉引用(XREF)从数据(方法名)找到使用它的代码(分发函数),再从分发函数找到真正的处理函数。

如上图所示的XREF有很多,其中一个引用来自get_request_response函数,双击即可跟进函数的具体实现处理方式等,或者从ghidra的反编译窗口里找if语句调用的process_open_safebox_request函数,同样可双击跟进函数内的具体实现方式。

双击跟进后就来到了OpenSafebox请求的分发点

process_open_safebox_request(HttpsRequestProcessor *this, int *param_1, string *param_2)

  • this: 指向 HttpsRequestProcessor 对象的指针,包含了请求的所有上下文信息。
  • int *param_1: 输出参数,用于设置返回的 HTTP 状态码 (例如 200, 400)。
  • string *param_2: 输出参数,用于设置返回给客户端的响应体内容。

然后根据不同的结果响应不同的状态码以及内容

请求参数解读如下

local_40 = (string)0x4; // 字符串长度 4
local_3f = 0x6b75;     // ASCII "uk"
local_3d = 0;           // 字符串结束符

pHVar2 = (HttpsRequestProcessor *)std::__tree<>::find<>((__tree<> *)(this + 0x80),&local_40);
  • 这几行代码首先在栈上构造了一个 std::string 对象,内容是 "uk"。
  • 然后,它调用 std::__tree<>::find<> 在一个树形结构(通常是 std::map 的底层实现)中查找这个 "uk" 键。
  • this + 0x80 指向的正是存储了所有请求参数的 map。

下面属于对uk参数的校验

if (pHVar2 == this + 0x88) {
  _internal_log(0x40,"HttpsRequestProcessor::process_open_safebox_request uk not found");
  uVar3 = 1;
}
  • this + 0x88 是参数 map 的 end() 迭代器。如果 find 的结果等于 end(),说明没有找到。
  • 如果找不到名为 "uk" 的参数,程序会记录一条日志 "uk not found" 并返回错误。

关键处理如下

else {
  // 从map中提取"uk"参数的值
  std::string::string(&local_40,(string *)(pHVar2 + 0x38));

  // 将"uk"的值传递给真正的处理函数
  iVar1 = open_safebox(&local_40); 

  // 根据open_safebox的返回值设置HTTP响应
  if (iVar1 == 0) { // 成功
    *param_1 = 200;
  }
  else { // 失败
    *param_1 = 400;
  }
  // ...
  uVar3 = 0;
}

然后我们跟进open_safebox,看下它的实现

完整的反汇编如下


/* baidu::netdisk::service::open_safebox(std::string const&) */

undefined4 baidu::netdisk::service::open_safebox(string *param_1)

{
  int iVar1;
  undefined4 uVar2;
  void *pvVar3;
  void *pvVar4;
  pair<> *this;
  pair<> *ppVar5;
  ulong local_88;
  undefined8 uStack_80;
  void *local_78;
  void *local_70;
  locale local_60 [8];
  locale local_58 [8];
  util local_50;
  undefined4 local_4f;
  undefined1 local_4b;
  long local_48;
  void *local_40;
  undefined1 local_38;
  undefined4 *local_30;

  local_88 = 0;
  uStack_80 = 0;
  local_78 = (void *)0x0;
  pvVar3 = operator.new(0x20);
  pvVar4 = operator.new(0x60);
  *(void **)((long)pvVar3 + 8) = pvVar4;
  *(undefined8 *)((long)pvVar4 + 0x38) = 0;
  *(long *)((long)pvVar4 + 0x40) = (long)pvVar4 + 0x38;
  *(long *)((long)pvVar4 + 0x48) = (long)pvVar4 + 0x38;
  *(long *)((long)pvVar4 + 0x58) = (long)pvVar4 + 0x50;
  *(long *)((long)pvVar4 + 0x50) = (long)pvVar4 + 0x50;
  *(undefined8 *)((long)pvVar3 + 0x18) = 0;
  local_50 = (util)0x4;
  local_4f = CONCAT13(local_4f._3_1_,0x6b75);
  local_38 = 0x2e;
  local_70 = pvVar3;
  local_30 = &local_4f;
  boost::property_tree::basic_ptree<>::put<>(&local_88,&local_50,param_1);
  if (((byte)local_50 & 1) != 0) {
    operator.delete(local_40);
  }
  local_50 = (util)0x8;
  local_4f = 0x65707974;
  local_4b = 0;
  local_38 = 0x2e;
  local_30 = &local_4f;
  std::locale::locale(local_58);
  std::locale::locale(local_60,local_58);
  boost::property_tree::basic_ptree<>::put<>
            ((basic_ptree<> *)&local_88,&local_50,"open_safebox",local_60);
  std::locale::~locale(local_60);
  std::locale::~locale(local_58);
  if (((byte)local_50 & 1) != 0) {
    operator.delete(local_40);
  }
  base::util::convert_ptree_to_string(&local_50,(basic_ptree *)&local_88);
  if (((byte)local_50 & 1) == 0) {
    if ((byte)local_50 >> 1 != 0) goto LAB_10007560c;
  }
  else if (local_48 != 0) {
LAB_10007560c:
    iVar1 = send_message((string *)&local_50);
    if (iVar1 == 0) {
      uVar2 = 0;
    }
    else {
      uVar2 = start_netdisk_process((string *)&local_50);
    }
    goto joined_r0x000100075660;
  }
  uVar2 = 1;
  _internal_log(0x40,"ASSERT FAIL @ %s(%d)",
                "/Users/ferry5/ONLINE_SERVICE/other/ferry/task_workspace/2b7eb7a409efeaf01018ee5eb06 5b8ce/baidu/netdisk/pc-browserengine/source/startupservice/httpsservice/https_util.c pp"
                ,0xd5);
joined_r0x000100075660:
  if (((byte)local_50 & 1) != 0) {
    operator.delete(local_40);
  }
  pvVar3 = local_70;
  ppVar5 = *(pair<> **)((long)local_70 + 8);
  this = (pair<> *)(*(long *)(ppVar5 + 0x58) + -0x50);
  if (*(long *)(ppVar5 + 0x58) == 0) {
    this = (pair<> *)0x0;
  }
  if (this != ppVar5) {
    do {
      ppVar5 = (pair<> *)(*(long *)(this + 0x58) + -0x50);
      if (*(long *)(this + 0x58) == 0) {
        ppVar5 = (pair<> *)0x0;
      }
      std::pair<>::~pair(this);
      operator.delete(this);
      this = ppVar5;
    } while (ppVar5 != *(pair<> **)((long)pvVar3 + 8));
  }
  operator.delete(ppVar5);
  operator.delete(pvVar3);
  if ((local_88 & 1) != 0) {
    operator.delete(local_78);
  }
  return uVar2;
}

可以看到开发或者打包百度网盘这个功能的哥们电脑用户名ferry5,以及项目路径/baidu/netdisk/pc-browserengine/ ,为了方便调试而留的

整个函数的功能并没有直接的执行操作,更像是一个中间人,负责传话的。简析如下

// ...
// 1. 添加 "uk" 字段
boost::property_tree::basic_ptree<>::put<>(&local_88, "uk", param_1);

// 2. 添加 "type" 字段
boost::property_tree::basic_ptree<>::put<>
          ((basic_ptree<> *)&local_88, "type", "open_safebox", local_60);
// ...
  • 代码使用了 boost::property_tree 库,这是一个常用于处理配置数据(如 JSON、XML)的工具。
  • 它创建了一个数据结构,并向其中添加了两个键值对:
    • 键 uk,值是 param_1 (客户端传来的 uk 值)。
    • 键 type,值是固定的字符串 "open_safebox"。
  • base::util::convert_ptree_to_string(&local_50, (basic_ptree *)&local_88);序列化数据。
  • 发送消息(传话)
iVar1 = send_message((string *)&local_50);
if (iVar1 == 0) {
  uVar2 = 0; // 成功
}
else {
  // 如果发送失败,尝试启动主进程再发送
  uVar2 = start_netdisk_process((string *)&local_50);
}
  • 这是最关键的一步。程序调用 send_message 函数,将刚刚生成的包含 type 和 uk 的字符串发送给另一个正在运行的进程。
  • 如果 send_message 失败(可能因为主程序没有运行),它会尝试调用 start_netdisk_process 来启动主程序,并把消息作为启动参数传递过去。

因此需要继续分析的话,就需要分析百度网盘主程序,看了一眼是个混血儿界面是Electron写的,还要跟踪分析寻找监听和读取名为 "netdisk_unite_message_queue" 的消息队列的逻辑 ,这就触及我的知识盲区了啊!等有能力的师傅去分析或者等我学会了再来分析。

通过本次简单分析并不是毫无收获嘛,我们找到了一个可用于蜜罐的百度网盘API,通过WebGetLoginToken获取已经登录的百度账户的token、头像、用户名等等信息。


手机扫码阅读

西部数码 NAS DsdkProxy.php 命令执行漏洞

金和OA AcceptGetSourceFileName.aspx SQL注入漏洞

评 论
avatar
师傅你好,我本地mac版百度网盘版本为4.53.5,本地已登陆
GetVersion等api能调用,网页也会返回东西
但是WebGetLoginToken就返回空白了,什么数据都没有
请问师傅是什么版本的啊
1 年前 回复
avatar
Mrxn
@Rat:比你这个低三四个版本
1 年前 回复