Hacking Time (reverse 200 pts)

Starting

At challenge start we have the HackingTime_03e852ace386388eb88c39a02f88c773.nes file (which we name ht.nes for simplicity).

ht.nes

The first reaction is to analyze the nature of the file

file ht.nes

As we can see it is a "iNES ROM" file. I'm not completely new to this kind of file as I used to play old NES game on a xbox emulator but, I never see a .nes file into a CTF...I'm curious to see what it is ;-)

Let's continue the analysis with the command "strings"

string ht.nes

Mmmm... there are some interesting strings referring to some "lockdown" password and also messages saying that the password can give us score point. This let me think that the password is also the flag to submit !

Going inside the game

At this point I think the file is actually a "real" NES game, so let's try it. After some internet research we found a good NES emulator (with a debugger and RAM explorer) to play the game [http://www.fceux.com/web/home.html]

Here we are ! the game start

start

After following the game messages we land on the "lockdown stage"

lockdown

After some time spent to become familiar with the emulator and all the tools it offers, we start to play with the password input.

The first things we notice are the memory changes that occurs at every password modification (modifying password from "AAAAAAAAAAAAAAAAAAAA" to "ABCDAAAAAAAAAAAAAAAA"

ram_1

ram_2

As we can see the address $0005, $0006, etc are changing according with the password input. Moreover we can see that the memory contains the hex value of the password character we have as input.

Now is time to inspect a little bit the code execution. We place a "Write breakpoint" for the memory address $0005 and we change the first character of the password to see what happen.

wb

The breakpoint is hit and we can "step into" the game execution. At this point we are moving inside the game instructions to look if there is something interesting, but it is not an easy task and there are too many "functions" call to follow...I'm a bit lost damn...

The next step is to see if there is something interesting at the moment where the game read the password. So we add a "Read breakpoint" for the memory address $0005 and we press "A button" inside the game to validate the password

Bingo ! the breakpoint is hit and we can inspect what the game is doing when reading the password

rb

The execution is stopped at $82F7, where we can see there is a "memory read" for the address $0005, Y

thanks to the NES opcodes and addressing guides we found here:

http://www.thealmightyguru.com/Games/Hacking/Wiki/index.php?title=6502_Opcodes

http://www.thealmightyguru.com/Games/Hacking/Wiki/index.php?title=Addressing_Modes

we can say that LDA $0005,Y is the same as saying "read memory[0005 + Y]

Back to our code. We look around in this "function" to see important and interesting things.

>00:82F1:A0 00     LDY #$00
 00:82F3:A9 00     LDA #$00
 00:82F5:85 3B     STA $003B = #$40
 00:82F7:B9 05 00  LDA $0005,Y @ $0007 = #$41
 00:82FA:AA        TAX
 00:82FB:2A        ROL
 00:82FC:8A        TXA
 00:82FD:2A        ROL
 00:82FE:AA        TAX
 00:82FF:2A        ROL
 00:8300:8A        TXA
 00:8301:2A        ROL
 00:8302:AA        TAX
 00:8303:2A        ROL
 00:8304:8A        TXA
 00:8305:2A        ROL
 00:8306:48        PHA
 00:8307:A5 3B     LDA $003B = #$40
 00:8309:AA        TAX
 00:830A:6A        ROR
 00:830B:8A        TXA
 00:830C:6A        ROR
 00:830D:AA        TAX
 00:830E:6A        ROR
 00:830F:8A        TXA
 00:8310:6A        ROR
 00:8311:85 3B     STA $003B = #$40
 00:8313:68        PLA
 00:8314:18        CLC
 00:8315:65 3B     ADC $003B = #$40
 00:8317:59 5E 95  EOR $955E,Y @ $9560 = #$53
 00:831A:85 3B     STA $003B = #$40
 00:831C:AA        TAX
 00:831D:2A        ROL
 00:831E:8A        TXA
 00:831F:2A        ROL
 00:8320:AA        TAX
 00:8321:2A        ROL
 00:8322:8A        TXA
 00:8323:2A        ROL
 00:8324:AA        TAX
 00:8325:2A        ROL
 00:8326:8A        TXA
 00:8327:2A        ROL
 00:8328:AA        TAX
 00:8329:2A        ROL
 00:832A:8A        TXA
 00:832B:2A        ROL
 00:832C:59 76 95  EOR $9576,Y @ $9578 = #$7A
 00:832F:99 1E 00  STA $001E,Y @ $0020 = #$00
 00:8332:C8        INY
 00:8333:C0 18     CPY #$18
 00:8335:D0 C0     BNE $82F7
 00:8337:A0 00     LDY #$00
 00:8339:B9 1E 00  LDA $001E,Y @ $0020 = #$00
 00:833C:D0 08     BNE $8346
 00:833E:C8        INY
 00:833F:C0 18     CPY #$18
 00:8341:D0 F6     BNE $8339
 00:8343:A9 01     LDA #$01
 00:8345:60        RTS -----------------------------------------
 00:8346:A9 00     LDA #$00
 00:8348:60        RTS -----------------------------------------

This function is apparently reading a character of our password, manipulating it with some shift, xor, additions and finally is checking something.

If we look closely to the check part we can notice 2 things:

 00:8332:C8        INY
 00:8333:C0 18     CPY #$18
 00:8335:D0 C0     BNE $82F7

this first check is used to jump at the beginning of the function, when a char of the password is read if Y (which is a kind of for loop counter) is not equal to 0x18 which is in fact 24 in decimal and magic, is also the length of the password.

00:8337:A0 00     LDY #$00
 00:8339:B9 1E 00  LDA $001E,Y @ $0020 = #$00
 00:833C:D0 08     BNE $8346
 00:833E:C8        INY
 00:833F:C0 18     CPY #$18
 00:8341:D0 F6     BNE $8339
 00:8343:A9 01     LDA #$01
 00:8345:60        RTS -----------------------------------------
 00:8346:A9 00     LDA #$00
 00:8348:60        RTS -----------------------------------------

the second check seems to look if the value of the memory at $001E, Y is equal to 0. If it is the case, then it increment Y and it continue the check until Y reach 24. If the stored value is not 0 then the function end returning 0

Mmmm...we are on the right way (I hope ^^ )

let's see where we go when the function return.

we "step over" until we reach the LDA#$00 and RTS. Then at the next step we land here:

rb

Interesting...there is a comparison to see if the returned value is equal to 0. In our scenario the value is 0 so the jump is not taken. We continue the execution and we get the "Access Denied" message

access denied

So we retry but this time we modify the "Z" flag just before the "CMP" instruction at $8224.

patching

after this modification we continue the execution and Magic ! We have a success.

patching

It's a good hit but it's not enough. We clearly have identified where the password is checked and we also have won the game with a "random" password but in order to win the flag we have to find the right password that disable the lockdown.

So let's jump back to the "password reading / checking" function that we have identified to start at "$82F1" and give to it a clooser look:

we can identify this kind of logic:

for i from 0 to 24:
   a=rol(passwd[i],3)       #rotate left password[i] for 3 times
   b=rot(M[003B],2)         #rotate right Memory[x] for 2 times
   c=a+b
   M[003B]= c xor M[955E+i]
   d= rol(M[003B],4)
   M[001E+i] = d xor M[9576+i]

If we look back a little bet we notice that M[001E +i] is exactly the memory portion used by the instruction $8339 in order to decide if the function will return 0 (game over) or 1 (win).

So what we have to do? chose the password in order that after "password logic computation" the result will be 0.

To do this we develop a python script.

Before developing this script there is another important thing to notice. During the "computation" process there are 2 memory lookup that load some "magic" numbers into the computation process. These numbers must be extracted from the memory in order that we can use it inside our python script. The extraction is a simple step. After having paused the application with a breakpoint we can inspect the RAM and look for addresses: * $955E to $9575 * $9576 to $958D

Ok, now we have all we need to develop the "hacking_time_brute.py" script to bruteforce the password


# max bits > 0 == width of the value in bits (e.g., int_16 -> 16)
# Rotate left: 0b1001 --> 0b0011
rol = lambda val, r_bits, max_bits: \
    (val << r_bits%max_bits) & (2**max_bits-1) | \
    ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))

