In 32-bit x86 code compiled with a stack-based calling convention x86 Calling Conventions: cdecl vs stdcall (cdecl or stdcall), arguments are accessed as positive offsets from ebp. IDA Pro names them arg_0, arg_4, arg_8, arg_C, etc., where the number is the byte offset from ebp.
Stack Layout After Prologue
[ebp] saved old ebp
[ebp+4] return address
[ebp+8] arg_0 ← first argument
[ebp+0Ch] arg_4 ← second argument
[ebp+10h] arg_8 ← third argument
[ebp+14h] arg_C ← fourth argument
[ebp+18h] arg_10 ← fifth argument
[ebp+1Ch] arg_14 ← sixth argument
Mental Shortcut
Divide the hex offset by 4 (size of a dword), then add 1:
| Offset | Calculation | Argument position |
|---|---|---|
arg_0 |
0/4 + 1 | 1st |
arg_4 |
4/4 + 1 | 2nd |
arg_8 |
8/4 + 1 | 3rd |
arg_C |
12/4 + 1 | 4th |
arg_10 |
16/4 + 1 | 5th |
arg_14 |
20/4 + 1 | 6th |
Reading an Instruction
mov esi, [ebp+arg_C]
arg_C means [ebp+0xC], which is [ebp+12]. Using the shortcut: 12/4 + 1 = 4. So esi is loaded with the 4th argument.
See Reading Function Arguments from Disassembly for more on this pattern. The layout above assumes a standard Stack Frames and Function Prologue/Epilogue frame after the push ebp; mov ebp, esp sequence.
Type Matters for the Data, Not the Address
mov esi, [ebp+arg_C] ; loads 4 bytes (dword)
cmp [ebp+arg_14], 0 ; compares 1 byte because arg_14 was defined as byte ptr
The address is still calculated the same way. The byte ptr / dword ptr annotation tells you how many bytes to read at that address.
Example Application
From the real snippet:
mov esi, [ebp+arg_C] ; esi = 4th argument
mov edi, [ebp+arg_4] ; edi = 2nd argument
add esi, edi ; esi = 4th_arg + 2nd_arg
cmp [ebp+arg_14], 0 ; compare 6th argument against zero
So in C-like pseudocode:
void encrypt_func(..., int arg2, ..., int arg4, ..., char arg6)
{
int esi = arg4;
int edi = arg2;
esi = esi + edi;
if (arg6 != 0) { ... }
}