social



SHA2017 CTF - Samsung S4 writeup

A forensic investigator was wondering how hacker boxes like RIFF and Z3X use JTAG on a Samsung S4 phone while the main cores have debugging disabled.

oldzg@zhongtiao

Ah, what a pleasant evening! From the outside, I hear a distant thunder; the air, still calm, carries the scent of a nearby lake. I finish sorting the herbs I gathered on the mountainside today and stretch a bit, eager to take on tonight's mysterious riddle. Pouring myself some tea, I unroll the scroll, revealing the neat lines:

Time, tms, tck, tdi, tdo
0.000000250, 1, 0, 1, 1
0.000000500, 1, 1, 1, 1
0.000000750, 1, 1, 1, 1
0.000001000, 1, 0, 1, 1
0.000001250, 1, 0, 1, 1
0.000001500, 1, 1, 1, 1
0.000001750, 1, 1, 1, 1
0.000002000, 0, 0, 1, 1
0.000002250, 0, 0, 1, 1
...
0.001150750, 0, 1, 1, 0
0.001151000, 0, 1, 1, 0

These columns remind me of something, but what? I use a discerning spell to get my bearings:

(ctf) oldzg@zhongtiao:~/work/sha2017/samsungs4$ googler --np --count 3 tms tck tdi tdo
1 JTAG - Wikipedia
https://en.wikipedia.org/wiki/JTAG
The Joint Test Action Group (JTAG) is an electronics industry association formed in 1985 for .... TDI (Test Data
In); TDO (Test Data Out); TCK (Test Clock); TMS (Test Mode Select); TRST (Test Reset) optional. Test reset signal
is not shown in the ...

2 Technical Guide to JTAG - XJTAG Tutorial - XJTAG.com
https://www.xjtag.com/about-jtag/jtag-a-technical-overview/
TMS (Test Mode Select) – this signal is sampled at the rising edge of TCK to ... BYPASS – this instruction causes
the TDI and TDO lines to be connected via a ...

3 What is the Purpose of the TCK, TDI, TDO, and TRST Pins on the ...
http://digital.ni.com/public.nsf/allkb/56D0F010AB081DED86256FF10070EBB4
1 aug. 2016 - The TNT5002 Reference Manual mentions the existence of pins such as TCK, TDI, TDO,and TRST, but I
cannot seem to find any description on ...

JTAG, hmm? Sadly, I do not know much about this arcane interface, but a curious mind prevails, like a river grinding down a mountain; and so, mixing some fresh leaves and stalks into my tea, I start perusing the guides for more clues to its function.

After some exploration, it becomes apparent that the JTAG nexus behaves as a "state machine", with its mode of operation shifting according to some rules based on the value of the TMS signal; also, the interesting bits would most probably be poured into and drained from a device while the system stays in either a "Shift-DR" or a "Shift-IR" state, through the TDI and TDO lines.

A course of action emerges; first, I must parse the table to derive the actual data coming in and out of the connection. The line states should be sampled on the rising clock edge, that is, whenever the TCK signal switches to 1:

lines = open("CTF_SamsungS4.csv").readlines()[1:]

tms_s = ""
tdi_s = ""
tdo_s = ""

for l in lines:
    tms, tck, tdi, tdo = l.strip().split(", ")[1:]
    if tck == "1" and prev_tck == "0":
        tms_s += tms
        tdi_s += tdi
        tdo_s += tdo
    prev_tck = tck

open("data.bin", "w").write((tms_s, tdi_s, tdo_s))

Now, I would need some guidance pertaining to the inner workings of the JTAG "state machine". I scry for a handy reference and, finding one, begin my work on the mimicry:

states = {
        "Test-Logic-Reset":("Run-Test/Idle", "Test-Logic-Reset"),
        "Run-Test/Idle":("Run-Test/Idle", "Select DR-Scan"),
        "Select DR-Scan":("Capture-DR", "Select IR-Scan"),
        "Select IR-Scan":("Capture-IR", "Test-Logic-Reset"),
        "Capture-DR":("Shift-DR", "Exit1-DR"),
        "Capture-IR":("Shift-IR", "Exit1-IR"),
        "Shift-DR":("Shift-DR", "Exit1-DR"),
        "Shift-IR":("Shift-IR", "Exit1-IR"),
        "Exit1-DR":("Pause-DR", "Update-DR"),
        "Exit1-IR":("Pause-IR", "Update-IR"),
        "Pause-DR":("Pause-DR", "Exit2-DR"),
        "Pause-IR":("Pause-IR", "Exit2-IR"),
        "Exit2-DR":("Shift-DR", "Update-DR"),
        "Exit2-IR":("Shift-IR", "Update-IR"),
        "Update-DR":("Run-Test/Idle", "Select DR-Scan"),
        "Update-IR":("Run-Test/Idle", "Select DR-Scan"),
        }

# assume the initial state is "Run-Test/Idle"
state = "Run-Test/Idle"

tms_s, tdi_s, tdo_s = eval(open("data.bin").read())

for clock in range(len(tms_s)):
    tms = tms_s[clock]
    state = states[state][int(tms)]
    print clock, state
