key값을 찾기 위해 문제에서 제공한 정보는 오로지 pcapng 형식의 파일 하나뿐이었다.

Wireshark를 이용하여 열어보니 USB protocol로 통신한 흔적뿐이었다.

 

USB protocol에서 사용되는 통신의 유형은 총 4가지가 있는데 각 통신이 주로 사용되는 상황은 다음과 같다.
1. Control Transfer
: 호스트가 장치에 명령을 전송하거나, 장치 설정 정보 등을 요청할 때 사용된다. ex) 장치 초기 설정, 관리
2. Interrupt Transfer
: 소량의 데이터를 자주 보내야 할 때 사용된다. ex) 마우스, 키보드
3. Bulk Transfer
: 대용량 데이터 전송에 사용된다. ex) 프린트, 대용량 데이터
4. Isochronous Transfer
: 데이터의 실시간 전송에 사용된다. ex) 음악, 동영상 스트리밍

위 Wireshark 패킷 목록을 보면 알 수 있겠지만 장치가 호스트에게 interrupt 통신으로 데이터들을 계속 보내고 있다.
그리고 여기서 장치가 호스트에게 보내는 패킷들을 확인해보면, 아래와 같이 Leftover Capture Data라는 이름으로 어떤 데이터가 함께 전송되고 있는 것을 확인할 수 있다.

 

 

같은 USB protocol을 사용한다고 해도 데이터를 전송하는 형식이 다르기 때문에 어떤 장치가 보낸 데이터인지 확인할 필요가 있다.

USB 장치가 연결되면 호스트는 가장 먼저 연결된 장치에게 설정 정보를 보내라는 요청을 보낸다. (Control transfer - 1번 패킷)
이 요청을 받은 장치는 자신의 descriptor를 호스트에게 보낸다. (Control transfer - 2번 패킷)
이 과정을 통해 장치와 호스트가 데이터를 송수신할 준비를 하게 되고 그 후 통신이 시작된다.
그러므로 장치에 대한 정보는 2번 패킷인, 장치가 보낸 패킷 안에 있을 것임을 유추해볼 수 있다.

 

 

2번 패킷을 들여다보면 정말 DEVICE DESCRIPTOR가 있는 것을 볼 수 있는데, 여기서 idVendor와 idProduct를 통해 장치의 제조사와 제품명을 알 수 있다. Wacom의 Bamboo Pen인 것을 알았다. 아마 좀 전에 interrupt transfer로 전송된 많은 데이터들은 이 펜마우스로부터 전송된 데이터들인 건가 보다.
(3, 4번 패킷도 마찬가지로 descriptor를 확인해보면 허브인 것을 알 수 있는데, 이후 패킷들로는 Pen과 호스트 간에 송수신한 패킷만 존재하므로 패스했다.)

그럼 이제 이 제품의 데이터 전송 형식을 찾은 뒤 capture data만 추출하여 그 데이터들이 그리고 있는 그림이 어떤 것인지 알아내면 key에 대한 정보를 볼 수 있을 것 같다.

사실 이 문제 풀면서 bamboo pen 전송 데이터 형식 찾는 게 제일 어려웠다... 
일단 처음에는 bamboo pen protocol과 같은 키워드로 검색을 했는데, 여기서 발견한 것이 다음과 같은 사이트였다.
bamboo pen을 판매하는 것과 같은 느낌의 사이트였는데, tech specs에서 이 펜이 microsoft pen protocol을 사용한다는 정보를 얻게 되었다. 곧바로 키워드를 바꿨다.

 

Microsoft Pen Protocol을 검색해보면 아래와 같은 내용이 담긴 문서를 찾을 수 있다.

 

 

문제에서 사용된 장치는 Pen.. 따라서 마우스 부분에 해당되는 부분을 보면 X좌표와 Y좌표 순서로 데이터를 전송한다는 것을 알 수 있다. 그런데 위 내용의 마지막을 보면 알 수 있지만.. Note... "however this interface would not be acceptable for a Boot Device (use separate interfaces for keyboards and mouse devices)"
대략 위의 폼으로 전송되지만 이게 확실한지를 결정하려면 추가적인 정보가 필요하다.

 

 

내가 능력이 안 되는 건지.. 정확히 저 모델의 포맷을 찾는데 실패했다,,
그래서 도움을 좀 받은 결과 다음과 같은 포멧을 가진다고 한다.
길이가 9인 Capture Data가 02 F0 4C 1C D8 12 00 00 04의 값을 가진다고 할 때,

02 F0 : 헤더
4C 1C : X 좌표
D8 12 : Y 좌표
00 00 : Z 좌표 (펜을 누른 정도, 압력)
04 : 접미사

따라서 위 패킷 목록에서 Capture Data가 있는 패킷들만 뽑은 후 그 안에서 다시 Capture Data 값만 추출해 낸다. 그 후 다시 여기에서 뽑아낼 수 있는 X 좌표와 Y 좌표를 화면 상에 나타내면 저 펜을 이용하여 어떤 그림을 그렸는지 알아낼 수 있을 것이다.