# Rotate right: 0b1001 --> 0b1100
ror = lambda val, r_bits, max_bits: \
    ((val & (2**max_bits-1)) >> r_bits%max_bits) | \
    (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))

max_bits = 8  # For fun, try 2, 17 or other arbitrary (positive!) values


#dumped from the memory manually
magic_955E = [ 0x70,0x30 ,0x53 ,0xA1,0xD3,0x70,0x3F,0x64,0xB3,0x16,0xE4,0x04,0x5F,0x3A,0xEE,0x42,0xB1,0xA1,0x37,0x15,0x6E,0x88,0x2A,0xAB]
magic_9576 = [ 0x20,0xAC,0x7A,0x25,0xD7,0x9C,0xC2,0x1D,0x58,0xD0,0x13,0x25,0x96,0x6A,0xDC,0x7E,0x2E,0xB4,0xB4,0x10,0xCB,0x1D,0xC2,0x66]

#password to test (from 0x30--> '0' to 0x5A --> 'Z')
passwd_guess = [0x30]*24
m_003B=0
result = [0x1]*24

good_password=[0x1]*24
found = False

#password computing algorithm
for y in range (0,len(passwd_guess)):
    found = False
    m_003B_back=m_003B
    while(found == False):
        m_003B=m_003B_back
        tmp= rol(passwd_guess[y],3,max_bits) 
        m_003B= ror(m_003B,2,max_bits)
        tmp_2= tmp+m_003B    
        m_003B= tmp_2 ^ magic_955E[y]
        tmp_2 = rol(m_003B,4,max_bits)
        result[y] = tmp_2 ^ magic_9576[y]
        if(result[y]==0x0):
           good_password[y]=passwd_guess[y]
           found=True           
        else:
           passwd_guess[y]=(passwd_guess[y]+0x1)

           if(passwd_guess[y] > 0x5A):
              print "Out of range! impossible password"
              exit(-1)


print "Lockdown password found ! "
passwd=""
for i in good_password:
    passwd+=str(unichr(i))

print passwd


hacking_time_brute.py

And here we go...we start the script and we wait for the password to be found !

flag

Boom ! the script returns the password to disable the "lockdown" inside the game and also to score the 200 pts of the challenge !