I am trying to code a recursive fibonacci sequence in assembly, but it is not working for some reason.
It does not give an error, but the output number is always wrong.
section .bss
_bss_start:
; store max iterations
max_iterations resb 4
_bss_end:
section .text
global _start
; initialise the function variables
_start:
mov dword [max_iterations], 11
mov edx, 0
push 0
push 1
jmp fib
fib:
; clear registers
mov eax, 0
mov ebx, 0
mov ecx, 0
; handle fibonacci math
pop ebx
pop eax
add ecx, eax
add ecx, ebx
push eax
push ebx
push ecx
; incriment counter / exit contitions
inc edx
cmp edx, [max_iterations]
je print
; recursive step
call fib
ret
print:
mov eax, 1
pop ebx
int 0x80
For instance, the above code prints a value of 79 rather than 144 (11th fibonacci number).
Alternatively, if I make
mov dword [max_iterations], 4
Then the above code prints 178 rather than 5 (5th fibonacci number).
Any one have an idea?
K
As an approach, you should try to debug it with the smallest possible input, like 1 iteration. That will be most revealing as you can watch it do the wrong thing in great detail without worrying about multiple recursing's. When that works, go to 2 iterations.
When you use complex addressing modes, it is harder to debug as we cannot see what the processor is doing. So, when an instruction using a complex addressing mode doesn't work, and you want to debug it, then split that instruction into 2 instructions as follows:
mov dword [fibonacci_seq + edx + 4], ecx
---
lea esi, [fibonacci_seq + edx + 4]
mov [esi], ecx
With the alternate code sequence, you can observe the value of the addressing mode computation, which will provide you with additional debugging insight.
As another example:
cmp edx, [max_iterations]
---
mov edi, [max_iterations]
cmp edx, edi
Using the 2 instruction version, you will be able to see what value the processor is comparing edx with.
Or better, do that that mov load once before the loop, so you're keeping the loop bound in a register all the time. That's what you should normally do when you have enough registers, only using memory when you run out.
You are jmping to fib from one place in the code and calling it from another. Though your logic should work because when you've reached the limit, you don't return to the main, this is really bad form: to mix main code with function. More on that below...
mov dword [fibonacci_seq + edx + 4], ecx
Is this working for you? You're only incrementing edx by 1. Perhaps you wanted:
mov dword [fibonacci_seq + edx * 4], ecx
I would argue that your code is not really recursive.
call fib ; jumps to fib, pushes a return address
ret ; never, ever reached, so, pointless
---
jmp fib ; iterate w/o pushing unwanted return address onto the stack
The 1-instruction jmp will be superior to the call as a mechanism to iterate, in part b/c it doesn't push an unnecessary return address onto the stack.
When you debug with 2 iterations, you'll probably see that the unused return address pushed by the call messes up your "parameter" passing, pops.
To expand on the "recursion", when the iteration stops and control transfers to print, there will be some 11 (depending on iteration count) unused return addresses on the stack (modulo the interference by the pop's and pushes).
The recursive call is only used for iteration, the recursion never unwinds. Thus, I would argue it's not recursive (not even tail recursive) — it just erroneously pushes some unused return addresses onto the stack — that's not recursion.
This line is your main problem:
mov dword [fibonacci_seq + edx + 4], ecx
Because of the +4, you never write to the first entry of the "array". And because you only increment EDX by 1, each write to the array overwrites 3 bytes of the previous entry. Try this instead:
mov dword [fibonacci_seq + edx * 4], ecx
A bit of redesign, as I did not realise that the call instruction used the stack in this way, and the solution is here
section .bss
_bss_start:
; store max iterations and current iteration
max_iterations resb 4
iteration resb 4
; store arguments
n_0 resb 4
n_1 resb 4
_bss_end:
section .text
global _start
; initialise the function variables
_start:
mov dword [max_iterations], 11
mov dword [iteration], 0
mov dword [n_0], 0
mov dword [n_1], 1
jmp fib
fib:
mov ecx, 0
mov edx, 0
mov eax, [n_0]
mov ebx, [n_1]
add ecx, eax
add ecx, ebx
mov edx, [n_1]
mov dword [n_0], edx
mov dword [n_1], ecx
mov edx, [iteration]
inc edx
mov dword [iteration], edx
cmp edx, [max_iterations]
je print
call fib
ret
print:
mov eax, 1
mov ebx, [n_1]
int 0x80
I've wondered for some time how some software hides secret keys in such a way that they can't be trivially discovered. Just a few examples:
DVD Player Software hides CSS keys
Software with serial numbers/registration codes hides keys/hashes used to validate the serial numbers
Obviously, these programs do something more than just have the key in a byte[], as that would make it easy to steal their keys and generate your own serial numbers, etc.
What sorts of strategies are used to hide these keys so that they can't be found easily?
The reasons those secret keys were so easily discovered is because they were hidden in software.
Avoid hiding secrets in software at all cost - obfuscation will only get you so far. Ask yourself this: How well can I hide a key in software from someone with full access to the disassembly, user mode and kernel mode debuggers, and no day job? It's only a matter of time before it gets cracked.
You just hide the key somewhere, and decrypt it when you need it. Using the key "securely" is the complicated part. Crackers might set a breakpoint to the place where you use the decrypted key and dump it. They might scan your code for patterns which show that you are using a known crypto algorithm (most algorithms have precalculated tables). etc etc.
That's why you need to make the whole software executable hard to analyze. For this you use executable packers, running code in a virtual machine, integrity checks etc. All this is to slow down debugging and modifying your code.
As most people here point out you can't stop anyone, just slow them down. I'd go to a cracker forum and ask there for suggestions about key hiding problematics. They are most likely helpful if you ask nicely.
ps. Public key crypto won't hide the key any better, but it might make it harder (or theoretically impossible) to make a key generator, if you're doing a licensing scheme.
The bottom line is, you can't. See any other comment here for the reasons why. Even encryption software like PGP/GPG stores the keys in a file, and then stridently urges those files to be kept on a flash drive in a safe, or something else secure. Keys stored as part of executable code will be discovered.
In fact, if you're trying to encrypt anything on a client machine that will be decrypted by the client as part of normal operations, that is also a fool's errand. The client machines are inherently insecure, and you can't control what they're going to be able to do to your data.
If you're trying to authenticate, instead, look at Internet based authentication with logins to a server, or some kind of generated KeyCode that is used to validate the software.
Secret keys as part of a Public-Private Keypair should be kept in data files that can be secured. Symmetric keys should be generated on the fly as Session Keys, then discarded. Always assume that anyone who has a Secret or Session key on their computer will be able to discover it, and use it against your intentions.
Read "Applied Cryptography" by Bruce Schneier for more information.
You can't hide a key forever. But you can sure make it hard to find. Some approaches are to encrypt the key in memory, keep multiple copies (perhaps encrypted differently) that are checked against each other, leave dummy copies to be accessed, store the key in some bizarre format, etc. None of them will work if somebody really wants your key, but you can at least dissuade a casual/inexperienced attacker.
You don't always need a key to validate a license.
But ignoring that fact, your key can also be the result of another function. You don't actually store a specific key value, instead you have a function that generates the key on the fly (always the same result). Although not impossible, it's much harder to find since you're no longer looking for a value, but you have to figure out it's an equation.
When we started developing our software, we've created a dated license file. Then, we realized, that not too many people are even interested in buying our software. Then, we decided to give it away for free. A lot more people started to care at least trying our masterpiece. Finally, we've open sourced our software. A lot more users started using it. Now we just hope that a small number of these users might turn into paying customers (i.e. buying prod. support or asking for customization).
The bottom line is, if someone wants to crack your software, he/she'll do it anyway. Is it really worth it to waste your time trying to protect it with this hidden secret key?
If you can afford it, the best is to store the private key in a cryptographic USB token. The key is write only, ie you can set it but not read it. The token does the cryptographic operations internally, in its hardware. It becomes very complicated to retrieve the key (if the token has no known vulnerability, which is not the case with older ones).
You can use https://godbolt.org/ to look at the assembly and see, there are a couple things you can do to make it more difficult. First is to hide the symbols (so checkSecret() isn't so obvious), and the second to use a function to generate the key/password. The idea is that it will take longer to find the part of the code that will lead to bypassing any security, so also not calling it from the main function is probably a good idea.
Looking at two approaches:
an explicitly declared password
a function that generates the password
Here's an example of the first:
#include <string>
#include <iostream>
using namespace std;
string secretKey = "fdasfdasfydsafhidljj3r32R##f";
bool securityCheck(string key){
if(key==secretKey) cout << "Success!" << endl;
return key==secretKey;
}
int main(){
securityCheck(secretKey);
}
As you can see more clearly on https://godbolt.org/, this creates the following assembly (with gcc 11.2):
secretKey[abi:cxx11]:
.zero 32
.LC0:
.string "Success!"
securityCheck(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >):
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
mov esi, OFFSET FLAT:secretKey[abi:cxx11]
mov rdi, rax
call __gnu_cxx::__enable_if<std::__is_char<char>::__value, bool>::__type std::operator==<char>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
test al, al
je .L16
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
.L16:
mov rax, QWORD PTR [rbp-8]
mov esi, OFFSET FLAT:secretKey[abi:cxx11]
mov rdi, rax
call __gnu_cxx::__enable_if<std::__is_char<char>::__value, bool>::__type std::operator==<char>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
leave
ret
main:
push rbp
mov rbp, rsp
push rbx
sub rsp, 40
lea rax, [rbp-48]
mov esi, OFFSET FLAT:secretKey[abi:cxx11]
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) [complete object constructor]
lea rax, [rbp-48]
mov rdi, rax
call securityCheck(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)
lea rax, [rbp-48]
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
mov eax, 0
jmp .L22
mov rbx, rax
lea rax, [rbp-48]
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
mov rax, rbx
mov rdi, rax
call _Unwind_Resume
.L22:
mov rbx, QWORD PTR [rbp-8]
leave
ret
.LC1:
.string "basic_string::_M_construct null not valid"
.LC2:
.string "fdasfdasfydsafhidljj3r32R##f"
__static_initialization_and_destruction_0(int, int):
push rbp
mov rbp, rsp
push rbx
sub rsp, 40
mov DWORD PTR [rbp-36], edi
mov DWORD PTR [rbp-40], esi
cmp DWORD PTR [rbp-36], 1
jne .L58
cmp DWORD PTR [rbp-40], 65535
jne .L58
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
call __cxa_atexit
lea rax, [rbp-17]
mov rdi, rax
call std::allocator<char>::allocator() [complete object constructor]
lea rax, [rbp-17]
mov rdx, rax
mov esi, OFFSET FLAT:.LC2
mov edi, OFFSET FLAT:secretKey[abi:cxx11]
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
lea rax, [rbp-17]
mov rdi, rax
call std::allocator<char>::~allocator() [complete object destructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:secretKey[abi:cxx11]
mov edi, OFFSET FLAT:std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
call __cxa_atexit
jmp .L58
mov rbx, rax
lea rax, [rbp-17]
mov rdi, rax
call std::allocator<char>::~allocator() [complete object destructor]
mov rax, rbx
mov rdi, rax
call _Unwind_Resume
.L58:
mov rbx, QWORD PTR [rbp-8]
leave
ret
_GLOBAL__sub_I_secretKey[abi:cxx11]:
push rbp
mov rbp, rsp
mov esi, 65535
mov edi, 1
call __static_initialization_and_destruction_0(int, int)
pop rbp
ret
You can see that the secretKey is plainly visible, and the function names are as well.
Here's a second example:
#include <string>
#include <iostream>
using namespace std;
string getSecretKey(){
srand(100);
string chars = "qwertyuioplkjhgfdsazxcvbnm123456789";
string result = "";
for(int i = 0; i < 100; ++i){
result += chars[rand()%chars.size()];
}
return result;
}
string secretKey = getSecretKey();
bool securityCheck(string key){
if(key==secretKey) cout << "Success!" << endl;
return key==secretKey;
}
int main(){
securityCheck(secretKey);
}
This produces the following assembly:
.LC0:
.string "qwertyuioplkjhgfdsazxcvbnm123456789"
.LC1:
.string ""
getSecretKey[abi:cxx11]():
push rbp
mov rbp, rsp
push rbx
sub rsp, 72
mov QWORD PTR [rbp-72], rdi
mov edi, 100
call srand
lea rax, [rbp-22]
mov rdi, rax
call std::allocator<char>::allocator() [complete object constructor]
lea rdx, [rbp-22]
lea rax, [rbp-64]
mov esi, OFFSET FLAT:.LC0
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
lea rax, [rbp-22]
mov rdi, rax
call std::allocator<char>::~allocator() [complete object destructor]
lea rax, [rbp-21]
mov rdi, rax
call std::allocator<char>::allocator() [complete object constructor]
lea rdx, [rbp-21]
mov rax, QWORD PTR [rbp-72]
mov esi, OFFSET FLAT:.LC1
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
lea rax, [rbp-21]
mov rdi, rax
call std::allocator<char>::~allocator() [complete object destructor]
mov DWORD PTR [rbp-20], 0
jmp .L16
.L17:
call rand
movsx rbx, eax
lea rax, [rbp-64]
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::size() const
mov rcx, rax
mov rax, rbx
mov edx, 0
div rcx
mov rcx, rdx
mov rdx, rcx
lea rax, [rbp-64]
mov rsi, rdx
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator[](unsigned long)
movzx eax, BYTE PTR [rax]
movsx edx, al
mov rax, QWORD PTR [rbp-72]
mov esi, edx
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator+=(char)
add DWORD PTR [rbp-20], 1
.L16:
cmp DWORD PTR [rbp-20], 99
jle .L17
nop
lea rax, [rbp-64]
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
jmp .L26
mov rbx, rax
lea rax, [rbp-22]
mov rdi, rax
call std::allocator<char>::~allocator() [complete object destructor]
mov rax, rbx
mov rdi, rax
call _Unwind_Resume
mov rbx, rax
lea rax, [rbp-21]
mov rdi, rax
call std::allocator<char>::~allocator() [complete object destructor]
jmp .L21
mov rbx, rax
mov rax, QWORD PTR [rbp-72]
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
.L21:
lea rax, [rbp-64]
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
mov rax, rbx
mov rdi, rax
call _Unwind_Resume
.L26:
mov rax, QWORD PTR [rbp-72]
mov rbx, QWORD PTR [rbp-8]
leave
ret
secretKey[abi:cxx11]:
.zero 32
.LC2:
.string "Success!"
securityCheck(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >):
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
mov esi, OFFSET FLAT:secretKey[abi:cxx11]
mov rdi, rax
call __gnu_cxx::__enable_if<std::__is_char<char>::__value, bool>::__type std::operator==<char>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
test al, al
je .L28
mov esi, OFFSET FLAT:.LC2
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
.L28:
mov rax, QWORD PTR [rbp-8]
mov esi, OFFSET FLAT:secretKey[abi:cxx11]
mov rdi, rax
call __gnu_cxx::__enable_if<std::__is_char<char>::__value, bool>::__type std::operator==<char>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
leave
ret
main:
push rbp
mov rbp, rsp
push rbx
sub rsp, 40
lea rax, [rbp-48]
mov esi, OFFSET FLAT:secretKey[abi:cxx11]
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) [complete object constructor]
lea rax, [rbp-48]
mov rdi, rax
call securityCheck(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)
lea rax, [rbp-48]
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
mov eax, 0
jmp .L34
mov rbx, rax
lea rax, [rbp-48]
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
mov rax, rbx
mov rdi, rax
call _Unwind_Resume
.L34:
mov rbx, QWORD PTR [rbp-8]
leave
ret
.LC3:
.string "basic_string::_M_construct null not valid"
__static_initialization_and_destruction_0(int, int):
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
cmp DWORD PTR [rbp-4], 1
jne .L72
cmp DWORD PTR [rbp-8], 65535
jne .L72
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
call __cxa_atexit
mov eax, OFFSET FLAT:secretKey[abi:cxx11]
mov rdi, rax
call getSecretKey[abi:cxx11]()
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:secretKey[abi:cxx11]
mov edi, OFFSET FLAT:std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
call __cxa_atexit
.L72:
nop
leave
ret
_GLOBAL__sub_I_getSecretKey[abi:cxx11]():
push rbp
mov rbp, rsp
mov esi, 65535
mov edi, 1
call __static_initialization_and_destruction_0(int, int)
pop rbp
ret
You can definitely see that you're no longer to naively scan the code for interesting strings, but there's more involved. You'll have to set breakpoints and check different values, and try to look through it logically to solve it. Still, in this example, the symbols are visible, which gives away a great deal of sensitive information about what the application is doing. Hiding the symbols makes bypassing the security more difficult, from what you can see.
Hiding secret keys in code is not going to be really secure. As you may have noticed DVDs and most software serial number registrations get hacked on a daily basis. If you really want to secure something you need to use public key encryption.
You cannot, it is impossible. Any attacker who has access to the target machine would be able to disassemble your code to find it, or find the key file on the target machine, etc.
The ONLY way to ensure that the encryption key is secure, is to have it typed in manually by the user when it is needed.
I think this is one of the biggest reasons that DVD and BluRay were cracked so quickly. I think the only way that they could really stop the average person from being able to digitally copy home movies is if they created a medium that wasn't licensed for use on computers, and could only be used on certified players. Would cut out the part of the market that wanted to watch movies on their computers and laptops, but would probably stop from having perfect digital rips for a little longer, and would stop the average person from being able to do it.