hht hacking
## Issue 79, File 0xB
## Reversing the VKSI2000 Hand-Held Trading Device
by FOX PORT
-------| 0. Table Of Contents
––•–√\/–•–– 1. Introduction
––•–√\/–•–– 2. Reversing Device Hardware
––•–√\/–•–– 3. REDACTED
––•–√\/–•–– 4. AVR Assembly
––•–––––•–––––• Assembly Language
––•–––––•–––––• AVR
––•–––––•–––––• Program Structure
––•–√\/–•–– 5. The HHT Device
––•–√\/–•–– 6. What The HHT Does
––•–√\/–•–– 7. The FIX Protocol
––•–––––•–––––• Encoding
––•–––––•–––––• Messages
––•–––––•–––––• Order Management
––•–––––•–––––• Security
––•–√\/–•–– 8. Next Steps
-------| 1. Introduction
The VKSI2000 is a handheld trading system (HHT) that provides traders
with a "secure" RF connection to a trading exchange. Direct connections
to trading exchanges are hard to come by, and so I've been pawing my way
through this weird device for the last year in an effort to research
the security of trading and exchanges.
From the outside, the HHT is deceptively simple. It's like a 1995 Palm
Pilot trading interface. You enter ticker symbols; the device receives
OTA price information and tracks your the number of shares you own (your
"position"). You can enter a number of shares and execute buys or sells
at the current market price. That's it.
┌───────────────────────────────────────────┐
│ │
│ ◐ │
│ │
│ │
│ ┌────────────────┐┌───┐┌───┐ ┌───┐┌─┬─┐ │
│ │ FCOJ ││ $ ││ # │ │ # ││ │ │ │
│ └────────────────┘└───┘└───┘ └───┘└─┴─┘ │
│ ┌────────────────┐┌───┐┌───┐ ┌───┐┌─┬─┐ │
│ │ FCOJ ││ $ ││ # │ │ # ││ │ │ │
│ └────────────────┘└───┘└───┘ └───┘└─┴─┘ │
│ ┌────────────────┐┌───┐┌───┐ ┌───┐┌─┬─┐ │
│ │ FCOJ ││ $ ││ # │ │ # ││ │ │ │
│ └────────────────┘└───┘└───┘ └───┘└─┴─┘ │
│ ┌────────────────┐┌───┐┌───┐ ┌───┐┌─┬─┐ │
│ │ FCOJ ││ $ ││ # │ │ # ││ │ │ │
│ └────────────────┘└───┘└───┘ └───┘└─┴─┘ │
│ ┌────────────────┐┌───┐┌───┐ ┌───┐┌─┬─┐ │
│ │ FCOJ ││ $ ││ # │ │ # ││ │ │ │
│ └────────────────┘└───┘└───┘ └───┘└─┴─┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │██████████████████████████████████████│ │
│ │██████████████████████████████████████│ │
│ │██████████████████████████████████████│ │
│ │██████████████████████████████████████│ │
│ │██████████████████████████████████████│ │
│ └──────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────┘
But lurking inside the 1995 handheld UI is a more sophisticated
1997-grade trading engine. To get to it, we need to reverse the device,
get our code running on it, and, if need be, jailbreak it.
-------| 2. Reversing The Device Hardware
Not hard.
The device has a programming port on the back, which we assume is used
to provision them for specific companies. It accepts an 8-pin connector.
For our purposes, this is simply a standard serial connector.
Serial ports are usually easy to reverse[1]. This one isn't an
exception. Pop the plastic shell of the HHT open and a multimeter finds
a ground pin (marked G) by continuity testing. Power the device on and
off and watch the pins, and transmit (T) is easy to spot.
I rolled the dice and assumed 115200 8N1 (don't overthink it!), and "cu"
and some trial and error found the receive pin.
┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐
│ ? ││ R ││ T ││ ? ││ ? ││ ? ││ ? ││ G │
└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘
▲ ▲ ▲
└────┴──────────────────┬─────┘
│
┌ ─ ─ ─ ─ ─ ─ ─ ┐
connect to UART
└ ─ ─ ─ ─ ─ ─ ─ ┘
│
│
│
▼
USB
Restart it with everything hooked up, and we get:
VKSI2000 v1.9p392 [booting]
FIELD SUPPORT ONLY UNAUTHORIZED USE PROHIBITED
(c) 1993-2015 35=G Technologies / All Rights Reserved
---------------------------
Fun times!
It gets better. There's no command prompt or help. But probe the
connection and you'll find that "$" is a special character, which
generates cryptic-looking "E ##" messages. Sure enough: there's a GDB
remote debugging stub running on the port.
I've built a version of GNU binutils on my machine with every
conceivable architecture enabled. Using the debug stub, I dumped the
memory in hex into a file and decoded it into raw binary. There are two
runs of data, separated by a sea of NUL bytes, together adding up to
around 32k. Just for convenience sake I "objcopy"'d it into an ELF file,
and then starting with X86 and working my way down the common
architectures tried to find one for which "objdump -d" printed something
intelligible.
It's AVR. Not a big surprise. I had some trouble making the jump offsets
work out, until it occurred to me to split the file in half and
ELF-encode just the second half. AVR has a split address space for code
and data.
-------| 3. This Section Removed
Legal at 35=G Inc will allow me to publish this article only if this
section is not present.
-------| 4. AVR Assembly
To do anything interesting with an embedded system like this, we need to
understand basic assembly language programming.
--[ assembly programming ]-----------
Assembly language is more intimidating than it is hard. I can sum it up
for you very briefly:
* You're given 8-32 global variables of fixed size to work with, called
"registers".
* There are special registers. The most important is the "Program
Counter", which tells the CPU which instruction we're executing next.
Every time we execute an instruction, we advance the program counter.
* Virtually all computation is expressed in terms of simple operations
on registers.
* Real programs need many more than 32 1-byte variables to work with.
* What doesn't fit in registers lives in memory.
* Memory is accessed either with loads and stores at addresses, as if it
were a big array, or through PUSH and POP operations on a stack.
* Memory is to an assembly program what the disk is to a Ruby program:
you pull things out of memory into variables, do things with them, and
eventually put them back into memory.
* Control flow is done via GOTOs --- jumps, branches, or calls. The
effect of these instructions is to alter the program counter directly.
* A jump is just an unconditional GOTO.
* Most operations on registers, like addition and subtraction, have the
side effect of altering status flags, like "the last value computed
resulted in zero". There are just a few status flags, and they usually
live in a special register.
* Branches are just GOTOs that are predicated on a status flag, like,
"GOTO this address only if the last arithmetic operation resulted in
zero".
* A CALL is just an unconditional GOTO that pushes the next address on
the stack, so a RET instruction can later pop it off and keep going
where the CALL left off.
--[ avr assembly ]-----------
Now, we can fill in some of the details for the AVR architecture in use here:
* AVR gives us 32 1-byte registers.
* AVR is a RISC-style architecture, so it isolates computation to
registers; we get a "load", "store", "push", and "pop" and little else
that directly touches memory.
* There's a stack, that grows downwards: the bottom of the stack is at a
high address, and each PUSH decrements the stack pointer.
* This AVR chip gives us 16k of memory.
* The AVR status flags live in SREG, and are (C)arry, (Z)ero,
(N)egative, o(V)erflow, (S)igned, (H)alf-carry, (T), and (I)nterrupts.
C, Z, N, V, and S are the important ones.
--[ program structure ]-----------
99.9% of the time, when we're reversing assembly code, we're looking at
the output of a C compiler. That's especially likely an on embedded
architecture like AVR. Which is good news, because it lets us make
simplifying assumptions about the code:
* The program is divided into functions.
* We can spot functions because they have a common "prologue". In
AVR-GCC, for instance, a function that wants to use any of the registers
between 2 and 17 must save those registers, and will always do so on the
stack, which in turn adjusts the stack pointer.
* Functions are divided into basic blocks. A basic block is a run of
instructions that concludes in a GOTO. The basic blocks of a function
allow us to look at it like a flow-chart instead of a long list of
individual instructions.
We can easily break a function into basic blocks by hand. Here's a
simple example:
; r30 = r22 (16 bits, so r31 = r23, too)
2042 : movw r30, r22
; r26 = r24
2044 : movw r26, r24
; r0 = memory[Z++]
2046 : ld r0, Z+
; memory[X++] = r0
2048 : st X+, r0
; r0 = r0 AND r0
204a : and r0, r0
; if SREG[Z] == 1, goto -8
204c : brne .-8
; return
204e : ret
... now, without the annotation, start with basic block 0:
0:
2042 : movw r30, r22
2044 : movw r26, r24
2046 : ld r0, Z+
2048 : st X+, r0
204a : and r0, r0
204c : brne .-8
204e : ret
... now the ends of basic blocks by looking for jumps:
0:
2042 : movw r30, r22
2044 : movw r26, r24
2046 : ld r0, Z+
2048 : st X+, r0
204a : and r0, r0
204c : brne .-8
1:
204e : ret
... now make the TARGETS of any jumps the beginning of their own basic
blocks:
0:
2042 : movw r30, r22
2044 : movw r26, r24
1:
2046 : ld r0, Z+
2048 : st X+, r0
204a : and r0, r0
204c : brne .-8
2:
204e : ret
... and now note which blocks connect to which, and make some notes:
0: 1 (prologue)
1: 1, 2
2: (return)
Looking at an assembly program this way, we can see that control flow
isn't that much different than that of a Ruby program. We have
conditionals ("ifs") and we have loops.
We can spot many "ifs" by looking for (1) conditional branches that (2)
ultimately jump forward (this isn't always true, but it's a good
heuristic).
We can spot "while" loops by looking for conditionals with backwards
jumps.
A modern programming language might have for-loops, iterators,
exceptions, switches, and pattern matching. But on the metal, we can
express all those concepts with "if" statements and "while" loops.
The function we've been looking at copies a NUL-terminated string. If it
isn't clear why:
1. It's built around a single simple loop
2. The loop reads a value from memory and then writes that value
somewhere else in memory.
3. The loop concludes by AND'ing the value it loads with itself. A value
AND'd against itself is that value; because AND is cheap, "X AND X" is
an idiom for "check if this value is zero". If it is, the Z flag will
get set.
4. The loop concludes, by NOT branching backwards but instead proceeding
forward to the return, if the Z flag is set.
If you'd like practice, here's a more complicated function:
2050 : movw r30, r22
2052 : ld r21, Z+
2054 : and r21, r21
2056 : breq .+42
2058 : movw r22, r30
205a : movw r26, r24
205c : ld r20, X+
205e : cp r20, r21
2060 : cpse r20, r1
2062 : brne .-8
2064 : brne .+22
2066 : movw r24, r26
2068 : ld r0, Z+
206a : and r0, r0
206c : breq .+18
206e : ld r20, X+
2070 : cp r20, r0
2072 : cpse r20, r1
2074 : breq .-14
2076 : movw r30, r22
2078 : cpse r20, r1
207a : rjmp .-34
207c : ldi r24, 0x01
207e : ldi r25, 0x00
2080 : sbiw r24, 0x01
2082 : ret
Just remember that branches work by adding or subtracting from the
program counter AFTER decoding the instruction; in effect, you're
offsetting from the instruction FOLLOWING the branch.
--[ more on avr ]-----------
The AVR instruction set is available online; you can usually Google for
"avr CPSE" to see a page for the CPSE instruction. It's long enough that
I won't include it all here, but I'll give you a brief summary of its
contours:
You've got your math instructions:
ADC, ADD, ADIW, AND, ANDI, ASR, EOR, INC, LAS, LAT, LSL, LSR, ROL, ROR,
DEC, OR, NEG, SUB, SUBI
Each of those will take a pair of registers, perform an operation on
both, and store the result back to the first argument. So "ADD r2, r3"
means "R2 = R2 + R3".
You've got your PUSH, your POP, and your JMP.
You've got your branches, each of which checks a status flag or pattern
of status flags and either does or does not jump. BREQ and BRNE, which
check whether the Z flag is or isn't set, is the most common of these.
BRBC, BRBS, BRCC, BRCS, BREQ, BRGE, BRHC, BRHS, BRID, BRIE, BRLO, BRLT,
BRMI, BRNE, BRPL, BRSH, BRTC, BRTS, BRVC, BRVS
You've got your instructions that set or clear flags in SREG explicitly;
for instance, SEV (SE)ts the o(V)erflow flag:
SEC, SEH, SEI, SEN, SER, SES, SET, SEV, SEZ, CLC, CLH, CLI, CLN, CLR,
CLS, CLT, CLV, CLZ
You've got your instructions that call functions, or return from them. A
CALL is like a GOTO that pushes a return address onto the stack (it's a
"JUMP PUSH"), and a RET is a GOTO that pops the address to go to from
the stack (it's a "POP JUMP").
CALL, ICALL, RCALL, RET
You've got your loads and your stores:
LD[XYZ][+], ST[XYZ][+].
Loads and stores are relative to the X, Y, and Z registers, which are
aliases for the 16 bit register pairs starting at r26, r28, and r30
respectively (r30 is an alias for Z). Some loads and stores increment
the address after finishing, so they can be called repeatedly to (for
instance) copy data.
There are a few funkier instructions that have to do with I/O, or with
space-saving, but when you come across them, you can just Google them.
-------| 5. The HHT Device
Here's what I've worked out about what the board in this HHT looks like:
┌ ─ ─ ─ ─ ─ ┐
│ RF │
└ ─ ─ ─ ─ ─ ┘
▲
▼
╔═══════════╗ ╔═══════════╗ ╔═══════════╗
║ mystery ║ ║ app ║ ║ network ║
║ avr ║ ║ avr ║ ║ avr ║
║ ║ ║ ║ ║ ║
╚═══════════╝ ╚═══════════╝ ╚═══════════╝
▲ ▲ ▲
└───────────────┼──────────────┘
▼
┌ ─ ─ ─ ─ ─ ─ ─ ─
│ i/o controller |
└ ─ ─ ─ ─ ─ ─ ─ ─
▲
┌────────┴───────┐
│ │
▼ │
┌───────────┐ ┌ ─ ─ ─ ─ ─ ┐
│ │ │ │
│ flash │ │ interface │
│ │ │ │
└───────────┘ └ ─ ─ ─ ─ ─ ┘
There are three AVR processors in the device. I'll call them "app",
"mystery", and "network".
The "network" processor drives the RF chipset; it's the device's link to
the outside world.
The "app" processor handles requests from the user interface.
The AVR chips, storage, RF chipset, and interface components are linked
by a bus, which is managed by an I/O controller.
So in a sense, the HHT has a built-in LAN. The network uses what appears
to be a trivial protocol, with an address byte, a length byte, and a
variable-sized payload.
┌──────┐ ┌──────┐ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
│ ADDR │ │ LEN │ │ (VARLEN) PAYLOAD │
└──────┘ └──────┘ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
Now the bad news: we can dump and debug only the "app" processor. No
combination of pins I can find bring up a console, text output, or a
debug stub on the other two devices. Nor have I taken the time to figure
out how to dump the whole flash.
What we can see are the messages running between the processors.
Whatever the bus is, it's close enough to SPI (and uses the AVR SPI
programming interface) that a SPI bus sniffer gives us something
intelligible.
-------| 6. What The HHT Does
By sniffing the bus while fiddling with the UI, we can get a sense of
how it works:
┌ ─ ─ ─ ─ ─ ┐
│ interface │◀───────┐
└ ─ ─ ─ ─ ─ ┘ │
│ │
▼ │
╔═══════════╗ │
║ app ║ │
║ avr ║ │
║ ║ │
╚═══════════╝ │
│ │
┌──────┴─────┐ │
▼ ▼ │
╔═══════════╗┌───────────┐ │
║ mystery ║│ │ │
║ avr ║│ SPI flash │──┘
║ ║│ │
╚═══════════╝└───────────┘
│
└──────┐
▼
╔═══════════╗
║ network ║
║ avr ║
║ ║
╚═══════════╝
│
▼
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
│ exchange │
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
Enter a ticker symbol into the UI, and the "app" processor generates a
JSON message:
{
msgno: 293,
cmd: "subscribe",
ticker: "FCOJ",
}
(I've left off the I/O controller, and, as is true of all messages in
this system, it's framed with the I/O controller's protocol).
JSON goes into the "mystery" processor. FIX protocol comes out of it ---
a quote-request message.
FIX goes into the "network" processor, and, presumably, comes out the
other end and is routed to the exchange.
-------| 7. The FIX Protocol
FIX is the TCP/IP of money. It's the lowest common denominator standard
protocol used at trading exchanges (and private financial networks)
around the world.
I have good news and bad news about FIX.
The good news is: as protocols go, FIX is brain-dead simple. It is
almost, but not quite, just a weird variant of CSV. You can bang out a
FIX implementation in less than an hour.
The bad news is: every system that uses FIX uses a slightly different
dialect of FIX, so knowing how to speak FIX to one exchange won't
automatically let you speak it to another.
--[ fix encoding ]-----------
FIX messages are bags of key-value pairs. If you understand JSON, you
understand a far more sophisticated protocol than FIX.
The keys in FIX are called "tag numbers". They're numeric, and encoded
in ASCII. There are a bunch of standard tags, such as for message
encoding and basic commands, and then a zillion nonstandard tags in use
at most exchanges.
Tags and values are separated by the equals sign. So, for instance,
"666=foo" is a validly encoded tag.
Fields are virtually always 7-bit ASCII.
Fields in FIX are separated by the ASCII SOH character (that's 01h). SOH
serves the same role as commas would in CSV. FIX is the only protocol I
know of for which SOH is a metacharacter.
By convention, when writing pure-ASCII representations of FIX, we
substitute the '|' character for SOH, which isn't printable.
--[ fix messages ]-----------
All FIX messages have a prologue and an epilog, which are composed of
tags. The prologue has a version, a length, and a message type; the
epilogue has a checksum.
Here's a sample FIX message.
8=FIX.4.1|9=103|35=D|34=3|49=JOHNQTRADER|52=20151120-
12:14:01|56=EXEC|11=271948103|21=1|38=10000|40=1|
54=1|55=FCOJ|59=0|10=215
Less obnoxiously:
8= FIX.4.1 // (8) version
9= 103 // (9) len
35= D // (35) type ("order")
34= 3
49= JOHNQTRADER // (49) sendercompid
52= 20151120-12:14:01
56= EXEC // (56) targetcompid
11= 271948103
21= 1
38= 5000
40= 1
54= 1
55= FCOJ
59= 0
10= 062 // (10) checksum
You can look up FIX tags on a bunch of different websites. I've called
out the prologue, the epilogue, and the SenderCompId and TargetCompId
tags (which are, respectively, the source and the destination of the
message).
--[ fix orders ]-----------
FIX mostly exists to enter and manage orders into trading exchanges.
Clients create orders with ORDER messages. An order will have a SYMBOL
field identifying what's being traded, an ORDERQTY field specifying how
much of that thing to trade, a SIDE field specifying whether it's a buy
or sell, an ORDERTYPE field specifying whether it's a market or limit
order, and, for limit orders, a PRICE field.
Orders are tracked by two sets of identifiers, one managed by the
client, the other by the exchange.
The CLORDID field is the client's order number. The exchange echoes back
this identifier on messages about that order. It's the client's job to
make sure this number is unique, which is good news for us, because it
means order IDs are usually just monotonic counters.
The ORDERID field is the exchange's identifier for the order.
Once an order is submitted, the client gets EXECUTION REPORT messages to
update them on the status of their order.
Depending on the exchange, you might be able to use either CLORDID or
ORDERID to CANCEL or CANCEL/REPLACE an order.
Orders are uniquely identified by the tuple [SenderCompId, CLORDID,
ORDERID].
--[ fix security ]-----------
The short answer is there is mostly no such thing.
FIX defines a LOGON message (type 'A'). There's a "Password" tag, but if
you're lucky, your target system won't even use it; you start a session
by connecting to a FIX endpoint and sending a LOGON message with just
your SenderCompId.
Over the years, there have been several attempts at standards for
security features in FIX, ranging from better authentication to message
encryption. Few exchanges use these features. Trading software
developers are obsessed with message processing speed, and they're
skeptical about security.
A once common security pattern at exchanges was for connections to be
routed over private circuits, through which connectivity was only
provided to a single "gateway", which validated SenderCompId. If you
attempted to connect to Lehman Brothers FIX gateway using Bear Stearns'
SenderCompId, your connection would be rejected.
The better exchanges do have passwords on their FIX gateways. But the
performance of a FIX login is especially important (there's often a race
to start trading at the beginning of a session) and so passwords are
short and guessable.
Once logged in, there are three other big security issues.
The first and most obvious is that the market runs on software written
by humans, just like everything else. The pre-authentication attack
surface of an exchange is relatively small (FIX is a very simple
protocol to parse). But the post-auth attack surface can be enormous.
The second issue is that many markets rely on anonymity in order
handling. Clients of the market can see orders once they hit the books,
but they can't see who owns the order. Being able to learn the trading
intent of a high-volume trader can be very lucrative. There are also
trading strategies that rely on orders that aren't shown on the books
until some trigger happens. That means all the message handling at the
exchange needs to be careful not to leak information to us^H^Hmalicious
clients.
Finally and most importantly, the integrity of the market depends on us
not being able to manipulate other people's orders. That means:
[+] we shouldn't be able to submit orders with someone else's
SenderCompId
[+] we shouldn't be able to manipulate someone else's order using the
exchange's ORDERID field unless we're the ones who submitted it
[+] we shouldn't be able to learn about valid ORDERIDs that we haven't
submitted.
-------| 8. Where Do We Go Now?
We have a device that takes the old rules of securing an exchange ---
"security doesn't matter because everything's happening over leased
lines" --- and throws them out the window.
By getting our own code running on it, we should be able to get direct
access to FIX messaging.
We're at a point in the narrative where I should probably stop talking.
But, let's say for a second that I wasn't just a researcher, and I was
looking to take this attack vector as far as I could. What would my next
steps be?
1. We control the app processor, using the debugger. But it doesn't look
like the app processor can do much that the device UI can't already do.
We should make sure that's the case.
2. We should figure out what's running on the network and "mystery"
processors, and, obviously, try to get our own code running on them.
3. If we can pop the "mystery" processor, we should see how it and the
network processor work to validate FIX messages. If the "mystery"
processor does all the FIX validation, and we can take it over, maybe we
don't even care what's running on the network processor.
4. Once we get arbitrary FIX messaging, we want to start poking at the
exchange and figure out how to make money. Or, I mean, that's what you
want to do. I'm just a researcher writing tfiles.
-------------------------
GREETZ: ratscabies, BioH, THE MESSiAH[SiN], vacuum
Brought to you by Triple Sec, Old Grand-Dad Bourbon, and my dog
Reinforcements.