Skip to content

Commit dbfc9e1

Browse files
authored
Merge pull request #2461 from HackTricks-wiki/research_update_src_reversing_reversing-tools-basic-methods_blobrunner_20260703_034739
Research Update Enhanced src/reversing/reversing-tools-basic...
2 parents 63dd76f + 5d80cb7 commit dbfc9e1

1 file changed

Lines changed: 95 additions & 5 deletions

File tree

src/reversing/reversing-tools-basic-methods/blobrunner.md

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,94 @@
22

33
{{#include ../../banners/hacktricks-training.md}}
44

5-
The only modified line from the [original code](https://github.com/OALabs/BlobRunner) is the line 10.
5+
[**BlobRunner**](https://github.com/OALabs/BlobRunner) is a tiny Windows **shellcode loader for debugging**: it allocates RWX memory, copies the blob, prints the base address / entry point, and transfers execution there. This is handy when the sample is **raw shellcode**, a **decrypted stage extracted from malware**, or a **position-independent blob** that does not have a PE header.
6+
7+
The snippet below keeps the original idea, but uses **`%p` for printed pointers** so the x64 build doesn't truncate addresses while you are trying to attach a debugger or rebase the blob in your RE tool.
8+
9+
## Build
10+
11+
The simplest way to build the original project is from a **Visual Studio Developer Command Prompt**:
12+
13+
```bash
14+
cl blobrunner.c
15+
cl /Feblobrunner64.exe /Foblobrunner64.out blobrunner.c
16+
```
17+
18+
You can also paste the code into a small Visual Studio / VS Code C project and compile it there.
19+
20+
## Useful usage patterns
21+
22+
```bash
23+
# Execute from the beginning of the blob
24+
BlobRunner.exe shellcode.bin
25+
26+
# Start from a known offset inside the blob
27+
BlobRunner.exe shellcode.bin --offset 0x100
28+
29+
# Don't stop before transferring execution
30+
BlobRunner.exe shellcode.bin --nopause
31+
32+
# Force an access violation and let the configured JIT debugger catch it
33+
BlobRunner.exe shellcode.bin --jit
34+
```
35+
36+
- In **x86**, BlobRunner pauses and then performs a direct jump to the blob entry point.
37+
- In **x64**, it creates a **suspended thread**, so you can break on the thread start address before resuming execution.
38+
- `--offset` is especially useful when the dumped blob starts with a **decoder / unpacking stub** and you already know the real entry point.
39+
40+
## Practical notes
41+
42+
### Fix the printed addresses in x64 labs
43+
44+
Older BlobRunner code prints addresses via casts such as `(int)(size_t)lpvBase` and `%08x` / `%016x`. In 64-bit workflows this can truncate the high half of the pointer and make rebasing / breakpoint placement annoying. The snippet below already fixes that by printing **`%p`** values directly.
45+
46+
### `--jit` is useful for first-instruction breakpoints
47+
48+
`--jit` removes execute access from the first byte of the shellcode and lets Windows raise an **access violation** when the blob starts executing. This is useful when you want the **configured JIT debugger** (for example x64dbg) to catch the first execution attempt instead of manually racing to attach. After the debugger breaks, restore execute rights and continue.
49+
50+
A practical **x64dbg** flow is:
51+
52+
```text
53+
setjit
54+
setjitauto on
55+
BlobRunner.exe shellcode.bin --jit
56+
setpagerights <region>, ExecuteReadWrite
57+
```
58+
59+
The first two commands register x64dbg as the JIT debugger, and `setpagerights` restores execute rights on the region printed by BlobRunner after the debugger catches the access violation.
60+
61+
### Time-travel the shellcode instead of single-stepping it live
62+
63+
A very practical recent workflow is to record BlobRunner under **TTD** and then inspect the trace in **Binary Ninja** / **WinDbg**. This is great when the blob decrypts itself, resolves APIs dynamically, or performs several short-lived stages. Since **Binary Ninja 4.1**, TTD support is no longer just beta quality: it can drive reverse-debugging and simplify the WinDbg / TTD workflow directly from Binary Ninja.
64+
65+
```bash
66+
TTD.exe .\blobrunner.exe .\shellcode.bin
67+
```
68+
69+
The important part is to **note the allocated base address printed by BlobRunner** and then **rebase** the shellcode view to that address before replaying the trace. Also note that Microsoft documents TTD recording as **invasive**: run it from an **elevated** prompt, expect noticeable slowdown, and keep the recording window short to avoid massive trace files.
70+
71+
### If the blob needs companion data, use a PE wrapper instead
72+
73+
Some shellcode expects a **second blob**, a **mapped file**, or some other **structured content** to exist in memory. BlobRunner is intentionally minimal, so for these cases a runner such as **SCLauncher** can be more convenient because it can:
74+
75+
- pause before execution,
76+
- insert an `INT3` breakpoint,
77+
- load **additional content** into memory,
78+
- memory-map that extra content, or
79+
- wrap the shellcode inside a temporary **PE** for easier analysis in tools that prefer normal executables.
80+
81+
Example:
82+
83+
```bash
84+
SCLauncher.exe -f=shellcode.bin -pause -d=config.bin -mm
85+
SCLauncher.exe -f=shellcode.bin -pe -64 -ep=0x120
86+
```
87+
88+
For complementary workflows such as **jmp2it**, **Cutter** emulation, or **scdbg**-based shellcode tracing, check the [parent shellcode reversing page](README.md).
89+
90+
## Source code
91+
92+
The only modified lines from the [original code](https://github.com/OALabs/BlobRunner) are the pointer-printing lines used to avoid x64 address truncation.
693
In order to compile it just **create a C/C++ project in Visual Studio Code, copy and paste the code and build it**.
794

895
```c
@@ -69,7 +156,7 @@ LPVOID process_file(char* inputfile_name, bool jit, int offset, bool debug) {
69156
lpvBase = VirtualAlloc(NULL, fileLen, 0x3000, 0x40);
70157

71158
printf(".Allocated!\n");
72-
printf(" [*] |-Base: 0x%08x\n", (int)(size_t)lpvBase);
159+
printf(" [*] |-Base: %p\n", lpvBase);
73160
printf(" [*] Copying input data...\n");
74161

75162
CopyMemory(lpvBase, buffer, fileLen);
@@ -106,7 +193,7 @@ void execute(LPVOID base, int offset, bool nopause, bool jit, bool debug)
106193
return;
107194
}
108195
printf(" [*] Created Thread: [%d]\n", thread_id);
109-
printf(" [*] Thread Entry: 0x%016x\n", (int)(size_t)shell_entry);
196+
printf(" [*] Thread Entry: %p\n", shell_entry);
110197

111198
#endif
112199

@@ -131,7 +218,7 @@ void execute(LPVOID base, int offset, bool nopause, bool jit, bool debug)
131218
printf(" [*] Resuming Thread..\n");
132219
ResumeThread(thread_handle);
133220
#else
134-
printf(" [*] Entry: 0x%08x\n", (int)(size_t)shell_entry);
221+
printf(" [*] Entry: %p\n", shell_entry);
135222
printf(" [*] Jumping to shellcode\n");
136223
__asm jmp shell_entry;
137224
#endif
@@ -209,7 +296,10 @@ int main(int argc, char* argv[])
209296
}
210297
```
211298
212-
{{#include ../../banners/hacktricks-training.md}}
213299
214300
301+
## References
215302
303+
- [Time Travel Debugging Shellcode with Binary Ninja](https://www.lrqa.com/en/cyber-labs/time-travel-debugging-shellcode-with-binary-ninja/)
304+
- [Analyzing Shellcode with SCLauncher](https://www.thecyberyeti.com/post/analyzing-shellcode-with-sclauncher)
305+
{{#include ../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)