This post is about how to read the LPINTERNET_BUFFERSA struct used by the Windows API HttpSendRequestExA. The background behind this post is answering an StackOverflow question but with a more detailed explanation.
To perform this example, instead of writing an application I will use Internet Explorer and I will use FRIDA on it. That said, the definition of the struct used in HttpSendRequestExA/W is:
typedef struct _INTERNET_BUFFERSA {
DWORD dwStructSize;
struct _INTERNET_BUFFERSA *Next;
LPCSTR lpcszHeader;
DWORD dwHeadersLength;
DWORD dwHeadersTotal;
LPVOID lpvBuffer;
DWORD dwBufferLength;
DWORD dwBufferTotal;
DWORD dwOffsetLow;
DWORD dwOffsetHigh;
} INTERNET_BUFFERSA, *LPINTERNET_BUFFERSA;
There are two things to note here, the first one is that the first member is the structure size and the second member of the struct is a pointer to the next one. A second thing to take into account is that LPCSTR is going to be a LPCWSTR if HttpSendRequestExW is used instead.
That said, this structure is pretty simple to parse, there is only three tricky members here:
- LPCSTR size varies depending on the Process.pointerSize()
- LPVOID size varies depending on the Process.pointerSize()
* Next is a pointer to the next structure, so it will also vary depending on the Process.pointerSize()
With this in mind for a 32-bit process where Process.pointerSize=4 the offsets are:
DWORD dwStructSize; // 0
struct _INTERNET_BUFFERSA *Next; // 4
LPCSTR lpcszHeader; // 8
DWORD dwHeadersLength; // 12
DWORD dwHeadersTotal; // 16
LPVOID lpvBuffer; // 20
DWORD dwBufferLength; // 24
DWORD dwBufferTotal; // 28
DWORD dwOffsetLow; // 32
DWORD dwOffsetHigh; // 36
If the calculations are correct, dwStructSize will be 40 for a 32-bit process or 0x28 in hex. The following script is able to intercept correctly the HttpSendRequestW structure for a 32-bit process:
Interceptor.attach(Module.getExportByName(null, "HttpSendRequestExW"), {
onEnter (args) {
let internetBufferStruct = args[1];
console.log("Struct size: " + internetBufferStruct.readPointer());
console.log("*Next: " + internetBufferStruct.add(Process.pointerSize).readPointer());
console.log("lpcszHeader: " + internetBufferStruct.add(Process.pointerSize * 2).readPointer());
console.log("dwHeadersLength: " + internetBufferStruct.add(12).readPointer());
console.log("dwHeadersTotal: " + internetBufferStruct.add(16).readPointer());
let dwBufferLength = parseInt(internetBufferStruct.add(24).readPointer());
console.log("lpvBuffer: " + internetBufferStruct.add(Process.pointerSize * 5).readCString(dwBufferLength));
console.log("dwBufferLength: " + dwBufferLength);
console.log("dwBufferTotal: " + internetBufferStruct.add(28).readPointer());
console.log("dwOffsetLow: " + internetBufferStruct.add(32).readPointer());
console.log("dwOffsetHigh: " + internetBufferStruct.add(36).readPointer());
}
});
And this is the result in a 32-bit process where as predicted the first member dwStructSize is 40(0x28) as predicted:
For a 64-bit process the only changes required are the differences in offsets generated by every pointer member and adjust the other members accordingly.