This is actually a challenge from the qualifiers, but because this challenge will be used in the Finals again so we weren’t allowed to post about it until after the finals. More on that later, here’s the writeup.
LSCVM: Immaculate Invasion
DESCRIPTION
During our recon on the notorious fools of LightSpeedCorp, we have discovered this service which runs on a really simple, tiny, trivial, virtual machine that they have created. We are not sure of its purpose, but it certainly looks fun to play with.
lscvm-ii.cddc19q.ctf.sg 9001
IMPORTANT: We highly recommend you to fully understand this challenge as we will use this VM again in the Final.
Solution
Looking at the strings in the binary, it looks like we are dealing with a Stack based vm, and those long strings are probably the instructions.
If we try running it we’ll get an error.
cddc/re/LSCVM ➜ ./lscvm-ii
[-] Flag file open error: No such file or directory
We find the code that is throwing the error and we can see it is trying to read a file called flag.
Let’s create a file called flag to fix this error.
echo 'CTF{flag}' > flag
Now when we run it we can see that it asks us for an ID which we have to figure out.
cddc/re/LSCVM ➜ ./lscvm-ii
=== Welcome to LSCVM(LightSpeed Corp Virtual Machine) ===
ID : 1
[-] Wrong id
After doing some analysis, I figured out that if argc is 2 the program will print out ‘debug’ information.
cddc/re/LSCVM ➜ ./lscvm-ii a
@0 c [ 02 ]
@1 f [ 02 05 ]
@2 M [ 0a ]
@3 c [ 0a 02 ]
@4 f [ 0a 02 05 ]
@5 M [ 0a 0a ]
@6 h [ 0a 0a 07 ]
@7 i [ 0a 0a 07 08 ]
@8 M [ 0a 0a 38 ]
@9 f [ 0a 0a 38 05 ]
@10 A [ 0a 0a 3d ]
.
.
.
The information can be interpreted this way.
Instruction pointer | instruction | output | Stack after running instruction |
---|---|---|---|
@0 | c | [ 02 ] | |
@1 | f | [ 02 05 ] | |
@2 | M | [ 0a ] |
Using the debug output and static analysis, I was able to recover the whole instruction set.
Opcode (Hex) | Opcode (Char) | Assmebly | Comment |
---|---|---|---|
Default | NIL | nop | no operation, waste cycle |
0x0a | \n | nop | no operation, waste cycle |
0x20 | \s | nop | no operation, waste cycle |
0x41 | A | add | Pop 2 values from stack and push the addition |
0x42 | B | hlt | Stop executing code |
0x43 | C | jmp | Pop and jump to value |
0x44 | D | pop | Pop a value and do nothing |
0x45 | E | read | Pop addr and push val. 0 <= addr <= 0x3fff |
0x46 | F | sclone | pop value n and (clone) push n+1 th previous stack value |
0x47 | G | ipadd | Pop value and add it to IP (Instruction Pointer) |
0x48 | H | sshift | pop value n and shift n+1 th previous stack value to the top of the stack |
0x49 | I | pint | Pop value and Print as int |
0x4a | J | cmp | pop 2 values and push 0 if equal, 1 if first pop is smaller else -1. 0 |
0x4b | K | write | first pop is addr, 2nd pop is value to write |
0x4d | M | mul | Pop 2 values from stack and push the multiplication |
0x50 | P | pchar | Pops value from stack and print as char |
0x52 | R | rjmp | Jump to previous jump location, cant do twice in a row, because it consumes the previous jump location. |
0x53 | S | sub | subtract 1st pop from 2nd pop push result |
0x56 | V | div | Divide (floor) 2nd pop by 1st pop and push result |
0x5a | Z | ipcadd | conditional add to IP, if 2nd pop is 0, add 1st pop to IP |
0x61 | a | p0 | Push 0x00 to stack |
0x62 | b | p1 | Push 0x01 to stack |
0x63 | c | p2 | Push 0x02 to stack |
0x64 | d | p3 | Push 0x03 to stack |
0x65 | e | p4 | Push 0x04 to stack |
0x66 | f | p5 | Push 0x05 to stack |
0x67 | g | p6 | Push 0x06 to stack |
0x68 | h | p7 | Push 0x07 to stack |
0x69 | i | p8 | Push 0x08 to stack |
0x6a | j | p9 | Push 0x09 to stack |
The input we are giving the program is actually being executed by the vm as code.
To get the flag, we have to provide the vm with code that will write lsc_user
to the vm memory, and then provide another code that will write hi_darkspeed-corp!
to the vm memory.
Using the instruction set recovered I wrote this script to generate the vm code required to write the strings to memory and submit it to the service.
#!/usr/bin/env python3
import socket
nums = {
0x00:'a',
0x01:'b',
0x02:'c',
0x03:'d',
0x04:'e',
0x05:'f',
0x06:'g',
0x07:'h',
0x08:'i',
0x09:'j',
0x0a:'jbA',
0x0b:'jcA',
0x0c:'jdA',
0x0d:'jeA',
0x0e:'jfA',
0x0f:'jgA',
0x10:'jhA',
0x11:'jiA',
0x12:'jjA',
0x21:'eiMbA',
0x2d:'jfM',
0x5f:'jjMchMA',
0x61:'jjMcjMAcS',
0x63:'jjMcjMA',
0x64:'jjMcjMAbA',
0x65:'jjMcjMAcA',
0x68:'jjMdhMcAA',
0x69:'jjMdhMdAA',
0x6b:'jjMdjMAbS',
0x6c:'jjMdjMA',
0x6f:'jjMdjMAdA',
0x70:'jjMfhMeSA',
0x72:'jjMfhMcSA',
0x73:'jjMfhMbSA',
0x75:'jjMfhMbAA'}
def write_str(target):
output = ''
for i in range(len(target)):
output += nums[ord(target[i])] + nums[i]
return output + 'K' * len(target)
id_code = write_str('lsc_user')
pass_code = write_str('hi_darkspeed-corp!')
print('ID: {}\nPassword: {}\n'.format(id_code, pass_code))
print('Connecting to service...')
client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('lscvm-ii.cddc19q.ctf.sg',9001))
client.sendall('{}\n'.format(id_code).encode())
client.sendall('{}\n'.format(pass_code).encode())
while True:
msg = client.recv(1024).decode()
if msg:
print(msg)
if '$CDDC19${' in msg:
client.close()
break
cddc/re/LSCVM ➜ ./solve.py
ID: jjMdjMAajjMfhMbSAbjjMcjMAcjjMchMAdjjMfhMbAAejjMfhMbSAfjjMcjMAcAgjjMfhMcSAhKKKKKKKK
Password: jjMdhMcAAajjMdhMdAAbjjMchMAcjjMcjMAbAdjjMcjMAcSejjMfhMcSAfjjMdjMAbSgjjMfhMbSAhjjMfhMeSA
ijjMcjMAcAjjjMcjMAcAjbAjjMcjMAbAjcAjfMjdAjjMcjMAjeAjjMdjMAdAjfAjjMfhMcSAjgAjjMfhMeSAjhAeiMbAjiAKK
KKKKKKKKKKKKKKKK
Connecting to service...
=== Welcome to LSCVM(LightSpeed Corp Virtual Machine) ===
ID : Password :
Login Successful! $CDDC19${IcY_GrE37ings_Fr0M_LigHT5pEeDC0Rp}
lsc_user, Good Bye!
Flag
$CDDC19${IcY_GrE37ings_Fr0M_LigHT5pEeDC0Rp}
Rant
The organizers stated in the qualifiers LSCVM challenges that it is important to fully understand the VM as it will be used again in the Finals.
IMPORTANT: We highly recommend you to fully understand this challenge as we will use this VM again in the Final.
The way they brought this accross made me think that it will play a big part in the finals, so I built a tool for the purpose of working on LSCVM challenge(s) during the Finals. However LSCVM was not a big part of the Finals and I didn’t get to use my tool at all.
The finals also had Rings where you have to solve a certain number of challenges to unlock the next ring. Personally I don’t think the rings concept is a good idea, because what if it just so happens the participant can’t solve the starting challenges but they can solve the ones in the next or next next ring? I would’ve preferred it if the challenges were all available at the start.
I understand that it was implemented to limit the number of teams that can attempt the hardware challenges because they don’t have enought equipment for everyone to attempt at the same time, but this can also be done by only unlocking the hardware challenges once a team reach a certain number of points/solves, instead of implementing Rings.
There’s not gonna be any writeup for the finals challenges cause I did everything on the provided laptop and didn’t transfer the files out.