Hacking Time (reverse 200 pts)
At challenge start we have the HackingTime_03e852ace386388eb88c39a02f88c773.nes file (which we name ht.nes for simplicity).
The first reaction is to analyze the nature of the file
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"
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
After following the game messages we land on the "lockdown stage"
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"
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.
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
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:
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:
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
So we retry but this time we modify the "Z" flag just before the "CMP" instruction at $8224.
after this modification we continue the execution and Magic ! We have a success.
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
And here we go...we start the script and we wait for the password to be found !
Boom ! the script returns the password to disable the "lockdown" inside the game and also to score the 200 pts of the challenge !