Skip to content

Latest commit

 

History

History
102 lines (90 loc) · 6.19 KB

File metadata and controls

102 lines (90 loc) · 6.19 KB

8.04 Get Element - WIP

Now let's look at a function that has to do with looking data up in the table. Remember, it doesn't matter which functions we reverse first. I'm choosing based on what I think will be a good order to go in.

There are multiple functions that might get something from the table: RtlEnumerateGenericTable, RtlGetElementGenericTable, RtlLookupElementGenericTable, and some others. Based on the names, I think RtlGetElementGenericTable or RtlLookupElementGenericTable will be the easiest. Out of those two I'd guess RtlGetElementGenericTable will be the simplest. I'd guess that it simply looks in the table at the index passed to the function. This is just a guess, I don't even know what parameters it takes yet.

Disassembly of RtlGetElementGenericTable:

7FF8BBD1C1B0    MOV QWORD PTR SS:[RSP+0x8], RBX
7FF8BBD1C1B5    MOV R10D, DWORD PTR DS:[RCX+0x20]
7FF8BBD1C1B9    LEA R11D, QWORD PTR DS:[RDX+0x1]
7FF8BBD1C1BD    MOV R8, QWORD PTR DS:[RCX+0x18]
7FF8BBD1C1C1    OR EBX, 0xFFFFFFFF
7FF8BBD1C1C4    MOV R9D, R11D
7FF8BBD1C1C7    CMP EDX, EBX
7FF8BBD1C1C9    JE ntdll.7FF8BBD1C21C
7FF8BBD1C1CB    MOV EAX, DWORD PTR DS:[RCX+0x24]
7FF8BBD1C1CE    CMP R11D, EAX
7FF8BBD1C1D1    JA ntdll.7FF8BBD1C21C
7FF8BBD1C1D3    CMP R11D, R10D
7FF8BBD1C1D6    JE ntdll.7FF8BBD1C200
7FF8BBD1C1D8    JB ntdll.7FF8BBD66C7A
7FF8BBD1C1DE    SUB EAX, R11D
7FF8BBD1C1E1    MOV EDX, R11D
7FF8BBD1C1E4    SUB EDX, R10D
7FF8BBD1C1E7    INC EAX
7FF8BBD1C1E9    CMP EDX, EAX
7FF8BBD1C1EB    JA ntdll.7FF8BBD1C20A
7FF8BBD1C1ED    TEST EDX, EDX
7FF8BBD1C1EF    JE ntdll.7FF8BBD1C1F8
7FF8BBD1C1F1    MOV R8, QWORD PTR DS:[R8]
7FF8BBD1C1F4    ADD EDX, EBX
7FF8BBD1C1F6    JNE ntdll.7FF8BBD1C1F1
7FF8BBD1C1F8    MOV QWORD PTR DS:[RCX+0x18], R8
7FF8BBD1C1FC    MOV DWORD PTR DS:[RCX+0x20], R11D
7FF8BBD1C200    LEA RAX, QWORD PTR DS:[R8+0x10]
7FF8BBD1C204    MOV RBX, QWORD PTR SS:[RSP+0x8]
7FF8BBD1C209    RET
7FF8BBD1C20A    LEA R8, QWORD PTR DS:[RCX+0x8]
7FF8BBD1C20E    TEST EAX, EAX
7FF8BBD1C210    JE ntdll.7FF8BBD1C1F8
7FF8BBD1C212    MOV R8, QWORD PTR DS:[R8+0x8]
7FF8BBD1C216    ADD EAX, EBX
7FF8BBD1C218    JE ntdll.7FF8BBD1C1F8
7FF8BBD1C21A    JMP ntdll.7FF8BBD1C212
7FF8BBD1C21C    XOR EAX, EAX
7FF8BBD1C21E    JMP ntdll.7FF8BBD1C204

This function is clearly going to be more of a challenge than the others.

General Overview

