There are very common patterns that you will find in code that denote structure usage.
Dereferencing offsets:
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: pMyStruct->member_at_offset_8
.
Side note: Do not confuse dereferencing at the offset of some variable with dereferencing at offsets of the stack pointer or the frame pointer (esp
or 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
call strncpy
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:
struct _MYSTRUCT
{
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
push eax
call some_function
mov eax, [ebp+var_54] ; Let's say we've never touched var_54 before...
test eax, eax ; ...But we're checking its value!
jz somewhere
...
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]
push eax
call some_other_function
...
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 some_other_function
.
Both of these examples in C might look like:
some_function(&myStruct);
if (myStruct.member_at_offset_4) ...
and
myStruct.member_at_offset_8 = 0;
some_other_function(&myStruct);
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.