CHIP 8 /1
We were given a web-based implementation of the chip8 emulator. The chip has
- 4KB memory (shared for code and data)
- 16 1B general purpose registers (V0-V15)
- a special 2B register (I) (which is used by display instructions)
- a program counter (PC)
- a 64×32-pixel monochrome display
- (it also has a stack, carry flag and some more registers).
The first 512B (0x000-0x1FF) are protected and should no be accessible by programs. The author mentioned it’s a TOP SECRET. Obviously, our flag is there.
Looking for some more info about chip8 on the web quickly leads to a useful and more detailed technical reference of the chip. In the display section of the reference, an interesting statement appears:
Programs may also refer to a group of sprites representing the hexadecimal digits 0 through F. These sprites are 5 bytes long, or 8×5 pixels. The data should be stored in the interpreter area of Chip-8 memory (0x000 to 0x1FF).
Apparently, the display instructions of the chip can access the protected memory area. These are the available display instructions:
Command | Type | C equivalent | Details |
---|---|---|---|
00E0 | Display | disp_clear() | disp_clear() |
DXYN | Display | draw(Vx,Vy,N) | Draws a sprite at coordinate (VX, VY) that has a width of 8 pixels and a height of N pixels. Each row of 8 pixels is read as bit-coded starting from memory location I; I value doesn’t change after the execution of this instruction. As described above, VF is set to 1 if any screen pixels are flipped from set to unset when the sprite is drawn, and to 0 if that doesn’t happen |
The DXYN
instruction will draw 8xN bits from memory address stored at register I to the screen. If we could set I to a low value, we could read from memory by drawing it to the screen. I can be affected by the following instructions:
Command | Type | C equivalent | Details |
---|---|---|---|
ANNN | MEM | I = NNN | Setts I to the address NNN |
FX1E | MEM | I += VX | Adds VX to I |
FX29 | MEM | I = sprite_addr[Vx] | Sets I to the location of the sprite for the character in VX. Characters 0-F (in hexadecimal) are represented by a 4×5 font stored in the first bytes of memory. |
Trying to set I to a low value using the first two instructions (ANNN
, FX1E
) will raise an access-violation error and halt the program. Though the third one, FX29
, looks much more promising (remembering the statement above which suggested that the actual location of these sprites is in the protected memory area).
As suspected, using FX29
(specifically, F229
while V2==0) set I to 0! (for V2==1, I will be set to 15, for V2==2, 30 and so on)
Using the DXYN
instruction (specifically D10F
while V1==V0==0) immediately after, ‘draws’ the first 8×15 bits in memory to the screen in a form of black (0) and white (1) colored pixels. Though the first 122 bytes only revealed the original chip’s stripes (graphical letters 0-F), starting at address 0x7E, our flag started to appear, each byte on a separate line.
The following chip8 code will dump the first 130B of the protected memory (including the entire flag) to the screen:
00E0 ; CLS F229 ; set I to sprite of V2 (0) D10F ; draw at coordinates(0,0) from memory at 0x00 7203 ; V2+=1 (1) 7105 ; V1+=5 (5) F229 ; set I to sprite of V2 (15) D10F ; draw at coordinates(5,0) from memory at 0x0F 7203 ; V2+=1 (2) 7105 ; V1+=5 (10) F229 ; set I to sprite of V2 (30) D10F ; draw at coordinates(10,0) from memory at 0x1E 7203 ; V2+=1 (3) 7105 ; V1+=5 (15) F229 ; set I to sprite of V2 (45) D10F ; draw at coordinates(15,0) from memory at 0x2D 7203 ; V2+=1 (3) 7105 ; V1+=5 (20) F229 ; set I to sprite of V2 (60) D10F ; draw at coordinates(20,0) from memory at 0x3C ; FLAG AREA 7203 ; V2+=1 (4) 7105 ; V1+=5 (25) F229 ; set I to sprite of V2 (75) D10F ; draw at coordinates(25,0) from memory at 0x4B 7203 ; V2+=1 (4) 7109 ; V1+=9 (34) F229 ; set I to sprite of V2 (90) D10F ; draw at coordinates(34,0) from memory at 0x5A 7203 ; V2+=1 (5) 7109 ; V1+=9 (43) F229 ; set I to sprite of V2 (105) D10F ; draw at coordinates(43,0) from memory at 0x69
Decoding the b/w pixels back to bits and translating to chars using an ASCII table revealed the flag:
01001000 0x48 'H' 01100001 0x61 'a' 01100011 0x63 'c' 01101011 0x6B 'k' 01010100 0x54 'T' 01001101 0x4D 'M' 01111011 0x7B '{' 01100001 0x61 'a' 00110101 0x35 '5' 00110101 0x35 '5' 01100101 0x65 'e' 01101101 0x6D 'm' 00111000 0x38 '8' 01101100 0x6C 'l' 01100101 0x65 'e' 01100100 0x64 'd' 01011111 0x5F '_' 01110011 0x73 's' 00110000 0x30 '0' 01100011 0x63 'c' 01101011 0x6B 'k' 01110011 0x73 's' 01111101 0x7D '}'
Flag: HackTM{a55em8led_s0cks}
CHIP 8 /2
Please first read CHIP 8 /1.
The second phase of the challenge was very similar to the first though this time, the last 512B in memory are also protected. The flag hides somewhere in 0xDFF-0xFFF.
Trying to set I to high values was not seem to be possible so we can not ‘draw’ the memory anymore or access it.
Digging around the available instructions looking for clues, the goto equivalent command (1NNN
) which affects the PC raised a question – could a code run in the protected high memory area?
Command | Type | C equivalent | Details |
---|---|---|---|
1NNN | Flow | goto NNN; | Jumps to address NNN. |
Trying to set the PC to 0xE00 1E00
seems to work. Upon a step/cycle, the chip will interpret the data there as an instruction, process it and increment PC by 2 (each instruction is 2B). The funny thing is, the emulator has a debug monitor which contains “Last instruction”. We can see the actual instruction which was just been processed after each step (ex. Last instruction 7279). We can read the memory 2B after 2B. If the actual data doesn’t represent a legal instruction, the program will halt, “Last instruction” will be blank but the error message will reveal the bytes (ex. Invalid instruction: 5F61). In such case, we can continue ‘reading’ by setting PC over to the next 2B using 1NNN
and stepping again.
The potential data appeared at location 0x1F40:
AA48 .H 6163 ac 6B54 kT 4D7B M{ 6436 d6 655F e_ 6A75 ju 7279 ry 5F61 _a 6E64 nd 5F33 _3 7833 x3 6375 cu 7431 ti 6F6E on 7DAA }.
We have the string HackTM{d6e_jury_and_3x3cution} in memory but trying to submit this as a flag fails. Re-checking memory / translating to ASCII again produced the same wrong string. What?
It took a few more minutes to realize we may be missing ‘ju’ to complete the word ‘judge’. When searching for ‘dge_jury_and_execution’ in Google, it immediately suggest a correction – “judge jury and executioner”.
Flag: HackTM{jud6e_jury_and_3x3cution}