(ctf) oldzg@zhongtiao:~/work/sha2017/samsungs4$ python decode.py > out
(ctf) oldzg@zhongtiao:~/work/sha2017/samsungs4$ head -n30 out 
0 Select DR-Scan
1 Select IR-Scan
2 Capture-IR
3 Shift-IR
4 Shift-IR
5 Shift-IR
6 Shift-IR
7 Shift-IR
8 Shift-IR
9 Shift-IR
10 Shift-IR
11 Shift-IR
12 Shift-IR
13 Shift-IR
14 Shift-IR
15 Shift-IR
16 Shift-IR
17 Shift-IR
18 Exit1-IR
19 Update-IR
20 Select DR-Scan
21 Capture-DR
22 Shift-DR
23 Shift-DR
24 Shift-DR
25 Shift-DR
26 Shift-DR
27 Exit1-DR
28 Update-DR
29 Select DR-Scan

Well, my imitation certainly seems to be spending right amount of time in the Shift-IR and Shift-DR states; the next step would be learning about what actually gets passed in and out:

...

out_buf = ""
in_buf = ""

for clock in range(len(tms_s)):

    if state == "Shift-DR":
        out_buf = tdo_s[clock] + out_buf
        in_buf = tdi_s[clock] + in_buf
    if state == "Exit1-DR":
        print "Shift-DR out:", out_buf
        print "Shift-DR in:", in_buf
        out_buf = ""
        in_buf = ""

    if state == "Shift-IR":
        out_buf = data_out[clock] + out_buf
        in_buf = data_in[clock] + in_buf
    if state == "Exit1-IR":
        print "Shift-IR out:", out_buf
        print "Shift-IR in:", in_buf
        out_buf = ""
        in_buf = ""

    tms = tms_s[clock]
    state = states[state][int(tms)]

And, after some repeating lines, I see something interesting:

(ctf) oldzg@zhongtiao:~/work/sha2017/samsungs4$ python decode.py
...
Shift-IR out: 000000000010001
Shift-IR in: 111111111111100
Shift-DR out: 0000000000000000000000000000000001
Shift-DR in: 1011111111111100000000001000101110
Shift-DR out: 0000000000000000000000000000000000
Shift-DR in: 1000000000000000000000101100001110
Shift-DR out: 0000011000000000000101100010010000
Shift-DR in: 1000000000000000000000101100001110
Shift-DR out: 0011001100011011010000110111001100
Shift-DR in: 1000000000000000000000000000000000
Shift-DR out: 0110111101001110010000110000011000
Shift-DR in: 1000000000000000000000000000000000
Shift-DR out: 0101001101010110011000110100111000
Shift-DR in: 1000000000000000000000000000000000
Shift-DR out: 0001011001000011011000110110011000
Shift-DR in: 1000000000000000000000000000000000
Shift-DR out: 0101001101100110000011100111011000
Shift-DR in: 1000000000000000000000000000000000
Shift-DR out: 0100011001001110010001100110011000
Shift-DR in: 1000000000000000000000000000000000
Shift-DR out: 0000011001000011001000110100011000
Shift-DR in: 1000000000000000000000000000000000
Shift-DR out: 0110011001100110011000110011001100
Shift-DR in: 1000000000000000000000000000000000
Shift-DR out: 0010001100110011010100110100011000
Shift-DR in: 1000000000000000000000000000000000
Shift-DR out: 0000111001011111000000000000000000
Shift-DR in: 1000000000000000000000000000000000
Shift-DR out: 0000000000000000000000000000000000
Shift-DR in: 1000000000000000000000000000000000
Shift-DR out: 0111111110000000000000000000000000
Shift-DR in: 1000000000000000000000000000000000

I recall that, by the description of the puzzle, the answer should take the form of flag{[0-9a-f]{32}}, that is, be exactly 38 symbols long; and, looking at the bits coming through TDO, I can see exactly nine full batches of 34 bits and then one that seems to be holding two meaningful bytes.

Unsure in the precision of my understanding (why are there 34 bits, for instance?), I nevertheless check the ASCII manuscript for the bits of "flag":

(ctf) oldzg@zhongtiao:~/work/sha2017/samsungs4$ python 
Python 2.7.13 (default, Jan 19 2017, 14:48:08)
[GCC 6.3.0 20170118] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> bin(ord('f'))
'0b1100110'
>>> bin(ord('l'))
'0b1101100'
>>> bin(ord('a'))
'0b1100001'
>>> bin(ord('g'))
'0b1100111'

So, how well does it correspond with the first output of 0011001100011011010000110111001100? Immersed in thought, I pick up my brush, and after a minute the connection becomes apparent:

Flag string

Savoring the moment of triumph, I sip my tea before setting to rework the last bit of my spell:

...

# borrowed from https://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks

def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]

out_buf = ""
in_buf = ""
flag = ""

for clock in range(len(tms_s)):

    if state == "Shift-DR":
        out_buf = tdo_s[clock] + out_buf
    if state == "Exit1-DR":
        flag += ''.join([chr(int(_[::-1], 2)) for _ in list(chunks(out_buf[1:], 8))[:4]])

    tms = tms_s[clock]
    state = states[state][int(tms)]

flag = flag[flag.index("f"):flag.index("}")+1]
print flag
(ctf) oldzg@zhongtiao:~/work/sha2017/samsungs4$ python decode.py
flag{9a0e5c94ac3e38719130ab133cfbfe18} 

Success again! Indeed, there can be no mistakes if you just keep to your course and following the signs. Even JTAG won't be able to hide its secrets forever from a noble hermit's curious eyes!

But a wise man also should allow himself plenty a pause; listening in to the thunderstorm, now close by, I finish my tea, breathe in deeply and let my eyes rest for tonight.

For more oldzg@zhongtiao writeups, proceed here!