
Authentication Verification
Authentication (from the Greek “authentikos” — genuine) is the “heart” of the vast majority of security mechanisms. We need to verify whether the entity that claims an identity is indeed the one interacting with the program and whether it is authorized to work with the program at all!
A “user” can refer not only to a person but also to their computer or the storage device containing a licensed copy of the software. Accordingly, security mechanisms can generally be divided into two main categories:
- Knowledge-based protections (passwords, serial numbers)
- Possession-based protections (key disks, documentation)
If your security relies solely on the assumption that its code won’t be studied or altered, it’s inadequate. The lack of source code is by no means an insurmountable barrier to understanding and modifying an application. Modern reverse engineering technologies can automatically identify library functions, local variables, stack arguments, data types, branches, loops, and more. In the near future, disassemblers might even generate listings that closely resemble high-level programming languages.
Even today, analyzing binary code isn’t so time-consuming as to significantly hinder attackers. The sheer number of ongoing breaches serves as the best evidence of this fact. Ideally, the knowledge of a protection algorithm should not affect its robustness, but achieving this is often challenging. For instance, if a developer of a server program decides to set a limit on the number of concurrent connections in a demo version (a common practice), an attacker just needs to find the specific processor instruction performing this check and remove it. Modifications to the program can be thwarted by constantly verifying its integrity, but once again, the code responsible for integrity checks can be identified and eliminated.
Step One: Warm-Up
The algorithm of a basic authentication mechanism involves comparing each character of the password entered by the user with a reference value, which is stored either within the program itself (as is often the case) or externally, such as in a configuration file or the registry (which is less common).
The advantage of this protection is its extremely simple software implementation. Its core essentially consists of a single line, which can be expressed in C as follows:
if (strcmp(entered_password, reference_password)) {/* Password incorrect */} else {/* Password OK */}
Let’s enhance this code by adding procedures for password requests and displaying comparison results. Then, we’ll test the program to evaluate its robustness and resistance to hacking.
Listing 1. Example of a Basic Authentication System
#include "stdafx.h"
// Simple authentication system -
// character-by-character password comparison
#include <stdio.h>
#include <string.h>
#define PASSWORD_SIZE 100
#define PASSWORD "myGOODpassword\n"
// The newline is needed to avoid
// removing the newline from the user's input
int main()
{
// Counter for failed authentication attempts
int count=0;
// Buffer for the user's entered password
char buff[PASSWORD_SIZE];
// Main authentication loop
for(;;)
{
// Prompt and read user's
// password
printf("Enter password:");
fgets(&buff[0],PASSWORD_SIZE,stdin);
// Compare the original and entered passwords
if (strcmp(&buff[0],PASSWORD))
// If passwords do not match, show an error message
printf("Wrong password\n");
// Otherwise (if passwords match)
// exit the authentication loop
else break;
// Increment counter for failed
// authentication attempts, and if all attempts
// are exhausted, terminate the program
if (++count>3) return -1;
}
// If we are here, the user entered the correct
// password
printf("Password OK\n");
}
In popular movies, cool hackers effortlessly break into incredibly secure systems, somehow guessing the right password in just a few tries. Why not try to follow their path?
Passwords often consist of meaningful words like Ferrari, QWERTY, names of beloved pets, or names of geographical locations. Guessing a password is akin to reading tea leaves—there are no guarantees of success, and it comes down to sheer luck. And as the saying goes, luck is a fickle thing. Isn’t there a more reliable way to crack a password?
Let’s think about it. If the reference password is stored within the program itself and isn’t encrypted in a complex manner, it can be found by simply scanning through the program’s binary code. By reviewing all text strings encountered in the code, starting with those that resemble a password, we can quickly identify the necessary key to unlock the program. The scope of this search can be significantly narrowed down since, in most cases, compilers place all initialized variables in the data segment (in PE files, this is located in the .data or .rdata section). The exception includes older Borland compilers, which had a tendency to insert text strings directly into the code segment at the point of their invocation. While this simplifies the compiler itself, it creates many problems. Unlike the old MS-DOS, modern operating systems prohibit modifications to the code segment, making variables placed there read-only. Additionally, on processors with separate caching systems, these variables “pollute” the instruction cache by being pre-loaded there, only to be reloaded from slow main memory (L2 cache) into the data cache upon first access. This results in lags and decreased performance.
Alright, let’s make this a data section! The only thing left is to find a convenient tool for viewing a binary file. Of course, one could press F3 in their favorite shell (like FAR, for example) and hold down the Page Down key, watching the scrolling numbers until it gets boring.
You can use any hex editor you prefer (such as QView, Hiew…), but for clarity, the article showcases the results from the DUMPBIN utility, which is included with Microsoft Visual Studio. DUMPBIN is run from the Developer Command Prompt.
Let’s direct the utility to the executable file of our program, where the password is stored, and request it to print the section containing initialized read-only data, called rdata (using the switch /
), in “raw” form (using the switch /
). Use the >
symbol to redirect the output to a file (the program’s response takes up a lot of space, and only the “tail” fits on the screen).
Listing 2
> dumpbin /RAWDATA:BYTES /SECTION:.rdata passCompare1.exe > rdata.txt
004020E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004020F0: 18 30 40 00 68 30 40 00 45 6E 74 65 72 20 70 61 .0@.h0@.Enter pa
00402100: 73 73 77 6F 72 64 3A 00 6D 79 47 4F 4F 44 70 61 ssword:.myGOODpa
00402110: 73 73 77 6F 72 64 0A 00 57 72 6F 6E 67 20 70 61 ssword..Wrong pa
00402120: 73 73 77 6F 72 64 0A 00 50 61 73 73 77 6F 72 64 ssword..Password
00402130: 20 4F 4B 0A 00 00 00 00 00 00 00 00 00 00 00 00 OK.............
00402140: 00 00 00 00 90 0A C1 5B 00 00 00 00 02 00 00 00 ......A[........
00402150: 48 00 00 00 24 22 00 00 24 14 00 00 00 00 00 00 H...$"..$.......
Among other things, there’s a line here that looks painfully similar to the master password (it’s highlighted in bold in the text). Shall we test it? However, there’s little point—judging by the original program code, it really is the password that unlocks the protection like a golden key. The compiler chose too obvious a place for storing it—the password could have been better hidden.
One way to achieve this is by forcibly placing a reference password into a section of our own choosing. This capability is not part of the standard, which means that each compiler developer (or more accurately, linker developer) can choose to implement it in their own way, or not implement it at all. In Microsoft Visual C++, there is a special pragma called data_seg
, which specifies into which section the initialized variables that follow should be placed. Uninitialized variables are, by default, located in the .
section and are managed by the bss_seg
pragma accordingly.
In Listing 1, we’ll add a new section before the main function where we will store our password:
// From this point, all initialized variables will be// placed in the .kpnc section#pragma data_seg(".kpnc")#define PASSWORD_SIZE 100#define PASSWORD "myGOODpassword\n"char passwd[] = PASSWORD;#pragma data_seg()
Inside the main
function, we’ll initialize an array:
// Now all initialized variables will again be placed// in the default section, i.e., .rdatachar buff[PASSWORD_SIZE]="";
The string comparison condition in the loop has been slightly modified:
if (strcmp(&buff[0],&passwd[0]))
Let’s run the DUMPBIN utility on the new executable file:
> dumpbin /RAWDATA:BYTES /SECTION:.rdata passCompare2.exe > rdata.txt
004020C0: D3 17 40 00 00 00 00 00 D8 11 40 00 00 00 00 00 O.@.....O.@.....
004020D0: 00 00 00 00 2C 11 40 00 D0 11 40 00 00 00 00 00 ....,.@.?.@.....
004020E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004020F0: 18 30 40 00 68 30 40 00 45 6E 74 65 72 20 70 61 .0@.h0@.Enter pa
00402100: 73 73 77 6F 72 64 3A 00 57 72 6F 6E 67 20 70 61 ssword:.Wrong pa
00402110: 73 73 77 6F 72 64 0A 00 50 61 73 73 77 6F 72 64 ssword..Password
00402120: 20 4F 4B 0A 00 00 00 00 00 00 00 00 00 00 00 00 OK.............
00402130: 00 00 00 00 6F CB C4 5B 00 00 00 00 02 00 00 00 ....oEA[........
00402140: 48 00 00 00 14 22 00 00 14 14 00 00 00 00 00 00 H...."..........
00402150: 6F CB C4 5B 00 00 00 00 0C 00 00 00 14 00 00 00 oEA[............
Ah, now the password is no longer in the data section, and hackers can “rest”! But don’t jump to conclusions just yet. Let’s first display a list of all the sections present in the file:
> dumpbin passCompare2.exe
Summary
1000 .data
1000 .kpnc
1000 .rdata
1000 .reloc
1000 .rsrc
1000 .text
The unusual .kpnc section immediately grabs your attention. Let’s take a closer look and see what’s inside!
> dumpbin /SECTION:.kpnc /RAWDATA passCompare2.exe
RAW DATA #4
00404000: 6D 79 47 4F 4F 44 70 61 73 73 77 6F 72 64 0A 00 myGOODpassword..
There it is, the password! Some hiding spot… You could get creative and stash sensitive data in the uninitialized data section (.bss) or even the code section (.text)—not everyone will think to search there, and it won’t disrupt the program’s functionality. However, don’t forget about the ease of automated text string searches in binary files. No matter which section the password is in, a filter will find it effortlessly. The only challenge is identifying which of the many text strings is the key you’re looking for; you might need to sift through a dozen or more potential “candidates.”
Step Two: Getting Familiar with the Disassembler
Alright, we’ve got the password. But it’s so tedious to type it in from the keyboard every time we start the program! It would be great to hack it so that it doesn’t ask for a password at all, or so that any password entered is accepted as correct.
Thinking about hacking, are you? Well, it’s not that hard! What’s trickier is deciding exactly what to hack. The arsenal of tools available to hackers is extremely varied and vast, featuring everything from disassemblers and debuggers to API and message spies, file (port, registry) access monitors, and executable file unpackers, among others. It can be overwhelming for a beginner to navigate through all these tools!
However, spies, monitors, and unpackers are secondary background utilities, while the primary tools of a hacker are the debugger and disassembler.
A disassembler is suitable for examining compiled programs and is somewhat useful for analyzing pseudocompiled code. Given this, it should work for bypassing the password protection of passCompare1.exe. The main question is which disassembler to choose.
Not all disassemblers are created equal. There are “intelligent” ones capable of automatically identifying various constructs such as function prologues and epilogues, local variables, and cross-references. Then there are the “simple” ones, whose abilities are limited to merely translating machine code into assembly instructions.
It makes the most sense to use a smart disassembler (if available), but… let’s not rush and try to perform the entire analysis manually. Technology, of course, is a great tool, but it’s not always readily available, so it’s beneficial to learn how to work in field conditions. Moreover, dealing with a subpar disassembler highlights the benefits of a good one.
Let’s use the DUMPBIN utility, which we’ve already acquainted ourselves with. It’s a true “Swiss army knife” with numerous useful features, including a disassembler. We’ll disassemble the code section (which, as we remember, is named .text) and redirect the output to a file since it clearly won’t fit on the screen:
> dumpbin /SECTION:.text /DISASM passCompare1.exe > code-text.txt
Let’s take another look at the data section (or another one, depending on where the password is stored). See listing 2.
Let’s remember the found password: myGOODpassword
. Unlike Visual C++ 6.0, which Chris used, Visual C++ 2017 doesn’t refer to initialized variables by hexadecimal offset but directly substitutes the value into the code section. Therefore, we can try to locate the previously uncovered password in the disassembled listing through a simple context search using any text editor.
0040107D: B9 08 21 40 00 mov ecx,offset ??_C@_0BA@PCMCJPMK@myGOODpassword?6?$AA@00401082: 8A 10 mov dl,byte ptr [eax]00401084: 3A 11 cmp dl,byte ptr [ecx]00401086: 75 1A jne 004010A200401088: 84 D2 test dl,dl0040108A: 74 12 je 0040109E
The central part of this listing is responsible for comparing the values in the EAX and ECX registers. As we can see, the reference password is loaded into ECX in the first line of the listing, which means the user-entered password is in EAX. Then, a comparison is made, and the execution jumps to nearly the same locations: 0x4010A2 and 0x40109E. Let’s take a look at what’s there:
0040109E: 33 C0 xor eax,eax004010A0: EB 05 jmp 004010A7004010A2: 1B C0 sbb eax,eax004010A4: 83 C8 01 or eax,1004010A7: 85 C0 test eax,eax004010A9: 74 63 je 0040110E004010AB: 0F 1F 44 00 00 nop dword ptr [eax+eax]004010B0: 68 18 21 40 00 push offset ??_C@_0BA@EHHIHKNJ@Wrong?5password?6?$AA@004010B5: E8 56 FF FF FF call _printf
Here, the central role is played by the test
instruction located at offset 0x4010A7. If eax equals 0, the following JE command jumps to 0x40110E. Otherwise, the string “Wrong password:” is pushed to the top of the stack.
push offset ??_C@_0BA@EHHIHKNJ@Wrong?5password?6?$AA@
and following that is the invocation of a function with a descriptive name:
call _printf
So, a non-zero value in the EAX register indicates an incorrect password, while a zero indicates the password is correct.
Okay, let’s move on to analyzing the valid branch of the program, which is executed after jumping to 0x40110E. Here, we encounter an instruction that places the string “Password OK” on top of the stack, followed by a call to the _printf
procedure, which evidently outputs the string to the screen:
0040110E: 68 28 21 40 00 push offset ??_C@_0N@MBEFNJID@Password?5OK?6?$AA@00401113: E8 F8 FE FF FF call _printf
Here’s the operational consideration: if you replace the JE (Jump if Equal) instruction with JNE (Jump if Not Equal), the program will reject the correct password as incorrect and accept any incorrect password as correct. Furthermore, if you change TEST EAX,EAX to XOR EAX,EAX, the EAX register will always be set to zero after executing this command, regardless of the password entered.
The task at hand is a small one: locate these specific bytes in the executable file and make slight adjustments to them.
Step Three: Precision Surgery
Modifying an executable file directly is a serious matter. Due to the constraints of the existing code, we are limited to working with what’s already there. We can’t just expand or shift the instructions by removing “unnecessary parts,” as this would offset all other instructions while leaving pointer values and jump addresses unchanged, leading to incorrect targets.
Dealing with “removing spare parts” is actually quite simple—you just need to fill the code with NOP commands (whose opcode is 0x90, not 0x0 as many beginning coders mistakenly believe), which means a no-operation instruction (actually, NOP is just an alternative form of the instruction XCHG EAX, EAX—if you’re curious). The challenge is more significant when it comes to “relocation”! Fortunately, PE files always contain many “gaps” left from alignment, and these can be used to insert your own code or data.
But wouldn’t it be easier just to compile the modified assembly file after making the necessary changes? No, it’s not easier, and here’s why: if the assembler does not recognize pointers passed to a function (as we’ve seen, our disassembler couldn’t distinguish them from constants), it won’t properly adjust them. Consequently, the program won’t work.
You need to modify the program on the fly. The easiest way to do this is with the Hiew utility, which analyzes PE-format files, simplifying the search for the needed segment. Any version of this hex editor will work. For example, I’ve used version 6.86, which is quite compatible with Windows 10. Launch it by specifying the file name in the command line with hiew32
, press Enter twice, switch to assembly mode, and use the F5 key to jump to the required address. As we recall, the TEST command, which checks if the result is zero, was located at address 0x4010A7.
To enable Hiew to differentiate an address from an offset within the file, prefix it with a dot: .4010A7.
004010A7: 85 C0 test eax,eax004010A9: 74 63 je 0040110E
Ah, just what we need! Press the F3 key to switch Hiew into edit mode, move the cursor to the TEST EAX, EAX command, and by pressing the Enter key, replace it with XOR EAX, EAX.
004010A7: 33 C0 xor eax,eax004010A9: 74 63 je 0040110E

Pleased to see that the new team integrated seamlessly with the previous one, let’s press F9 to save the changes to the disk, exit Hiew, and then attempt to run the program by entering the first password that comes to mind:
>passCompare1
Enter password:Hello, hat!
Password OK
Success! The protection has been defeated! Now, what if Hiew couldn’t handle PE files? In that case, we’d have to rely on contextual search. Let’s look at the hex dump located to the left of the assembly commands in the disassembler. Searching for the sequence 85 C0 (the opcode for the TEST EAX,EAX command) alone wouldn’t be effective, as there could be hundreds of TEST commands in a program. However, the jump address is likely different in each branch of the program, making the substring TEST EAX,EAX/JE 0040110E potentially unique. We can try to find the corresponding code in the file: 85 C0 74 63 (in Hiew, you can do this by pressing F7).
Oops! We’ve found only one instance, which is exactly what we need. Now, let’s try modifying the file directly in hex mode without switching to assembly. Let’s also note that inverting the least significant bit of an opcode changes the jump condition to its opposite; for example, 74 JE (Jump if Equal) becomes 75 JNE (Jump if Not Equal).

Is it working?

It seems like the security system has completely lost it—it no longer recognizes the correct passwords, but happily accepts others. Wonderful!

2023.02.12 — Gateway Bleeding. Pentesting FHRP systems and hijacking network traffic
There are many ways to increase fault tolerance and reliability of corporate networks. Among other things, First Hop Redundancy Protocols (FHRP) are used for this…
Full article →
2022.04.04 — Elephants and their vulnerabilities. Most epic CVEs in PostgreSQL
Once a quarter, PostgreSQL publishes minor releases containing vulnerabilities. Sometimes, such bugs make it possible to make an unprivileged user a local king superuser. To fix them,…
Full article →
2022.04.04 — Fastest shot. Optimizing Blind SQL injection
Being employed with BI.ZONE, I have to exploit Blind SQL injection vulnerabilities on a regular basis. In fact, I encounter Blind-based cases even more frequently…
Full article →
2023.02.13 — First Contact: Attacks on Google Pay, Samsung Pay, and Apple Pay
Electronic wallets, such as Google Pay, Samsung Pay, and Apple Pay, are considered the most advanced and secure payment tools. However, these systems are also…
Full article →
2023.01.22 — Top 5 Ways to Use a VPN for Enhanced Online Privacy and Security
This is an external third-party advertising publication. In this period when technology is at its highest level, the importance of privacy and security has grown like never…
Full article →
2022.01.13 — Step by Step. Automating multistep attacks in Burp Suite
When you attack a web app, you sometimes have to perform a certain sequence of actions multiple times (e.g. brute-force a password or the second authentication factor, repeatedly…
Full article →
2023.07.29 — Invisible device. Penetrating into a local network with an 'undetectable' hacker gadget
Unauthorized access to someone else's device can be gained not only through a USB port, but also via an Ethernet connection - after all, Ethernet sockets…
Full article →
2022.01.13 — Bug in Laravel. Disassembling an exploit that allows RCE in a popular PHP framework
Bad news: the Ignition library shipped with the Laravel PHP web framework contains a vulnerability. The bug enables unauthorized users to execute arbitrary code. This article examines…
Full article →
2022.02.09 — Dangerous developments: An overview of vulnerabilities in coding services
Development and workflow management tools represent an entire class of programs whose vulnerabilities and misconfigs can turn into a real trouble for a company using such software. For…
Full article →
2022.06.01 — Quarrel on the heap. Heap exploitation on a vulnerable SOAP server in Linux
This paper discusses a challenging CTF-like task. Your goal is to get remote code execution on a SOAP server. All exploitation primitives are involved with…
Full article →