When a function is pretty involved, I always like going through it and identifying the obvious stuff, then going back through and analyzing more closely. The first thing we'll do is check for function parameters.

  • RCX is used like a data structure (MOV EAX, DWORD PTR DS:[RCX+0x24]), so it's probably the table.
  • RDX is also used, but with an offset of +0x1 (LEA R11D, QWORD PTR DS:[RDX+0x1]). RDX is probably not a data structure, maybe a pointer, we'll figure that out later. Just note that RDX is used.
  • R8 and R9 are overwritten, so they were probably not parameters. Now we know that this function only takes 2 parameters. The first one being the table, the second one is only a guess but it could be an index into the table. That quick analysis is going to make things easier, but let's keep looking around.

Something I like doing is identifying loops. Just by scanning this function I can tell it's very likely there is a loop because of the JMP ntdl.### instructions.

The first set of jumps are JCC ntdll.7FF8BBD1C21C. ntdll.7FF8BBD1C21C zeroes EAX, then jumps to ntdll.7FF8BBD1C204 which does something to RBX then returns. This is probably because of a condition "failing", so we'll keep that in mind. I say this is may be a fail condition because it's early on in the function and it jumps immediately to a ret. Also the return value is set to 0.

There is some other mildly interesting jumps but nothing to note. We'll just examine those when we get there. With that said, I think this is a good time to get started with the full analysis.

If you don't already, I'd highly recommend writing notes and/or pseudo code as you are reverse engineering.

Part 1

Let's focus on the following code:

MOV QWORD PTR SS:[RSP+0x8], RBX
MOV R10D, DWORD PTR DS:[RCX+0x20]
LEA R11D, QWORD PTR DS:[RDX+0x1]
MOV R8, QWORD PTR DS:[RCX+0x18]
OR EBX, 0xFFFFFFFF
MOV R9D, R11D
CMP EDX, EBX
JE ntdll.7FF8BBD1C21C
MOV EAX, DWORD PTR DS:[RCX+0x24]
CMP R11D, EAX
JA ntdll.7FF8BBD1C21C
  • RBX is preserved/saved on the stack.
  • Member4 of the table is moved into R10D.
  • LEA R11D, QWORD PTR DS:[RDX+0x1] is a very important instruction. It's essentially R11D = RDX + 1. This might be done instead of using the INC instruction to preserve the value of RDX.
  • Next, the fourth member of the table is moved into R8.
  • OR EBX, 0xFFFFFFFF sets EBX to -1.
  • R11D (RDX + 1) is moved into R9D.
  • EDX (second parameter) is then compared to EBX (-1). This tells us a few things. First, we can be even more sure that EDX is an index. Also, previously the NumberOfElements member in the table data structure was accessed with the offset of +0x24 in RtlNumberGenericTableElements. You would expect it to be accessed with the offset of +0x20 because it's the fifth member. The fact that EDX is used instead of RBX could mean that the fifth parameter is not 8 bytes, instead it's only 4 bytes. We still can't be certain, but it's nice to know that it's less likely that we're wrong.
  • If EDX (the second parameter) is -1, then jump to ntdll.7FF8BBD1C21C which is the fail condition.
  • MOV EAX, DWORD PTR DS:[RCX+0x24] moves the NumberOfElements member into EAX.
  • R11D (RDX + 1) is then checked if it's greater than EAX (NumberOfElements). This is bounds checking to make sure the index isn't greater than the range of the table. This also tells us another piece of useful information, the index is zero-based. This means it starts from index zero, not 1, similar to a conventional array. This makes sense because the number of elements is not zero-based (0 elements would mean it's empty). Again, this is just like an array.
  • If R11D is greater than EAX, then it jumps to the fail condition. Here is a pro tip, pay attention to the jump types. JA tells us that it's unsigned.

Here is some pseudo code to ease your mind:

ULONG adjustedIndex = index + 1;
if(index == -1 || adjustedIndex > Table->NumberOfElements){
    return 0;
}

Part 2