|
| 1 | +# Veighty Machinery |
| 2 | + |
| 3 | +This service is a bytecode runner. |
| 4 | + |
| 5 | +``` |
| 6 | +Go program your |
| 7 | + __ |
| 8 | + / \ |
| 9 | + .-. | | |
| 10 | + * _.-' \ \__/ |
| 11 | + \.-' \ |
| 12 | + / _/ |
| 13 | + | _ /" |
| 14 | + | /_\' |
| 15 | + \ \_/ |
| 16 | + """" |
| 17 | +Give me your bytecode! |
| 18 | +I will load the cannon and execute it. |
| 19 | +``` |
| 20 | + |
| 21 | +## Reverse Engineering |
| 22 | + |
| 23 | +After reverse engineering the service, we find that the bytecode language is a stack-based machine with immediates and strings. The vm_context structure used by the service is shown below: |
| 24 | + |
| 25 | +```C |
| 26 | +struct vm_context |
| 27 | +{ |
| 28 | + __int64 _ip; |
| 29 | + __int64 _sp; |
| 30 | + __int64 stack[0x1000]; |
| 31 | + char code[0x1000]; |
| 32 | + int flags; |
| 33 | + int code_length; |
| 34 | +}; |
| 35 | +``` |
| 36 | + |
| 37 | +There are 0x20 opcodes in the language. These are: |
| 38 | + |
| 39 | +``` |
| 40 | +nop |
| 41 | +push_mem |
| 42 | +pop_mem |
| 43 | +push_input |
| 44 | +add2_stack |
| 45 | +sub2_stack |
| 46 | +multiply_stack |
| 47 | +divide2_stack |
| 48 | +modulus2_stack |
| 49 | +snprintf_imm |
| 50 | +branch |
| 51 | +cmp_geq |
| 52 | +cmp_leq |
| 53 | +cmp_neq |
| 54 | +branch_zero |
| 55 | +branch_notzero |
| 56 | +inc |
| 57 | +dec |
| 58 | +multiple_by_2 |
| 59 | +rshift_1 |
| 60 | +duplicate_top_stack |
| 61 | +swap2_stack |
| 62 | +alloc_buf_push_ptr_stack |
| 63 | +free_buffer |
| 64 | +fgets_malloc_push_ptr_stack |
| 65 | +concat_strings |
| 66 | +free_push_length |
| 67 | +atoi_free_stack |
| 68 | +do_strcmp |
| 69 | +strstr_stack |
| 70 | +fopen_write_stack |
| 71 | +fread_malloc_stack |
| 72 | +``` |
| 73 | + |
| 74 | +## Vulnerability Searching |
| 75 | + |
| 76 | +Both string pointers and immediates are stored on the stack. However, pointers are tagged with (1 << 63) to differentiate them from immediates, and every opcode correctly checks whether the operation is acting upon immediates or pointers. I could not find a type confusion vulnerability here. |
| 77 | + |
| 78 | +The vulnerability I found (and the intended one) is in swap2_stack. This opcode swaps the topmost two values on the stack, regardless of whether it's an immediate or pointer. However, it fails to check that there are two or more values on the stack - it only checks that there are 1 or more values. This vulnerability allows us to swap the top-most stack value with the stack pointer, which is right before the stack. |
| 79 | + |
| 80 | +## Exploitation |
| 81 | + |
| 82 | +Using the vulnerability, we can point the stack anywhere relative to the vm_context struct, which exists on the heap. Additionally, the opcodes do not check that the stack pointer points out of range (> 0x1000), but instead checks equivalence to 0x1000. This allows us to use all opcodes even when the stack pointer is out of range. With the push and pop operations, we effectively get arbitrary read and write on the heap. |
| 83 | + |
| 84 | +We can leak a libc pointer through large bins or unsorted bins, and obtain arbitrary write by overwriting tcache freelist FD. I set up the following chunk structure: |
| 85 | + |
| 86 | +``` |
| 87 | +========================== |
| 88 | +vm_context |
| 89 | +========================== |
| 90 | +A - 0x10 sized chunk |
| 91 | +========================== |
| 92 | +B - 0x500 sized chunk |
| 93 | +========================== |
| 94 | +C - 0x10 sized chunk |
| 95 | +========================== |
| 96 | +Top |
| 97 | +========================== |
| 98 | +``` |
| 99 | + |
| 100 | +By freeing A, B, and C then pointing the stack pointer to B using the vulnerability, we can use the pop operation and get a libc leak. |
| 101 | + |
| 102 | +Then, we can continue pop-ing the stack pointer until it reaches the freelist FD pointer of chunk A. We then use the push operation to push the address of `__free_hook` onto the tcache freelist. |
| 103 | + |
| 104 | +We then allocate twice from tcache's 0x10 bin, and get a chunk over `__free_hook`. |
| 105 | + |
| 106 | +Actually if we write to `__free_hook - 0x8`, and write the data "/bin/sh;" + p64(system), we can free the chunk that was just allocated and get a shell immediately. |
| 107 | + |
| 108 | +The exploit is in `solve.py` |
0 commit comments