Decompiler Construction: Chapter 18 Practical Decompilation Results
**This is a decompilation result as of 8/3/2026, information output passed this date is assumed to be outdated. To try the decompiler on your own visit: ** Luramas
To stress-test the process, I have included this program, which includes:
- State-dependent control flow with continuously mutating variables
- Mixed arithmetic and bitwise operations (XOR, shifts, rotation patterns)
- Early loop terminations
- Pointers
- Redundant computations that should be simplified
The following input program is compiled and passed through the decompilation process:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdint.h>
volatile int32_t sink;
typedef struct {
int32_t a; int32_t b; int32_t c;
} Node;
int32_t func(int32_t *arr, const Node *n, const int32_t x) {
int32_t acc = 0;
int32_t i = 0;
int32_t state = (x ^ 0x5A) + (n->a << 1);
while (i < 10) {
state = state ^ (i * 3);
if ((state & 1) == 0) { acc += arr[i] + n->b; } else { acc += arr[i] ^ n->c; }
if (acc > 100) { break; }
++i;
if (!(i % 3)) { continue; }
state = (state << 1) | (state >> 31);
}
int32_t tmp = acc * 2;
tmp = tmp - acc;
if (x < 0) { tmp = tmp ^ 0xDEADBEEF; }
sink = tmp;
return acc + state;
}
int32_t main(void) {
int32_t arr = 9;
for (int32_t t = 0; t < 1000; ++t) {
Node n = { .a = t, .b = t ^ 0xAA, .c = t + 3}; func(&arr, &n, 19); }
return 0;
}
Typically this step you would use DBI but for this example we have already compiled, extracted machine code, and mapped edges.
Assembly, this was compiled with CLANG.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0x0: push rbp
0x1: mov rbp, rsp
0x4: sub rsp, 0x20
0x8: mov dword ptr [rbp - 4], 0
0xf: mov dword ptr [rbp - 8], 9
0x16: mov dword ptr [rbp - 0xc], 0
0x1d: cmp dword ptr [rbp - 0xc], 0x3e8
0x24: jge 0x5d
0x26: mov eax, dword ptr [rbp - 0xc]
0x29: mov dword ptr [rbp - 0x18], eax
0x2c: mov eax, dword ptr [rbp - 0xc]
0x2f: xor eax, 0xaa
0x34: mov dword ptr [rbp - 0x14], eax
0x37: mov eax, dword ptr [rbp - 0xc]
0x3a: add eax, 3
0x3d: mov dword ptr [rbp - 0x10], eax
0x40: lea rdi, [rbp - 8]
0x44: lea rsi, [rbp - 0x18]
0x48: mov edx, 0x13
0x4d: call 0x65
....
Builder for Luramas Decompiler:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* Build assembly */
{
/* Build data */
b.emitd({0x55}); /* push rbp */
b.emitd({0x48, 0x89, 0xE5}); /* mov rbp, rsp */
b.emitd({0x48, 0x83, 0xEC, 0x20}); /* sub rsp, 0x20 */
b.emitd({0xC7, 0x45, 0xFC, 0x00, 0x00, 0x00, 0x00}); /* mov dword ptr [rbp - 0x4], 0x0 */
b.emitd({0xC7, 0x45, 0xF8, 0x09, 0x00, 0x00, 0x00}); /* mov dword ptr [rbp - 0x8], 0x9 */
b.emitd({0xC7, 0x45, 0xF4, 0x00, 0x00, 0x00, 0x00}); /* mov dword ptr [rbp - 0xc], 0x0 */
const auto label_1D = *b.emit_label(0x1D); /* label_1D: */
b.emitd({0x81, 0x7D, 0xF4, 0xE8, 0x03, 0x00, 0x00}); /* cmp dword ptr [rbp - 0xc], 0x3e8 */
const auto i_jge_5d_rpc = b.emitd({0x7D, 0x37}, edges, JUMP).first; /* jge label_5D */
b.emitd({0x8B, 0x45, 0xF4}); /* mov eax, dword ptr [rbp - 0xc] */
b.emitd({0x89, 0x45, 0xE8}); /* mov dword ptr [rbp - 0x18], eax */
b.emitd({0x8B, 0x45, 0xF4}); /* mov eax, dword ptr [rbp - 0xc] */
b.emitd({0x35, 0xAA, 0x00, 0x00, 0x00}); /* xor eax, 0xaa */
b.emitd({0x89, 0x45, 0xEC}); /* mov dword ptr [rbp - 0x14], eax */
b.emitd({0x8B, 0x45, 0xF4}); /* mov eax, dword ptr [rbp - 0xc] */
b.emitd({0x83, 0xC0, 0x03}); /* add eax, 0x3 */
b.emitd({0x89, 0x45, 0xF0}); /* mov dword ptr [rbp - 0x10], eax */
b.emitd({0x48, 0x8D, 0x7D, 0xF8}); /* lea rdi, [rbp - 0x8] */
b.emitd({0x48, 0x8D, 0x75, 0xE8}); /* lea rsi, [rbp - 0x18] */
....
We will lift it to IL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
107 SHRN r264, r263, 32
108 XOR r265, r263, r264
109 MOVE r263, r265
110 SHRN r266, r263, 16
111 XOR r267, r263, r266
112 MOVE r263, r267
113 SHRN r268, r263, 8
114 XOR r269, r263, r268
115 MOVE r263, r269
116 SHRN r270, r263, 4
117 XOR r271, r263, r270
118 MOVE r263, r271
119 SHRN r272, r263, 2
120 XOR r273, r263, r272
121 MOVE r263, r273
122 SHRN r274, r263, 1
123 XOR r275, r263, r274
124 MOVE r263, r275
125 ANDN r276, r263, 1
126 BITNOT r277, r276
127 ANDN r278, r277, 1
128 FLAGSET 16, r278
129 BITCAST r250, r250, 64, 0, false
130 MOVE r44, r250
131 NOP
132 BITCAST r248, r248, 32, 0, false
133 LOADINT r249, 0
134 MOVE r248, r249
135 BITCAST r250, r250, 32, 0, false
....
We will then lift the IL to IR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
r260 = memread<int32_t>(r250)
r259 = (r260 - r255)
r259 = int32_t(r259)
r262 = memread<int32_t>(r250)
r261 = (r262 & 15)
r261 = int32_t(r261)
r263 = (r253 & 15)
r263 = int32_t(r263)
r264 = (r261 + r263)
r264 = int32_t(r264)
r265 = (r264 > 15)
r265 = int32_t(r265)
r266 = (r259 & 15)
r266 = int32_t(r266)
r268 = memread<int32_t>(r250)
r267 = (r268 & 15)
r267 = int32_t(r267)
r269 = (r266 < r267)
r269 = int32_t(r269)
r270 = (r265 | r269)
r270 = int32_t(r270)
r270 = int32_t(r270)
....
The IR currently is unsimplified. We will use various passes to simplify it, here are the most common ones:
- Constant folding
- Page constructions
- Dead code elimination
- Jump threading
- Loop reconstruction
- Expression canonicalization
- Loop simplification
- Constant Propagation
- Dead store elimination We will not be doing any memory inferance for safety.
Again this was ran on 8/3/2026 with Luramas assume it is outdated. You can use the builder above to test with Luramas
Completely Safe IR:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DEFINITION([ARGS]{}, [UPVALUES]{}, [FLAGS]{}, [CASTS]{}, [RESULTS]{})
page_function_goto<100>(a36, a40, a44)
page_function_start (98) {
[PK] DEFINITION([ARGS]{a36, a44}, [UPVALUES]{}, [FLAGS]{}, [CASTS]{int32_t(a44), int64_t(a36)}, [RESULTS]{})
memset<int32_t>(331) = memread<int32_t>((a36 - 36))
local r35 = memread<int32_t>((a36 - 24))
a36 = memread<int32_t>(a44)
a44 = (a44 + 4)
[PK] [RT=a44] return ; 98
} page_function_end(98)
page_function_start (100) {
[PK] DEFINITION([ARGS]{a36, a40, a44}, [UPVALUES]{}, [FLAGS]{}, [CASTS]{int64_t(a36), int32_t(a44), int64_t(r44)}, [RESULTS]{})
memset<int32_t>((a44 - 4)) = a36
a36 = a44
a44 = (a44 - 32)
memset<int32_t>((a36 - 4)) = 0
....
Cpp Code Generation uses Luramas-Helpers For Cpp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__forceinline void pf98(std::int32_t val, std::int64_t val_bs);
__forceinline void pf100(std::int64_t dyn_val_bs, std::int32_t a_z, std::int64_t val_bs);
lura_page_function_goto<pf100>(a, a_c, a_e);
__forceinline void pf98(std::int32_t val, std::int64_t val_bs) {
lura_memset<std::int32_t>(331) = lura_memread<std::int32_t>((val - 36));
lura_deadv(auto mem = lura_memread<std::int32_t>((val - 24)));
val = lura_memread<std::int32_t>(val_bs);
val_bs += 4;
return;
}
__forceinline void pf100(std::int64_t dyn_val_bs, std::int32_t a_z, std::int64_t val_bs) {
lura_memset<std::int32_t>(val_bs - 4) = dyn_val_bs;
dyn_val_bs = val_bs;
val_bs -= 32;
....
Download Full CPP Code-Generation
Prev Chapter: Chapter 17 - Lowering IR to Readable and Executable Code