Menci's Blog
念念不忘,必有回响
被加密的移动光猫 PPPoE 密码

最近帮朋友家新装的宽带配网时,登录超级管理员 CMCCAdmin 后,发现光猫管理页面上的 PPPoE 密码是 6 个 * 星号 —— 好在这台光猫不需要拆机就可以开启 Telnet,不需要大动干戈地拆机拿示波器去找 TTL 了。然而高兴了没多久,登录进 Telnet 之后才发现,虽然是熟悉的 pppd 拨号,但配置里的拨号密码被加密了……

光猫的型号是 ZXHN F7005MV3,可以使用 zteOnu 工具开启 Telnet:

./zteOnu --ip "192.168.1.1" --new --user "CMCCAdmin" --pass "CMCCAdminXXXXXXXX" --port 80 --tp 23
ZteONU dev, built at unknown
source: https://github.com/thank243/zteOnu
-----------------------------------
step [0] reset factory: ok
step [1] request factory mode: ok
step [2] send sq: ok
step [3] check login auth: ok
step [4] enter factory mode: ok
-----------------------------------
user: aaaaaaaa
pass: bbbbbbbb

登录 Telnet 后,使用 ps 命令看到光猫使用 pppd 进行 PPPoE 拨号:

ps | grep pppd
 5718  user      7568    2376  S    <1>  pppd 0 oe normal file /var/tmp/ppp/options.oe0 nbif1
 6187  root      6276     948  S    <1>  grep pppd

查看 pppd 的配置文件 /var/tmp/ppp/options.oe0(其中地址/用户名/密码已被替换为样例):

cat /var/tmp/ppp/options.oe0
plugin rp-pppoe.so
+ipv6
ipv6 ::d2dd:ffff:ffff:ffff,
user 11111111111
plugin passpppoe.so
encrypasswd 5876a52b309edc013d37d8c857e11db1
encrypasswdlen 16
lcp-echo-failure 3
lcp-echo-interval 10
mru 1480
mtu 1480
dscp -1
priority -1
queuenum 15
padi-nterval 5
usepeerdns

可见拨号密码加密为 encrypasswd 字段,注意到可疑的 passpppoe.so 插件,猜测和处理解密有关,在 /lib 找到了这个文件(下载),使用 curl 提取出来:

file passpppoe.so
passpppoe.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, BuildID[sha1]=6bd5fa48300fa20933647df62747c97128e52e70, stripped

使用 IDA 打开,找到 plugin_init 函数:

int plugin_init()
{
  int result; // r0

  result = add_options(&off_11170);
  pap_check_hook = sub_978;
  pap_passwd_hook = sub_AB0;
  chap_check_hook = sub_978;
  chap_passwd_hook = sub_AB0;
  return result;
}

解密逻辑在 pap_passwd_hookchap_passwd_hook 所指向的 sub_AB0 函数中,对变量名进行还原后的逻辑如下:

int __fastcall sub_AB0(int a1, char *result)
{
  int dec_len; // r4
  size_t s_len; // r0
  bool has_extra_block; // zf
  int i; // r9
  unsigned int dec_len_; // r4
  char *decrypted_block; // r1
  char *decoded_block; // r0
  char s_sha256[32]; // [sp+10h] [bp-2E4h] BYREF
  char s[36]; // [sp+30h] [bp-2C4h] BYREF
  char decoded[196]; // [sp+54h] [bp-2A0h] BYREF
  char decrypted[196]; // [sp+118h] [bp-1DCh] BYREF
  _BYTE aes_key[280]; // [sp+1DCh] [bp-118h] BYREF

  memset(s, 0, 0x21u);
  memset(decoded, 0, 0xC3u);
  memset(decrypted, 0, 0xC3u);
  if ( !memcmp(&encrypasswd, decrypted, 0xC3u) )
  {
    ProcUserLog("passdecry.c", 113, "pwdecrypt_passwd", 4, 0, "the encrypasswd is NULL");
    return -1;
  }
  else
  {
    if ( result )
    {
      j_HexToStr((int)&encrypasswd, pass_dec_len, (int)decoded);
      strncpy(s, "608158c36497b00221db14afb845c9e3", 0x20u);
      dec_len = pass_dec_len;
      memset(s_sha256, 0, sizeof(s_sha256));
      memset(aes_key, 0, 0xF4u);
      s_len = strlen(s);
      // 密钥是上述字符串的 SHA256 值
      SHA256((int)s, s_len, s_sha256);
      AES_set_decrypt_key((int)s_sha256, 256, (int)aes_key);
      // AES padding 的处理
      has_extra_block = (dec_len & 0xF) == 0;
      if ( (dec_len & 0xF) != 0 )
        dec_len &= 0xFFFFFFF0;
      i = 0;
      if ( !has_extra_block )
        dec_len += 16;
      dec_len_ = dec_len & 0xFFFFFFF0;
      // AES-ECB 解密
      while ( dec_len_ != i )
      {
        decrypted_block = &decrypted[i];
        decoded_block = &decoded[i];
        i += 16;
        AES_decrypt(decoded_block, decrypted_block, aes_key);
      }
      strncpy(result, decrypted, 0xFFu);
    }
    return 1;
  }
}

可见,插件调用了 OpenSSL 进行解密,所用的加密方式是 AES-256-ECB,密钥是一段字符串的 SHA256 哈希值。由此编写出解密脚本:

import crypto from "crypto";

const encrypasswd = Buffer.from("d6122e4ad26fc0c611159cfb99dc9535", "hex"); // 该密码为样例
const aesKey = crypto.createHash("sha256").update("5876a52b309edc013d37d8c857e11db1").digest();
const decipher = crypto.createDecipheriv("aes-256-ecb", aesKey, null);
decipher.setAutoPadding(false);
const decrypted = Buffer.concat([
  decipher.update(encrypasswd),
  decipher.final()
]);

console.log(decrypted);
console.log(decrypted.toString("utf8"));

得到密码后,删除 PPPoE 和 TR069 网络,把 PPPoE 和 TR069 的 VLAN ID 桥出来,并在 LAN 口上配置 VLAN 绑定,这之后就是熟悉的配网流程了。