Table of Contents
- 1. Introduction
- 2. Environment Setup
- 3. Program Implementation
- 4. Assembly Source Code
- 5. Program Execution
- 6. Reverse Engineering in Cutter
- 7. Binary Patching
- 8. Patched Program Execution
- 9. Conclusion
1. Introduction {#introduction}
In this report, I implemented a simple password verification program using x86 32-bit assembly in SASM, then analyzed and modified its behavior using Cutter. The goal was to understand how a basic authentication mechanism works at the assembly level and how it can be altered through binary patching.
The program simulates a login system that prompts the user for a password, compares it against a hardcoded credential, and displays either a success or failure message. After building and testing the program, I loaded the compiled executable into Cutter, identified the verification logic, and patched it so that access is always granted regardless of the input.
This exercise demonstrates both low-level program design and practical reverse engineering techniques.

2. Environment Setup {#environment}
The development and analysis environment used for this task:
Component Tool
────────────────────────────────────────────
Operating System Windows
Assembler SASM (NASM x86)
Compiler / Linker MinGW (via SASM)
Reverse Engineering Cutter
The program was written and executed in SASM, which allows integration with C standard library functions such as printf, scanf, and strcmp.
3. Program Implementation {#implementation}
The program is structured using three sections: .data, .bss, and .text.
.data section -> stores all constant strings:
motd → "Welcome to the Secure Login System"
prompt → "Enter password: "
input_fmt → "%63s" (format string for scanf)
correct_pw → "rit123" (hardcoded password)
success_msg → "Access granted. Correct password."
failure_msg → "Access denied. Incorrect password."
.bss section -> reserves memory for runtime input:
user_input resb 64 (64-byte buffer for entered password)
.text section -> contains the program logic:
1. Print welcome message (motd)
2. Print input prompt
3. Read user input via scanf into user_input buffer
4. Compare user_input with correct_pw using strcmp
5. Check eax result:
eax == 0 → strings match → print success
eax != 0 → strings differ → print failure
4. Assembly Source Code {#source}
global main
extern printf
extern scanf
extern strcmp
section .data
motd db "Welcome to the Secure Login System", 10, 0
prompt db "Enter password: ", 0
input_fmt db "%63s", 0
correct_pw db "rit123", 0
success_msg db "Access granted. Correct password.", 10, 0
failure_msg db "Access denied. Incorrect password.", 10, 0
section .bss
user_input resb 64
section .text
main:
push ebp
mov ebp, esp
push motd
call printf
add esp, 4
push prompt
call printf
add esp, 4
push user_input
push input_fmt
call scanf
add esp, 8
push correct_pw
push user_input
call strcmp
add esp, 8
cmp eax, 0
jne wrong_password
correct_password:
push success_msg
call printf
add esp, 4
jmp program_end
wrong_password:
push failure_msg
call printf
add esp, 4
program_end:
mov eax, 0
mov esp, ebp
pop ebp
ret
The key logic is at the end of main: strcmp returns 0 if the strings match. The cmp eax, 0 + jne wrong_password pair is the single instruction controlling which branch executes, this is what I will target during patching.
5. Program Execution {#execution}
The program was tested in SASM using both correct and incorrect inputs.
Correct input (rit123):

Incorrect input (test):

Both cases confirm that the program correctly handles input and routes execution based on the strcmp result.
6. Reverse Engineering in Cutter {#reverse-engineering}
After compiling the program in SASM, I opened the generated login.exe in Cutter for static analysis.
Loading the binary:

Cutter auto-analyzed the binary and populated the Functions panel with all identified functions.
Navigating to main:
I located main in the Functions panel and opened it in the disassembly view. The full program logic is contained here.

Identifying the authentication logic:
The critical section is the password comparison block:
push str.rit123 ; correct_pw
push 0x405020 ; user_input
call sub.msvcrt.dll_strcmp
add esp, 8
cmp eax, 0 ; is result zero? (strings equal?)
jne 0x4013e5 ; if NOT equal → jump to failure path
Control flow:
strcmp result → eax
↓
cmp eax, 0
├── eax == 0 (match) → fall through → success path → print "Access granted"
│ → jmp program_end
└── eax != 0 (no match) → jne → 0x4013e5 → failure path → print "Access denied"
The jne 0x4013e5 at address 0x004013d4 is the single instruction that enforces the password check. Removing it means execution always falls through to the success path.
7. Binary Patching {#patching}
To bypass the authentication check, I targeted the jne instruction at 0x004013d4.
Editing the instruction:
I right-clicked the jne → Edit → Instruction. The edit dialog showed:

I replaced jne 0x4013e5 with nop and checked Fill all remaining bytes with NOP opcodes. Since jne rel8 encodes as 2 bytes (75 0f), this fills both bytes with 0x90 (NOP).
Result in disassembly after patch:

; Before patch:
0x004013d4 jne 0x4013e5 ; 75 0f -> jumps to failure on wrong input
; After patch:
0x004013d4 nop ; 90
0x004013d5 nop ; 90 -> no jump, execution always continues
Effect of the patch:
Execution now always falls through to the success path.
The failure block at 0x4013e5 remains in the binary but is unreachable.
After applying the patch, I saved the modified binary from Cutter using File → Save.
8. Patched Program Execution {#patched-execution}
The patched executable was tested from the command line using both wrong and correct inputs to confirm the patch worked in both cases.

Run 1: Input = wrong → Access denied. Incorrect password. (original binary)
Run 2: Input = rit123 → Access granted. Correct password. (original binary)
Run 3: Input = Patched → Access granted. Correct password. (patched binary)
Any input including completely wrong ones, now returns the success message. The authentication check no longer exists in the execution path.
9. Conclusion {#conclusion}
This task demonstrated how a simple authentication mechanism works at the assembly level and how it can be analyzed and bypassed using reverse engineering tools.
By writing the program myself first, I understood exactly which instruction controlled the outcome: a single jne following a cmp eax, 0 after strcmp. That one instruction is the entire enforcement point of the password check.
Replacing it with two nop bytes removed the conditional entirely and permanently redirected execution to the success path regardless of input.
Key takeaways:
Authentication at the binary level often reduces to a single branch instruction
jne / je are the most common targets in password bypass patching
NOP patching is simple and effective when protections are minimal
Understanding your own code first makes reverse engineering far easier
This also reinforces why compiled binaries should not be treated as tamper-proof. Without additional protections such as code signing, integrity checks, or obfuscation, any conditional branch in a binary can be trivially removed with a hex editor or a tool like Cutter.