A friend was suffering from vendor lock-in imposed by Canfield Scientific's Visia software which would store its images in a proprietary format with .T2K extension, a "mirror suite image". I received a copy of the installation directory from program files on a Windows XP machine from around 2008.
First, I couldn't even run the software, because Canfield decided that the giant face scanner wasn't dongle enough, and had to include a HASP dependency too. After stepping a bit from VEBASEU!CMirrorAppBase::InitHaspObject() I found myself extremely lucky that all HASP interaction (initialization, check, etc.) was abstracted thru a class (called "HASP"!) and I could just shortcut a few checks:
bp 0x4ff6b0 "r ecx=0; g"
eb 0x4ff6d9 90 90
bp 0x4ff6db "r eax=1; g"
bp 0x4ff721 "r eax=1; g"
g
There's one thing I'll always miss about windows: windbg. Look at how easy it is to assign behavior to breakpoints. It's interactive code instrumentation. And being back in 32-bit land without ASLR makes it so easy to move about compared to today. Binwalk turned up nothing but after journeying through large unnecessary abstractions (Visia is very OOP and MFC) and the T2KFILE class, I had a rough outline of T2K files:
0: FC 00 00 00 // version
4: 2A 00 00 00 // header size
8: crc[0]
9: crc[3]
? compression type
? flags
? image category
? jpeg-compressed "stamp"
original image width
original image height
original image flags
original image size
original image data (MUNGED)
crc[2]
crc[1]
I don't know what a stamp is, they weren't common across the images. Or what image categories are. Why the hell would they reorder and split up the checksum like that? Then I found T2KFILE::mungeBuffer() and T2KFILE::unmungeBuffer() and:
.text:00A346DC decrypt_loop:
.text:00A346DC 8B 06 mov eax, [esi]
.text:00A346DE 33 C3 xor eax, ebx ; filebyte ^ key
.text:00A346DE ; .
.text:00A346E0 8B C8 mov ecx, eax
.text:00A346E2 83 E1 1F and ecx, 1Fh ; cl = rotate_amount
.text:00A346E2 ; .
.text:00A346E5 89 06 mov [esi], eax
.text:00A346E7 83 C6 04 add esi, 4 ; store, ptr++
.text:00A346E7 ; .
.text:00A346EA D3 C3 rol ebx, cl ; key = rol(key, rotate_amt)
.text:00A346EA ; .
.text:00A346EC 4A dec edx
.text:00A346ED 75 ED jnz short decrypt_loop
The key is always hardcoded to 0xADB which I'll guess is the initials of whatever asshole thought it was a good idea to obfuscate a standard file format (JPEG) and call it their own.
You don't actually have to know all the details of the format to retrieve the hostage JPEG! The first bytes of JPEG (in my case at least) are FF D8 FF E0, see the JFIF file structure at https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format. Since the key always starts the same, we can find the obfuscated JPEG by looking for:
FF D8 FF E0 // first bytes of JPEG
xor DB 0A 00 00 // key
-----------
24 D2 FF E0 // first bytes of obfuscated JPEG
So that's it: find this signature, and read out the rest of the file with the exception of the two checksum bytes at the end. Here's a tool:
#!/usr/bin/env python
import os
import sys
from struct import pack, unpack
def decrypt(data):
key = 0xADB
while len(data) % 4:
data = data + b'\x00'
result = [None]*(len(data)//4)
i = 0
while i < len(data):
inDword = unpack('<I',data[i:i+4])[0]
outDword = inDword ^ key
result[i//4] = pack('<I', outDword)
rotAmt = outDword & 0x1F
key2 = ((key << rotAmt) & 0xFFFFFFFF) | (key >> (32-rotAmt))
key = key2
i += 4
return b''.join(result)
data = open(sys.argv[1], 'rb').read()
offset = data.find(b'\x24\xd2\xff\xe0')
if offset == -1: raise Exception('embedded JPEG not found')
print('found at offset 0x%X, leaving 0x%X bytes' % (offset, len(data)-offset))
jpeg = decrypt(data[offset:-2])
fpath = os.path.join('.', os.path.splitext(os.path.basename(sys.argv[1]))[0] + '.jpg')
print('writing %s' % fpath)
with open(fpath, 'wb') as fp:
fp.write(jpeg)
This worked for over 3500 T2K files, liberating 60GB worth of data from a situation where only a single PC could access it. Canfield deliberately took a standard format that would have allowed its customers easy migration and adulterated it with DRM. This cannot be attributed to HIPAA because the algorithm does not vary the key, and it wouldn't be cryptographically secure even if it did. It is there only to prevent customers from accessing their images outside of Visia. Imagine how easy it would be to develop with GnuPG, perhaps tying the key to the hardware, and storing JohnDoe.jpg.gpg with a migrate or export feature. Perhaps it's to cover up that their product is just an enclosure for off-the-shelf cameras:
00000000 FF D8 FF E1 14 A7 45 78 69 66 00 00 49 49 2A 00 08 00 00 00 10 00 │◆│......Exif..II*.......
...
0000048E 00 00 0A 00 00 00 00 00 00 00 00 00 00 00 00 40 49 4D 47 3A 50 6F │▒│...............@IMG:Po
000004A4 77 65 72 53 68 6F 74 20 41 36 34 30 20 4A 50 45 47 00 00 00 00 00 │▒│werShot A640 JPEG.....
000004BA 00 00 00 00 46 69 72 6D 77 61 72 65 20 56 65 72 73 69 6F 6E 20 31 │▒│....Firmware Version 1
000004D0 2E 30 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │▒│.00...................