summaryrefslogtreecommitdiff
path: root/other/burneye/doc/per-function-encryption.txt
diff options
context:
space:
mode:
authorRoot THC2026-02-24 12:42:47 +0000
committerRoot THC2026-02-24 12:42:47 +0000
commitc9cbeced5b3f2bdd7407e29c0811e65954132540 (patch)
treeaefc355416b561111819de159ccbd86c3004cf88 /other/burneye/doc/per-function-encryption.txt
parent073fe4bf9fca6bf40cef2886d75df832ef4b6fca (diff)
initial
Diffstat (limited to 'other/burneye/doc/per-function-encryption.txt')
-rw-r--r--other/burneye/doc/per-function-encryption.txt139
1 files changed, 139 insertions, 0 deletions
diff --git a/other/burneye/doc/per-function-encryption.txt b/other/burneye/doc/per-function-encryption.txt
new file mode 100644
index 0000000..8754653
--- /dev/null
+++ b/other/burneye/doc/per-function-encryption.txt
@@ -0,0 +1,139 @@
1
2BURNEYE preliminary documentation
3
4
5Description of how per-function encryption works in burneye
6===========================================================
7
8If the binary that is wrapped by burneye contains symbolic debug information
9(usually unstripped binaries compiled with the -g option), we apply a special
10encryption method.
11
12Through the debug info, certain information about the functions within the
13binary is extracted: The virtual address the function begins with, the length
14in bytes of the functions' machine instructions and its name.
15
16Each function is encrypted then. Since it is very difficult to move the
17functions, we cannot prepend them with a decryption stub. Instead, we encrypt
18the function, but overwrite its first eight bytes using a special sequence of
19machine instructions:
20
21 push eax ; 0x50
22 pusha ; 0x60
23 pushf ; 0x9c
24 call cg_entry ; 0xe8 0x00 0x00 0x00 0x00
25
26After the call instruction the remaining encrypted function data is stored. The
27first eight bytes - which we overwrite with our stub - are saved elsewhere (see
28below). This eight byte stub is stored directly within the binary. The function
29can only be in two states throughout runtime, it can be encrypted or decrypted.
30If it is encrypted the first eight bytes of the function contains always this
31stub bytes. On the other hand, if it is decrypted the real original bytes are
32at that place.
33
34Now for the interesting part. As the function calls the 'cg_entry' entry point,
35its stack space looks like this:
36
37 [(possible) function arguments]
38 [return address from func caller]
39 [eax]
40 [pusha register block]
41 [saved flag register]
42 [cg_entry caller return address (func + 8)]
43
44The code at 'cg_entry' pops the last address from the stack and computes the
45original function address from it (just minus eight). Using the address, it
46calls a search function which searchs through a table of structures, one for
47each encrypted function. Once it finds the appropiate structure, it restores
48the first eight bytes of the function with the saved encrypted bytes, then
49calls a decryption function on the entire functions data. Normally it could
50just restore the flags and all registers then and jump to the functions entry
51point. This would work perfectly, but the more functions are called in the
52application, the more it is decrypted. If all functions are called at least one
53time, the entire .text segment is decrypted and can be dumped.
54
55To avoid this 'lazy-decryption' problem, the 'cg_entry' code also replaces the
56return address of the function that is decrypted. Thus, as the now-decrypted
57function is returning through a simple 'ret' instruction, our code is called
58again. The diagram shows how this works:
59
60usual: [outside function]---[core function]---[called function]
61
62cg: [outside function]. .[core function]. .[called function]
63 | | | |
64 .---------' '--------. .----' '------------.
65 | | | |
66 '-----[cg_entry]-----' '-----[cg_detry]----'
67
68This way both the entry and return ('detry') point of the function is
69redirected. As a clever reader you may have noticed that the parent function
70remains decrypted in this setup. Therefore the 'cg_entry' code also re-encrypts
71its caller function.
72
73In detail the stub and 'cg_entry' code does:
74
75 1. save necessary data (flags, registers)
76 2. encrypt outside function
77 3. restore first eigth encrypted bytes of core function
78 4. decrypt core function
79 5. restore necessary data (flags, registers)
80 6. pass control to entry point of core function (through jmp)
81
82
83The 'cg_detry' code has to mirror the behaviour from the opposite perspective:
84
85 1. save necessary data (flags, registers)
86 2. encrypt core function
87 3. overwrite first eight bytes in core function with stub
88 4. decrypt outside function
89 5. restore necessary data (flags, registers)
90 6. return to real core function return address
91
92
93Possible runtime problems
94-------------------------
95
96This function wrapping method is quite reliable and can cope with various
97situations, such as goto's, signal handlers, function pointers and generally
98non-linear code. However, there is one case where this does not work.
99
100If there is an execution path which points from within one function to the
101middle of another encrypted function, the target function is not decrypted.
102This sounds complicated, so here is a rule of thumb: Do NOT use longjmp/setjmp
103within your code.
104
105If you have to use it or you have to protect a stock binary with symbols, you
106can tag the function that the 'longjmp' passes control to (i.e. the function
107that has the 'setjmp' call) with a decrypt-log. This means it is initially
108decrypted once, before your binary receives any control at all and remains
109decrypted throughout the whole runtime.
110
111Note that this way the function that receives the 'longjmp' remains unprotected
112through the whole time the binary runs. Hence, use this only if there is no way
113to replace the 'longjmp'. In most cases there is a way.
114
115Also, you can use the decrypt-lock functionality for performance improvements,
116see below.
117
118
119Performance overhead
120--------------------
121
122TODO
123
124Performance is an issue with this protection. However, I do not have made
125concrete statistics about it. For most I/O based programs the overhead may lie
126around 10 to 15 times the instructions executed in runtime than in the
127unprotected version. With some per-function optimizations, leaving the most
128often called functions unprotected at runtime these may drop to a level of five
129or less. If only single core functions are protected there may be no overhead
130at all.
131
132However, to make a real decision, statistics are required once the encrypter is
133finished. TODO
134
135
136
137--
138vi:fo=tcrq:tw=79
139