Skip to main content
  1. CTF Writeups/
  2. FortID CTF 2025/

Rev From the Past

·
Rev 100pts 118 Solves Dosbox 8086
subzcuber
Author
subzcuber
i like to imagine i’m funny
Table of Contents

Last year you’ve enjoyed pwning a binary from the MS-DOS era.

This year, we challenge you to reverse engineer one such binary.


you’re given CHAL.COM which from the hint in the description is an MS-DOS binary for the 8086 processor.

Set up the environment
#

  1. install dosbox
sudo pacman -S dosbox
  1. install borlandc++ (comes with turbo debugger)

i just followed this amazing guide but without the editor (i did try but i couldn’t the source anymore)

Learn 8086 assembly
#

  1. check out these resources

The runtime memory prefixes an extra 0x100 bytes to the binary to include runtime information. This is important because every index you see in the next step is going to offset by 0x100 in the original binary, and because information like command line arguments and their length is stored in this space. Namely the length of the cli argument is stored at 0x80

8086 has 4 main registers

  • ax, bx, cx and dx which are all 16 bit.
  • There are other general purpose and special purpose ones too

It also has a lot of weird/cool instructions, notably

  • xlatb: Translate byte from table. this is just one instruction crazy
  • loop: made simple loops nice and conscise
  • lodsb: get value from an index and autoincrement the index
  • scasb: compare value at 2 indices and autoincrement them
  • and a bunch more that i would need to open the disassembly again to remember

i’m halfassing this section but the above resources just make it so simple already

Understand the code
#

  1. spend hours understanding the code
  • i thought it was rc4 first so i spent a long time scouring the assembly looking for the key, but it wasn’t standard rc4, just similar.
  • eventually i realised i can view the data dump in Turbo Debugger and that helped me move ahead

What actually happens
#

  1. so what the binary does is initially validate your command line input
  • it looks for FortID{ then a 31 (0x21 - 2) length string then ends with } (the whitespace is important)
  1. then it enters a function to init the state matrix
  • First it creates an array of 255 characters all initialised to their index value
  • Then based on a fixed value in the file that it refreshes by xoring, it swaps keeps swapping characters in the array
  • so you get a sort of random array by the end (not anymore since clearly we can reconstruct it, the fixed value isn’t random lol)
00000140  72 65 63 74 21 0d 0a 24  0d 0a 4e 6f 70 65 2e 0d  |rect!..$..Nope..|
00000150  0a 24 00 00 c1 b4 00 00  00 00 00 00 00 00 00 00  |.$..............|
00000160  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

The fixed value is taken from address 0x254-5 which is also copied to 0x252-3 and 0x256-7 to give 0xb4c1

Once all this is set up we enter the main logic

  1. We first create our correct ciphertext

This is done by taken some bytes from the tail of our file and xoring them with [0x252] ^ 0xa5 which is 0xc1 ^ 0xa5 = 0x64

00000270  00 00 00 00 00 00 00 00  00 42 45 4e 43 21 2a 35  |.........BENC!*5|
00000280  a9 11 e3 6f 17 79 11 e3  79 88 94 b2 01 fd 68 11  |...o.y..y.....h.|
00000290  6f 01 b7 11 ac 6f 53 01  ce e2 11 84 35 35 51     |o....oS.....55Q|

Once we have this ciphertext the way we check is by taking every byte in our submitted flag, going to that index in the statematrix created in step 2, and comparing that with the expected ciphertext

  1. ATTACK
  • now that we have the statematrix and the final ciphertext we can simply get the correct flag by reversing the substitution

Solve script
#

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
# create state matrix
state = []
for i in range(0x100):
    state.append(i) # init

ax = 0xb4c1 # this is take from a fixed location in the file
cx = 0xff # just a counter
for i in range(0xff):
    # on the basis of this fixed number decided to xor or not
    cmp = ax & 0x01
    ax = ax >> 1
    if cmp == 0x01:
        ax ^= 0xb400
    # swap ax (mod counter) with counter
    dx = ax % (cx + 1)
    ah = state[cx]
    al = state[dx]
    state[dx] = ah
    state[cx] = al
    cx = cx - 1

# get final bytes to compare
b = 0x64 # 0xc1 ^ 0xa5 where c1 = [252]

# this comes from the tail of the file
# i could extract it in a better way but meh
c_37e = [0x2a, 0x35, 0xa9, 0x11, 0xe3, 0x6f, 0x17, 0x79, 0x11, 0xe3, 0x79, 0x88, 0x94, 0xb2, 0x01, 0xfd, 0x68, 0x11, 0x6f, 0x01, 0xb7, 0x11, 0xac, 0x6f, 0x53, 0x01, 0xce, 0xe2, 0x11, 0x84, 0x35, 0x35, 0x51]
cb_res = []
for i in c_37e:
    cb_res.append(i ^ b)

# bruteforce for flag?
for i in cb_res:
    if i in state:
        print(chr(state.index(i)), end='')
    else:
        print("not in state??")

FortID{N0w_S4v3_S3t71ng5_4nd_L4unch_D00M}

this here is a nice writeup too which also contains screenshots of the debugger which i am not including but i think look cool and would like you to see. They also include the download instructions which is nice

Reply by Email

Related

PlasticShield
Rev Aes Hash
Its Locked
Rev 453pt 98 Solves Perl Obfuscation
this was fun
Deflation Gangster
Rev 364pt 166 Solves
Blue Magic