2022CISCN初赛 ez_usb 复盘WriteUp

前言

本篇首发于看雪社区

发现看雪社区对带有图片markdown非常友好,开始有点想把我博客迁过去了

本篇所有图片都挂载在看雪社区的图床下

前期准备

USB协议规范

l USB UART
l USB HID
l USB Memory

最上面这个就是标准的串口实现,可以通过USB连接STM32或ESP8266这些MCU

中间这个可以做BadUSB(实现键盘模拟输入)

最下面这个应该是访问U盘

每一种USB设备,尤其是人机交互设备和存储设备,都有一串特殊的数字,这串数字被称为厂商ID和产品ID。这两个数字一般是成对出现的。厂商ID用于标明该产品是由哪一个制造商所生产的,而产品ID是产品的标识符。

有关协议的详细说明:USB初学(一)---USB-HID的初步认识【转】

HID设备的描述符除了5个USB的标准描述符(设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符)外,还包括三个HID设备类特定的描述符:HID描述符、报告描述符、实体描述符。

  除了HID的三个特定描述符组成对HID设备的解释外,5个标准描述符中与HID设备有关的部分有:

  • 设备描述符中:bDeviceClass, bDeviceSubClass, bDeviceProtocol三个值必须为0
  • 接口描述符中:bInterfaceClass的值必须时0x03, bInterfaceSubClass的值为0或1, 为1表示HID设备是一个启动设备(Boot Device, 一般对PC机有意义,意思是BIOS启动时能识别您使用的HID设备,切只有标准鼠标或者键盘才能称为Boot Device),为0表示HID设备是操作系统启动厚才能识别使用的设备。bInterfaceProtocol的取值含义如下:
bInterfaceProtocol的取值(十进制)含义
0None
1键盘
2鼠标
3-255保留

buu的一道赛题

当时做到时候由于不熟悉usb流量分析,网上资料也看的云里物理,找了篇题解进行分析

CTF-BUUCTF-MISC-USB

buu usb 1分题

首先,修复rar的文件头

里面有一张233.png

233.png通过stegsolver可以提取出一张二维码出来

2022-07-06_21-07.png

用草料二维码可以提取出一段字

ci{v3erf_0tygidv2_fc0}

另外还有一个fkm文件,发现503B这个zip文件头,另存为zip然后解压

提到了可以用tshark把指令提取出来

tshark -r key.pcap -T fields -e usb.capdata > usbdata.txt

但我实际用UsbKeyboardDataHacker出结果

image-20220706214409739.png

维吉尼亚密码,密码为xinan

fa{i3eei_0llgvgn2_sc0}

离flag很近,具备栅栏密码的特征

还真是

image-20220706215234037.png

切入正题

根据标识符,找到设备的情况:

2.4.0 未知设备

2.8.0 keyboard

2.10.0 logitech

2.3.0 Lite-on Technology

2.4.0 Intel Bluetooth

用usb.addr作为过滤条件筛出数据,然后分组导出

usb.addr== "2.8.1"
usb.addr== "2.10.1"

2.8.1 里藏了一个压缩包,有着rar文件头的标记

image-20220706221003241.png

标识去掉,新建hex文件

tips:010editior的hex粘贴需要从Edit进入

image-20220706221406518.png

2.10.1 里藏了密码35c535765e50074a,应该是鼠标信号

image-20220706220912894.png

解压即可得到flag

当时走过的弯路

当时十分莽撞的直接拿现成脚本去试,结果什么都没有

image-20220706221645171.png

照猫画虎x2,用提取不少帖子用tshark提取capdata

tshark -r ez_usb.pcapng -T fields -e usb.capdata > usbdata.txt

然后很不幸的得到了一个空的txt

看了hid部分以后,想了一下抓的应该是usbhid.data

tshark -r ez_usb.pcapng -T fields -e usbhid.data > usbdata.txt

有结果了

image-20220706225419916.png

找到一个处理冒号和空格的脚本(忘记从哪里找来的)

#!/usr/bin/env python
#-*- coding: utf-8 -*-
info = '''
kali下运行:
tshark -r usb.pcap -T fields -e usb.capdata > usbdata.txt
提取流量包信息
然后通过该脚本可以过滤掉空格和其他内容,并且添加冒号
'''
print(info)

f_data  = input("请输入带处理txt文件的路径:")
shujian = int(input("鼠标流量信息请输入8,键盘流量请输入16:"))
f = open(f_data,'r')

