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) { ... }
}