이 작업을 위해 우선 Wireshark에서 Capture Data가 있는 패킷만 골라 별도의 pcapng 파일로 저장한다.
필터링을 할 수 있는 입력란에 usb protocol packet 중 capdata가 있는 파일만 골라낼 수 있는 검색어인 usb.capdata를 입력하면 된다.
그 후 [File] - [Export Specified Packets]를 이용하여 필터링된 패킷들을 별도의 pcapng 파일로 저장한다.

 

 

그 후 패킷 내의 특정 데이터들만 추출하기 위해 Wireshark의 자매품인 tshark를 사용한다. command line 버전 Wireshark라고 생각하면 되는데 이것의 사용을 위해 리눅스를 켠다. 그 후 아래와 같은 명령어로 좀 전에 capdata만 있는 패킷 내에서 capdata 필드만 뽑아서 capdata.txt 파일에 저장한다. (tshark를 설치한 적이 없으면 설치해야 한다.. 당연히..)

 

$ tshark -r (대상 pcapng 파일) -T fields -e (필드명)

 

** 뽑아낼 필드가 여러 개일 경우 -e (필드명 1) -e (필드명 2) -e (필드명 3)...처럼 이어서 적어주면 된다.

 

그러면 다음과 같이 capdata 필드 값만 파일에 저장된 것을 볼 수 있다.

 

여기에서 필요한 값은 X, Y, Z 값이므로 이들만 추출해주기 위해 다음의 명령어를 사용할 수 있다.

$ awk -F: '{print $3$4, $5$6, $7}' capdata.txt > data.txt

** 필드 사이에 공백을 넣는 용도로 , (콤마)를 입력했다.

 

 

이렇게 예쁘게 X, Y, Z 값만 뽑아낸 데이터를 저장했다.

이제 이 데이터를 가지고 그래프를 그리면 되는데, 문제는 X와 Y좌표가 존재한다고 해도 Z값인 압력 값이 0이면 해당 위치에는 필기한 흔적이 없도록 해야 한다. 이를 위해 다음과 같은 스크립트를 작성했다.

 

f = open("data.txt", "r")

for s in f.readlines():
	ss = s.split(" ")
    x = int(ss[0], 16)
    y = int(ss[1], 16)
    z = int(ss[2], 16)
    
    if z > 0:
    	print(x, y)

 

그리고 이 스크립트를 실행시켜 출력된 결과를 별도의 파일로 저장되도록 한 후, 해당 파일의 데이터를 gnuplot으로 시각화시켰더니 다음과 같은 결과가 나왔다.

$ python3 script.py > plot.txt
$ gnuplot

> plot "plot.txt"

 

 

..?

뭐지.. 하는 생각을 한동안 가지고 있다가 알게 된 것이
gnuplot에 넣을 데이터 체계가 gnuplot이 실행되고 있는 시스템의 체계와 일치하도록 해야 된다는 것이었다.
즉, 내 gnuplot이 실행되고 있는 리눅스 환경은 little endian 바이트 체계를 사용하기 때문에 내가 입력해주는 데이터도 little endian 형식으로 만들어서 넣어줘야 한다는 것이다.

http://www.gnuplot.info/docs_5.5/Gnuplot_5_5.pdf

 

내 데이터는 big endian 체계로 가져온 값이 었는데 이 부분 때문에 문제가 생겼나 보다... 하고 리틀 엔디안 방식으로 x, y값의 체계를 바꿔서 저장할 수 있도록 코드를 수정했다.

from pwn import *

f = open("data.txt", "r")

for s in f.readlines():
	ss = s.split(" ")
    x = int(ss[0], 16)
    y = int(ss[1], 16)
    z = int(ss[2], 16)
    
    if z > 0:
    	print(u16(struct.pack(">H", x)), u16(struct.pack(">H", y)))

** u16 함수와 struct.pack 함수는 pwntools 라이브러리에서 지원하는 함수이다.

u16는 16바이트의 패킹된 바이트 문자열을 인자로 받아 unpacking 된 바이트 문자열로 바꿔주기 위해 사용된다. endian에 대한 별다른 옵션을 붙여주지 않을 경우 기본적으로 들어온 바이트 문자열을 little endian 타입으로 변환한다.

struct.pack은 어떤 데이터를 패킹된 바이트 문자열로 바꿔주는 함수인데 첫 번째 인자로 변환할 값의 포맷을, 두 번째 인자로 변환할 값을 넣어준다.
위 스크립트에서는 2 바이트 크기의 big endian unsigned short integer인 x와 y를 표현하기 위한 포맷으로 ">H"을 사용했다.
(더 많은 포멧 표현은 아래 문서 참고.)
- Format Characters : https://docs.python.org/2/library/struct.html#format-characters
- Byte Order, Size, Alignment : https://docs.python.org/2/library/struct.html#struct-alignment

결과적으로 2 바이트의 big endian integer인 x와 y를 바이트 문자열로 packing 하고, 이를 2 바이트의 little endian 바이트 문자열로 unpacking 해주는 것이다. 

이렇게 수정된 스크립트를 다시 실행시켜 x, y 좌표값 데이터 파일을 얻은 후 그래프를 그리면 flag가 쓰인 문자들을 읽을 수 있게 된다.

 

 

좌우반전까지 시키면 완벽하겠지만.. 사실 이렇게로도 얼추 다 알아볼 수 있어서 그냥 그대로 flag를 읽어냈다.

+ Recent posts