Concept

In x86/x86_32, arguments are usually pushed onto the stack right-to-left before a call. The last value pushed becomes the first parameter inside the function (at [ebp+8]). On __thiscall member functions, ecx holds the this pointer and is not pushed.

Example from encrypt_func

.text:003E37C8 push    1                  ; enable_xor (last pushed -> highest arg)
.text:003E37CD lea     eax, [ebp+var_28]
.text:003E37CD push    eax                ; arg_10
.text:003E37CE push    [ebp+var_34]        ; buffer_2_pointer
.text:003E37D1 lea     ecx, [esi+0C40h]    ; ecx = this (not pushed)
.text:003E37D7 push    [ebp+var_38]        ; buffer_1_pointer
.text:003E37DA push    0A8h                ; plaintext_buffer_length
.text:003E37DF push    ebx                 ; plaintext_buffer_pointer (first stack arg)
.text:003E37E0 call    encrypt_func

Inside encrypt_func, the declaration is:

plaintext_buffer_pointer= dword ptr  8
plaintext_buffer_length= dword ptr  0Ch
buffer_1_pointer= dword ptr  10h
buffer_2_pointer= dword ptr  14h
arg_10= dword ptr  18h
enable_xor= byte ptr  1Ch

Because ebx was pushed last, it sits at [ebp+8] as plaintext_buffer_pointer. 0A8h is at [ebp+0Ch], etc.

Takeaway

  • Work backwards from the call instruction.
  • The final push before call is parameter 1 at [ebp+8].
  • Watch for lea ecx, ... before the call — that usually signals __thiscall.