Samsung CTF 2018 - dingJMax

This challenge consisted of a 64bit ELF binary (dingjmax).
The idea was simple: get a perfect score (1000000) to get the flag.
I solved this challenge with the help of GDB’s Python API.

Analysis

After exploring the binary with Binary Ninja (and renaming everything I could make sense of), I realized that main contains most of the interesting functionality.
The following is a very high level view of the different code sections within main:

  1. UI Init
    a. UI windows are initialized (track window, scores window, etc)
    b. Initial flag value is set to qN7BuRx4rElDv84dgNaaNBanZf0HSHFjqOvbkFfgTRg3r
  2. Track Init
    a. Track is cleared and all key lanes are set to ‘ ‘ (0x20)
  3. Main Loop
    a. This is where the main loop starts and where user key presses are handled
    b. The loop control variable is also used to perform calculations and acts as a time variable.
    c. On each valid key press (‘d’,’f’,’j’,’k’) the flag value is updated here
    d. Other local variables are updated according to the key pressed
  4. If the time is right, update track
    a. Current track is set back by one row to check for ‘perfect’ scores
  5. Update Scores
    a. Checks the current game time and assigns a score depending on the key pressed (notice 4 similar blocks, one for each key)
    b. Scores could be: “MISS!”, “GREAT!”, “PERFECT!”
  6. Re-draw scores and update time
  7. Exit (when loop is over)
  8. I just wanted to show the main loop

Then, I asked myself the following:

  • What happens when the user presses a valid key?
  • How is a perfect score decided?

The following explains what happens when the user pressed the ‘f’ key:

  • A local variable f_key_pressed is set to 1
  • The current game_time is combined with the key code 0x66 to perform some calculations within something_with_keycode_and_time()
  • The current_flag variable is updated in update_current_flag_value()

The following shows how score is decided for the ‘f’ key lane:

A 'PERFECT!' scores happens if:

  • Current key is pressed (f_key_pressed) (not shown above)
  • Last value in the current key’s lane (f_key_lane_last_value) is 0x6F (ASCII o)
  • The current game_time equals some calculation:
    • game_time == (0x14 * (0xCCCCCCCCCCCCCCCDLL * game_time >> 64) >> 4)

Solution

My approach to the solution was simple (I had the program take care of the timing instead of doing calculations myself):

1
2
3
4
5
6
1. Set a breakpoint at Section 4 above where I knew the timing for a keypress was right.
2. Once the breakpoint hits:
a. I checked the current lane values for each key.
b. Then, I simulated a key press by jumping to the proper key handler in the main loop, depending on the current lane value.
3. ???
4. Profit.

The following is the GDB-Python script I wrote to get a perfect score every time:

dingjmax_solve.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import gdb

gdb.execute('set pagination off')
gdb.execute('file ./dingjmax')

def get_lane_vals():
d_kl = gdb.parse_and_eval('*(0x60764C-4)').cast(gdb.lookup_type('char'))
f_kl = gdb.parse_and_eval('*(0x60764D-4)').cast(gdb.lookup_type('char'))
j_kl = gdb.parse_and_eval('*(0x60764E-4)').cast(gdb.lookup_type('char'))
k_kl = gdb.parse_and_eval('*(0x60764F-4)').cast(gdb.lookup_type('char'))
return (d_kl,f_kl,j_kl,k_kl)

class MyBreakpoint(gdb.Breakpoint):
skip = 0
def stop (self):
(dl,fl,jl,kl) = get_lane_vals()

dk = int(dl == 0x6F)
fk = int(fl == 0x6F)
jk = int(jl == 0x6F)
kk = int(kl == 0x6F)

if (dk):
handler = '0x40143D' # jump to K key handler
elif(fk):
handler = '0x40145D' # jump to F key handler
elif(jk):
handler = '0x40147D' # jump to J key handler
elif(kk):
handler = '0x40149D' # jump to K key handler

if (dk or fk or jk or kk):
if (not self.skip):
gdb.execute('set $rip = ' + handler)
self.skip = 1 # since we're going back in the code flow, skip this breakpoint...otherwise infinite loop!
else:
self.skip = 0

return False


MyBreakpoint('*0x401623')
gdb.execute('run')

1
Flag: SCTF{I_w0u1d_l1k3_70_d3v3l0p_GUI_v3rs10n_n3x7_t1m3}