HACKvent 2020 - Day 14

01-01-2021 - 4 minutes, 36 seconds - CTF Reverse Engineering

Challenge - Santa's Special GIFt

Today, you got a strange GIFt from Santa:

Image of a small pixelated metal file

You are unsure what it is for. You do happen to have some wood lying around, but the tool seems to be made for metal. You notice how it has a rather strange size. You could use it for your fingernails, perhaps? If you keep looking, you might see some other uses…


First thing was to check the image for the usual steganography and hidden data. With such a small image I did not have much hope. binwalk got me nothing and also visual inspection of the pixels was a miss. But in my hex editor, appened to the image, i saw the ROT13 ciphtertext uvag:--xrrc-tbvat which reads hint:--keep-going.

Not exactly sure what to think I went further through the few remaining bytes in my hex editor when my eyes fixated on the last two: 55 AA. I have seen this sequence! The list of file magics i consulted didn’t have it, in neither endianess. So I wanted to go down the road of network protocols because thats where I suspected to have seen it. Luckily Daniel's list was better. Turns our its the magic of a master boot record. Indeed the file is exactly 512 long and thus fits perfectly into a sector.

I quickly dug out Bochs, as I remembered using it when I experimented with bootloaders a few years back. Daniel disassembled the file with an online disassembler but it failed halfway trough. dosbox + ndisasm got us the full source. Since we are both not particularly experienced with assember and the code is mixed with bogus instruction that resulted from the GIF data, we decided to better run it first.

Bochs did a great job and we got our first result: half a QR code. So we probably had to patch it to get the full QR. Good that bochs comes with a debugger. I set a breakpoint to 0x7c00 (lbreak 0x7c00) since this is where the BIOS loads the MBR to and executes it. Then I single-stepped through the code, hoping to see something on the screen, but got nothing. Time to look for better tools. And indeed, i found out that bochs has a kind of hidden graphical debugger as well, making the job easier. You just have to set an option in your bochsrc.bxrc config: display_library: win32,options="gui_debug".

Once in the graphical debugger I realized that I could just set breakpoints to the BIOS interrupts since they will be used to print on the screen. It also takes me right to the location of the most real code, and not some GIF junk. So I searched for all int 0x10 instructions in the disassembly, calculated the memory adresses and set a breakpoint for each. Then I hopped (cont) through the code interrupt by interrupt.

The breakpoints

lbreak 0x7C35
lbreak 0x7C44
lbreak 0x7C4D
lbreak 0x7C66
lbreak 0x7C6A
lbreak 0x7C71
lbreak 0x7C90
lbreak 0x7C9A
Image of Bochs GUI debugger
Image of Bochs GUI debugger.

Turns out this was the key as I saw something on the screen. No QR code, but a secret flag! Normally it would have been shown only very very briefly. This was the code around the interrupt at 0x7c35.


Continuing further then printed the half of the QR. Now we had to figure out how to make the program printing the whole.

A bit later we had it, int on 0x7C66 and 0x7C6A set up the line, 0x7C71 prints the spaces to center the QR in the middle of the screen and int 0x10 on 0x7C90 is used to print the QR itself. jnz 0x57 on 0x7c94 ends the horizontal iteration and cmp si,0xe0 on 0x7c5b is the premature halting condition that prevents the full QR from showing.

Listing of the relevant code

(Please excude the wrong syntax highlighting. The highlighter doesn't work with the address and opcode listing.)

< ... >
00000057  85FF              test di,di      ; begin QR loop
00000059  751E              jnz 0x79
0000005B  81FEE000          cmp si,0xe0     ; premature halting condition
0000005F  7502              jnz 0x63
00000061  FA                cli
00000062  F4                hlt             ; premature halt
00000063  B80D0E            mov ax,0xe0d
00000066  CD10              int 0x10        ; line feed, color, etc?
00000068  B00A              mov al,0xa
0000006A  CD10              int 0x10        ; line feed, color, etc?
0000006C  B91B00            mov cx,0x1b     ; init loop counter
0000006F  B020              mov al,0x20     ; begin loop, load space
00000071  CD10              int 0x10        ; print spaces to center QR
00000073  49                dec cx
00000074  75F9              jnz 0x6f        ; end loop
00000076  BF1900            mov di,0x19
00000079  89F1              mov cx,si
0000007B  21D1              and cx,dx
0000007D  01C9              add cx,cx
0000007F  89F3              mov bx,si
00000081  C1EB02            shr bx,byte 0x2
00000084  8BAF9E7C          mov bp,[bx+0x7c9e]
00000088  D3ED              shr bp,cl
0000008A  21D5              and bp,dx
0000008C  8A86F07C          mov al,[bp+0x7cf0]
00000090  CD10              int 0x10        ; print QR
00000092  4F                dec di
00000093  4E                dec si
00000094  75C1              jnz 0x57        ; end QR loop / done with the line?
00000096  B401              mov ah,0x1
00000098  B53F              mov ch,0x3f
0000009A  CD10              int 0x10
0000009C  FA                cli
0000009D  F4                hlt             ; the actual halt
< ... >

Only thing left to do now, was to either patch the code (for example cmp 0xe0 to cmp 0xff) or change esi temporarily to never hit the condition. Both ways worked fine, and we got the QR code that contained the flag. While this write-up is quite long and detailed, finding both flags took us only one and a half hour.

Image of the full QR code, in the bochs debugger window
I circumvented the premature cmp with the debugger and continued to the real end, having the full QR shown.

Next Post Previous Post