The customized CPU emulator and loader.
The challenge consists of two parts, exploiting chal.ormb
(user application) and exploiting orm
(emulator).
I only solved ORM-APP
during the competition.
This is a stack-based VM which is composed of 2 stacks (I denote them L
and R
), 1 register (I denote it V
) and memory. The instruction is only one byte encoded.
Bit 3~7 represents opcode. Bit 2 represents op1 stack type. Bit 0-1 represents op2 type (0: immediate number, 1: L
stack, 2: R
stack, 3: V
)
Therefore, 5 bits encode 32 types of instructions. I writed a disassembler objdump.py to dump the assembly code for chal.ormb
. Check asm.S for code. Note that it uses stack R
to store control flow related value (e.g., return address) and stack L
to store data flow related value (e.g., function parameter), thus increases difficulty of exploiting.
Common menu-based challenge, containing 6 segments.
[+] Wordlen: 8, Entry: 0x8080808080808000, Stacksize: 0x4000, Segnum: 6
[+] Segment addr: 0x1f10000, length: 0x1000, filelen: 0x1000, flag: 3
[+] Segment addr: 0x9000000000, length: 0x1000, filelen: 0x30, flag: 3
[+] Segment addr: 0x8000000000, length: 0x1000, filelen: 0x43f, flag: 4
[+] Segment addr: 0x9090909090909000, length: 0x1000, filelen: 0x1e0, flag: 1
[+] Segment addr: 0xa0a0a0a0a0a0a000, length: 0x2000, filelen: 0x14c8, flag: 3
[+] Segment addr: 0x8080808080808000, length: 0x1000, filelen: 0x66a, flag: 4
0x8000000000
and 0x8080808080808000
are 2 executable segments, which are library code and main logic accordingly.
The app contains 3 functions [A]dd project
, [S]how project
and [M]igrate project
.
When adding project, the code is like
808080808080805B: push R V
808080808080805C: push R 0xA0A0A0A0A0A0B000
8080808080808065: ldq R
8080808080808066: push L V
8080808080808067: pop L
8080808080808068: push L V
8080808080808069: push R 0x1
8080808080808072: add R V
8080808080808073: push L V
8080808080808074: stq L 0xA0A0A0A0A0A0B000
808080808080807D: pop L
808080808080807E: push L V
808080808080807F: push L V
8080808080808080: push L V
8080808080808081: push R 0xA0A0A0A0A0A0B008
808080808080808A: push R 0x8
8080808080808093: mul L R
8080808080808094: add R V
8080808080808095: push R V
8080808080808096: push R 0xA0A0A0A0A0A0B048
808080808080809F: push R 0x90
80808080808080A8: mul L R
80808080808080A9: add R V
80808080808080AA: push L V
80808080808080AB: pop R
80808080808080AC: stq L V
Thus we could infer that, 0xA0A0A0A0A0A0B000
is the number of projects, 0xA0A0A0A0A0A0B008
~0xA0A0A0A0A0A0B048
is the array of project pointer. Starting from 0xA0A0A0A0A0A0B048
, there is project data, 0x90
bytes for each.
Since there is no limitation on number of projects, when we add more than 8 projects, the pointer array would overflow into project data.
Note that in read-only segment (0x9090909090909000
), there is a string ====== CENSORED: FLAG LOCATED HERE. ======
. To exploit ORM-APP, we need to leak this string.
Trying several times, I found that after adding 8 projects then a migrate would trigger segmentation fault. Debugging shows we can corrupt stack R
which return addresses are stored, and there cannot be null-byte.
I didn't figure out what exact reason triggers vulnerability. It seems that migrate function calls strcpy
to copy project name onto stack R
. Because in normal it reads 7 bytes into 8-bytes buffer, there is a null-byte preventing overflow. However, I guess, when adding more than 8 projects, the name buffer would be overwritten by a buffer pointer (which is 0xA0A0A0A0...
with no null-byte). Thus strcpy
would also copy description
content onto stack and cause corruption of stack R
.
I chose to return to 80808080808084B6
which prints [+] Migrating
string (the original call only writes 14 bytes). As long as there is a huge value resides on stack L
, we could leak many bytes including flag string.
80808080808084AD: push L 0xE
80808080808084B6: push L 0x90909090909091A8 ; '[+] Migrating ====== CENSORED: F'
80808080808084BF: push L 0x1
80808080808084C8: call write
Here is full exploit.