# 整理到out.txt
with open('out.txt','w') as f_out:
    for i in f.readlines():
        s = i.strip()
        # 鼠标流量长度为8 ,键盘流量长度为16
        if len(s) == shujian:
            # 鼠标流量长度为8 ,键盘流量长度为16
            nsl = [s[j:j+2] for j in range(0,shujian,2)]
            ns = ":".join(nsl)
            f_out.write(ns)
            f_out.write('\n')

数据整理完成,开始转换

//tran.py
normalKeys = {"04": "a", "05": "b", "06": "c", "07": "d", "08": "e", "09": "f", "0a": "g", "0b": "h", "0c": "i",
              "0d": "j", "0e": "k", "0f": "l", "10": "m", "11": "n", "12": "o", "13": "p", "14": "q", "15": "r",
              "16": "s", "17": "t", "18": "u", "19": "v", "1a": "w", "1b": "x", "1c": "y", "1d": "z", "1e": "1",
              "1f": "2", "20": "3", "21": "4", "22": "5", "23": "6", "24": "7", "25": "8", "26": "9", "27": "0",
              "28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>", "2d": "-", "2e": "=", "2f": "[",
              "30": "]", "31": "\\", "32": "<NON>", "33": ";", "34": "'", "35": "<GA>", "36": ",", "37": ".", "38": "/",
              "39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
              "40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>"}

shiftKeys = {"04": "A", "05": "B", "06": "C", "07": "D", "08": "E", "09": "F", "0a": "G", "0b": "H", "0c": "I",
             "0d": "J", "0e": "K", "0f": "L", "10": "M", "11": "N", "12": "O", "13": "P", "14": "Q", "15": "R",
             "16": "S", "17": "T", "18": "U", "19": "V", "1a": "W", "1b": "X", "1c": "Y", "1d": "Z", "1e": "!",
             "1f": "@", "20": "#", "21": "$", "22": "%", "23": "^", "24": "&", "25": "*", "26": "(", "27": ")",
             "28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>", "2d": "_", "2e": "+", "2f": "{",
             "30": "}", "31": "|", "32": "<NON>", "33": "\"", "34": ":", "35": "<GA>", "36": "<", "37": ">", "38": "?",
             "39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
             "40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>"}
output = []
keys = open('out.txt') #这里是加号冒号的数据
for line in keys:
    try:
        if line[0]!='0' or (line[1]!='0' and line[1]!='2') or line[3]!='0' or line[4]!='0' or line[9]!='0' or line[10]!='0' or line[12]!='0' or line[13]!='0' or line[15]!='0' or line[16]!='0' or line[18]!='0' or line[19]!='0' or line[21]!='0' or line[22]!='0' or line[6:8]=="00":
             continue
        if line[6:8] in normalKeys.keys():
            output += [[normalKeys[line[6:8]]],[shiftKeys[line[6:8]]]][line[1]=='2']
        else:
            output += ['[unknown]']
    except:
        pass
keys.close()

flag=0
print("".join(output))
for i in range(len(output)):
    try:
        a=output.index('<DEL>')
        del output[a]
        del output[a-1]
    except:
        pass
for i in range(len(output)):
    try:
        if output[i]=="<CAP>":
            flag+=1
            output.pop(i)
            if flag==2:
                flag=0
        if flag!=0:
            output[i]=output[i].upper()
    except:
        pass
print ('output :' + "".join(output))

然后得到同样一个带有rar文件头的数据包

image-20220706230035160.png

当时为了修复这个包,花了不少力气。但事后来看,只要所有数据混到一块就全部木大。

运气好的话,没准可以用binwalk分离出来。

尾声

这类题目的套路就是把数据藏到capdata,然后找脚本读取就OK。本次CISCN的题目还不涉及加密算法,某些程度上来说比Buu上那道还简单。

当时在场上有把zip包从USB中提取出来,但是没把2.8.1的信息单独分离出来,即使修复文件头依然无法解压文件。只能说准备确实还不够充分,留下了遗憾。

补充

用过的exp

https://github.com/WangYihang/UsbKeyboardDataHacker/blob/master/UsbKeyboardDataHacker.py

参考过的资料

当时现场做不出来,使足力气Bing+Google,赛后发现自己没少看资料,就整理了出来

深入理解USB流量数据包的抓取与分析

CTF解题技能之压缩包分析基础篇

Wireshark官方对USB的描述

补充&巩固练习题目

关于usb流量分析

【技术分享】从CTF中学USB流量捕获与解析

内存取证&USB流量分析 —— 【高校战“疫”】ez_mem&usb