|
| 1 | + |
| 2 | +# llscript |
| 3 | +## What is it? |
| 4 | +- A basic custom low level scripting language, with a run-time-environment that can be injected as Shellcode. |
| 5 | +- The compiled bytecode can simply be appended to the run-time-environment shellcode to be executed when injected. |
| 6 | +- Includes a compiler (written in C#), a command line debugger (with custom debug information format) and a shellcode executer. |
| 7 | +- Can also be used as a very small, easily integratable, embedded-friendly run-time-environment for scripts that needs low level access. |
| 8 | +- Currently only works with Windows x64, but shouldn't be particularly hard to port. |
| 9 | + |
| 10 | +## What can it do? |
| 11 | +- Load arbitrary DLLs and extract symbols. |
| 12 | +- Call C functions loaded from those DLLs |
| 13 | +- Basic programming stuff. |
| 14 | + |
| 15 | +## What can't it do? |
| 16 | +- More advanced programming stuff that isn't absolutely essential. |
| 17 | +- It can't even do `for` loops, only `while`, no `structs` etc. but the compiler is very hackable. |
| 18 | +- Oh, and the shellcode isn't null free. One could hack the compiler to output null free byte code and spend the rest of eternity writing a null free interpreter, but that's not the point of this project at the moment. |
| 19 | + |
| 20 | +## How to use it for shellcode? |
| 21 | +### Step 1: Write Your Script |
| 22 | +This is the example script. It only opens a `MessageBoxA`. But demonstrates how to load symbols from a DLL. |
| 23 | +```c++ |
| 24 | +const text kernel32dll = "User32.dll"; // `text` maps to `ptr<i8>`. |
| 25 | +const text messageBoxA = "MessageBoxA"; |
| 26 | + |
| 27 | +// `load_library` is provided by the compiler. |
| 28 | +// other builtin functions include `alloc`, `free`, `realloc`, `get_proc_address`. |
| 29 | +voidptr kernel32dll_handle = load_library(kernel32dll); // `voidptr` maps to `ptr<void>`. |
| 30 | +voidptr messageBoxAAddr = get_proc_address(kernel32dll_handle, messageBoxA); |
| 31 | + |
| 32 | +// this is how casts and pointers to external function work. |
| 33 | +extern_func<i32 (const voidptr, const text, const text, u32)> messageBoxAFunc = |
| 34 | + cast<extern_func<i32 (const voidptr, const text, const text, u32)>>(messageBoxAAddr); |
| 35 | + |
| 36 | +messageBoxAFunc(null, "Hello from the other side!", "Very Important Message", 0); |
| 37 | +``` |
| 38 | +
|
| 39 | +### Step 2: Compile the Script. |
| 40 | +``` |
| 41 | +> llsc example.lls |
| 42 | +llsc - LLS Bytecode Compiler (Build Version: 1.0.8566.36389) |
| 43 | + |
| 44 | +Parsing Succeeded. (88 Nodes parsed from 1 Files) |
| 45 | + |
| 46 | +Warning (in 'example.lls', Line 13): |
| 47 | + lvalue call to 'extern_func<i32 (const ptr<void>, const ptr<const i8>, const ptr<const i8>, u32)> messageBoxAFunc' will discard the return value of type 'i32'. |
| 48 | + |
| 49 | +Instruction Generation Succeeded. (69 Instructions & Pseudo-Instructions generated.) |
| 50 | +Code Generation Succeeded. (393 Bytes) |
| 51 | +Successfully wrote byte code to 'bytecode.lls'. |
| 52 | + |
| 53 | +Compilation Succeeded. |
| 54 | +``` |
| 55 | +
|
| 56 | +### Step 3: Append the Bytecode to the Run-Time-Environment Shellcode |
| 57 | +- Open the compiler output file `bytecode.lls` and the run-time-environment shellcode `script_host.bin` in a hex editor like [HxD](https://mh-nexus.de/en/hxd/). |
| 58 | +- Create a new hex-file and first paste in the contents of `script_host.bin`. This section should end with the magic constant `37 6F 63 03 12 9E 71 31`. |
| 59 | +- Now paste in the contents of `bytecode.lls`. These should usually begin with `0E` (which is the op code `LLS_OP_STACK_INC_IMM`). |
| 60 | +- Save the file. |
| 61 | +
|
| 62 | +### Step 4: Test the shellcode |
| 63 | +Use `runsc` to test your shellcode. |
| 64 | +``` |
| 65 | +runsc <YourShellcodeFileName> |
| 66 | +``` |
| 67 | +
|
| 68 | +## How to debug scripts? |
| 69 | +Debugging such a hacked runtime environment is obviously not as easy as your normal programming languages, but there's a command line (low level) debugger. |
| 70 | +
|
| 71 | +### Step 1: Compile with Debug-Info |
| 72 | +``` |
| 73 | +> llsc example.lls -dbgdb |
| 74 | +llsc - LLS Bytecode Compiler (Build Version: 1.0.8566.36389) |
| 75 | + |
| 76 | +Parsing Succeeded. (88 Nodes parsed from 1 Files) |
| 77 | + |
| 78 | +Warning (in 'example.lls', Line 13): |
| 79 | + lvalue call to 'extern_func<i32 (const ptr<void>, const ptr<const i8>, const ptr<const i8>, u32)> messageBoxAFunc' will discard the return value of type 'i32'. |
| 80 | + |
| 81 | +Instruction Generation Succeeded. (69 Instructions & Pseudo-Instructions generated.) |
| 82 | +Code Generation Succeeded. (393 Bytes) |
| 83 | +Successfully wrote byte code to 'bytecode.lls'. |
| 84 | +Successfully wrote debug database to 'bytecode.lls.dbg'. |
| 85 | + |
| 86 | +Compilation Succeeded. |
| 87 | +``` |
| 88 | +
|
| 89 | +### Step 2: Launch the Command Line Debugger |
| 90 | +``` |
| 91 | +> llscript_dbg bytecode.lls bytecode.lls.dbg |
| 92 | +llshost byte code interpreter |
| 93 | + |
| 94 | + 'c' to run / continue execution |
| 95 | + 'n' to step |
| 96 | + 'l' to step a line (only available with debug info) |
| 97 | + 'f' to step out |
| 98 | + 'b' to set the breakpoint |
| 99 | + 'r' for registers |
| 100 | + 'p' for stack bytes |
| 101 | + 'y' for advanced stack bytes |
| 102 | + 'i' to inspect a value |
| 103 | + 'm' to modify a value |
| 104 | + 'v' show recent values (only available with debug info) |
| 105 | + 'o' clear recent values (only available with debug info) |
| 106 | + 'w' set value filter (only available with debug info) |
| 107 | + 'W' break on a value filter match (only available with debug info) |
| 108 | + 'F' continue to next function call/return |
| 109 | + 's' toggle silent |
| 110 | + 'S' toggle silent comments |
| 111 | + 'q' to restart |
| 112 | + 'x' to quit |
| 113 | + 'z' to debug break |
| 114 | + |
| 115 | + |
| 116 | +File: example.lls |
| 117 | + 1: const text kernel32dll = "User32.dll"; // `text` maps to `ptr<i8>`. |
| 118 | +>> |
| 119 | +``` |
| 120 | +
|
| 121 | +Now press <kbd>l</kbd> to step line by line, <kbd>c</kbd> to run. |
| 122 | +Recently modified values and associated lines in the script will be displayed above the input line if debug information is available. |
| 123 | +
|
| 124 | +``` |
| 125 | +kernel32dll @ code base offset 320 (array<i8>) : 0x7FF50BC00140 |
| 126 | + --> 85, 115, 101, 114, 51, 50, 46, 100, 108, 108, 0, 77, 101, 115, 115, 97, 103, 101, 66, 111, 120, 65, 0, 72, ... |
| 127 | + --> 0x55, 0x73, 0x65, 0x72, 0x33, 0x32, 0x2E, 0x64, 0x6C, 0x6C, 0x0, 0x4D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6F, 0x78, 0x41, 0x0, 0x48, ... |
| 128 | + --> "User32.dll" |
| 129 | +messageBoxA @ code base offset 331 (array<i8>) : 0x7FF50BC0014B |
| 130 | + --> 77, 101, 115, 115, 97, 103, 101, 66, 111, 120, 65, 0, 72, 101, 108, 108, 111, 32, 102, 114, 111, 109, 32, 116, ... |
| 131 | + --> 0x4D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6F, 0x78, 0x41, 0x0, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x74, ... |
| 132 | + --> "MessageBoxA" |
| 133 | + 6: voidptr kernel32dll_handle = load_library(kernel32dll); // `voidptr` maps to `ptr<void>`. |
| 134 | +``` |
| 135 | +
|
| 136 | +## How to integrate it with another application? |
| 137 | +Simply include the corresponding header, link to `script_host.lib` and start the run-time-environment with a pointer to the bytecode. |
| 138 | +
|
| 139 | +```c++ |
| 140 | +llshost_state_t state = {}; |
| 141 | +state.pCode = your_compiled_byte_code; |
| 142 | +
|
| 143 | +// If you need to pass in additional values, simply set register values: |
| 144 | +state.registerValues[0] = (uint64_t)value0; |
| 145 | +state.registerValues[1] = (uint64_t)value1; |
| 146 | +
|
| 147 | +// Now, start the runtime-environment. |
| 148 | +llshost_from_state(&state); |
| 149 | +``` |
| 150 | + |
| 151 | +## How to build it? |
| 152 | +- Clone the repo `git clone https://github.com/rainerzufalldererste/llscript.git` |
| 153 | +- Run `create_project.bat`, select `Visual Studio 2015` if you're trying to build the script host for shellcode (evenything newer will produce calls to `memcpy` even without the `crt`. If you're just planning to embed the script host in another application, any new Visual Studio version is fine. |
| 154 | + |
| 155 | +If you just want to play with the debugger or compiler, you can simply use `MSBuild` or `Visual Studio` to build the project solution. |
| 156 | + |
| 157 | +If you want to create a shellcode version of a modified run-time-environment: |
| 158 | + |
| 159 | +- Build `llscript_asm` then `llscript_host` then `llscript_host_bin`. (Visual Studio sometimes gets confused about `llscript_asm`, so not relying on dependencies is probably a good idea) |
| 160 | +- Extract the `.code` section via some dumping tool or simply open the binary in IDA, go to the last statement of the last symbol, and select everything upwards in the hex view, then paste that into a hex editor. |
| 161 | +- Now we need to patch the assembly, because getting the current `rip` isn't something that can be expressed in a position independent or overly complicated way in msvc afaik, so we'll need to replace the assembly generated for `uint8_t *pCode = __readgsqword(0);` with `lea <whatever register the compiler chose>, [rip]`. |
| 162 | +-- if the register was `rax`, replace `65 48 8B 04 25 00 00 00 00` (`mov rax,qword ptr gs:[0]`) with `48 8D 05 00 00 00 00 90 90`. |
| 163 | +-- if the register was `rax`, replace `65 48 8B 0C 25 00 00 00 00` (`mov rcx,qword ptr gs:[0]`) with `48 8D 0D 00 00 00 00 90 90`. |
| 164 | +-- if the register was anything else, use the [defuse.ca online x64 assembler](https://defuse.ca/online-x86-assembler.htm) and assemble `lea <whatever register the compiler chose>, [rip]` and replace the corresponding code with whatever that says. Pad with `0x90` (`nop`). |
| 165 | +- Lastly, append the magic constant (`37 6F 63 03 12 9E 71 31`) to the shellcode. The runtime-environment will search for this pattern when launched without code to find it's input. |
| 166 | + |
| 167 | +## License |
| 168 | +- MIT |
0 commit comments