There are very common patterns that you will find in code that denote structure usage.
If you have a pointer which is dereferenced at some non-zero offset, you are probably dealing with a structure. Look for patterns like:
mov eax, [ebp-8] ; Load a local variable into eax
mov ecx, [eax+8] ; **Dereference a dword at eax+8**
In this example, we have a variable that contains a pointer, but we care about the contents of memory at some specific offset ahead of the pointer. This is exactly how structures are used: We get a pointer to the structure, and then dereference the pointer plus some offset to access a specific member. In C, the syntax for this is:
Side note: Do not confuse dereferencing at the offset of some variable with dereferencing at offsets of the stack pointer or the frame pointer (
ebp). Of course, you could think of the local variables and function arguments as being one large structure, but in C, they are not explicitly defined as such.
More subtle pointer offsets:
You don't actually need to dereference anything to detect a structure member. For example:
mov eax, [ebp-8] ; Load a local variable into eax
push 30h ; num = 30h
push aSampleString ; src = "Sample String"
add eax, 0Ch
push eax ; dst = eax + 0xC
In this example, we are copying up to 0x30 characters from some source string to
eax + 0xC (see strncpy). This tells us that
eax probably points to a structure with a string buffer (of at least 0x30 bytes) at offset 0xC. For example, the structure may look something like:
DWORD a; // +0x0
DWORD b; // +0x4
DWORD c; // +0x8
CHAR d[0x30]; // +0xC
In which case, the sample code would look like:
strncpy(&pMyStruct->d, "Sample String", sizeof(pMyStruct->d));
Side note: It is possible (though unlikely) that we could be copying to a large string buffer at offset +0xC, but you would be able to determine this through context. If, say, offset +0x8 were an integer, for example, then it's definitely a struct. But if we copied a string of fixed length 0xC to address
eax then copied another string to address
eax+0xC, it's probably one giant string.
All reads / all writes:
Let's say you have a struct (not a pointer to a struct) as a local variable your stack. Most of the time, IDA doesn't know the difference between a struct on the stack or a bunch of individual local variables. But a huge tip-off that you're dealing with a structure is if you only ever read from a variable without writing to it, or (less so) if you only write to a variable without reading from it. Here's an example of each:
lea eax, [ebp+var_58] ; Load THE ADDRESS of a local variable into eax
mov eax, [ebp+var_54] ; Let's say we've never touched var_54 before...
test eax, eax ; ...But we're checking its value!
In this example, we're reading from
var_54 without ever writing anything to it (within this function). This probably means that it is a member of a structure which was accessed from some other function call. In this example, it's implied that
var_58 might be the start of that structure, since its address is pushed as the argument to
some_function. You can verify this by following the logic of
some_function and checking if its argument is ever dereferenced (and modified) at offset +0x4. Of course, this doesn't necessarily have to happen in
some_function -- it could happen in one of its child functions, or one of its child functions, etc.
A similar example exists for writing:
xor eax, eax
mov [ebp+var_28], eax ; Let's say this is the *only* time var_28 is touched
lea eax, [ebp+var_30]
When you see local variables being set and then never referenced again, you can't just forget about them, because they could very likely be members of a structure which is passed on to another function. This example implies that a structure (which starts at
var_30) is written to at offset +0x8 before the address of that structure is passed to
Both of these examples in C might look like:
if (myStruct.member_at_offset_4) ...
myStruct.member_at_offset_8 = 0;
Side note: Although each of these examples used local variables, the same logic applies to globals.
Documented functions that expect structures:
This one's probably obvious, and IDA will handle this for you almost all of the time, but an easy way to know when you have a structure in your code is if you call a documented function that expects a certain structure. For example,
CreateProcessW expects a pointer to a
STARTUPINFOW structure. This one shouldn't require an example.
How do I know if these patterns actually indicate structure usage?
One final point I want to make is that in all of these cases, yes, technically, the author of the program could written their code without the use of structures. They also could have written their code by defining every function as
__declspec(naked) with a large
__asm inline. You'd never be able to tell. But arguably, it doesn't matter. If there are logical groups of values that are stored contiguously in memory and passed from function to function, it is still meaningful to annotate them as structures. Almost all of the time, this is how the author wrote their code anyway.
If you need me to elaborate on anything, let me know.