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.