read a character from a string via inline assembler in visual studio - visual-studio

I'm having a problem with the visual studio inline assembler and it doesn't seem to be loading the right values. I declare a C-string like so:
const char* str = "1235";
Then I access the string like so in asm:
movzx ebx, byte ptr str[esi]
the problem is that instead of loading 49 into ebx (ascii code for '1') it loads 0. I'm sure that esi is the right index because of extensive debugging. am I loading the string wrong?

The instruction
movzx ebx, byte ptr str[esi]
says to read the byte that is esi bytes after the start of str. It is the assembly language equivalent of
ebx = *(unsigned char*)(&str) + esi);
In your case, str is a pointer variable, so str[0] is the low 8 bits of the pointer, str[1] is bits 8-15, str[2] is bits 16-23, and str[3] is bits 24-31. Any value larger than 3 is a buffer overflow because you are reading memory that is not part of str.
What you actually want to do to load the str variable into a register, and then do indexed addressing from that register.
mov ebx, str
movzx ebx, byte ptr [ebx+esi]
In pictures:
+----+
1003 | 12 |
+----+
1002 | 34 |
+----+
1001 | 56 |
+----+
str 1000 | 78 |
+----+
+----+
1234567D | 00 |
+----+
1234567C | 45 |
+----+
1234567B | 44 |
+----+
1234567A | 43 |
+----+
12345679 | 42 |
+----+
12345678 | 41 |
+----+
You are trying to read byte str+esi, which reads part of the variable str. If esi is greater than 3, you are reading past the end of str.
What you actually want to do is read the thing that str points to. This means that you need to load str into a register (I chose ebx), then add esi to the result, then access the byte there.

Try:
movzx ebx, byte ptr str[esi]

Related

Access violation while requesting memory in getmem.inc

I'm trying to understand an access violation (c0000005) in my C++ program build with "Codegear C++ Builder 2009"
I have catched the access violation with procdump and analysed it with WinDbg.
Here is the information i gathered with WinDbg.:
Callstack:
# ChildEBP RetAddr Args to Child
>00 0d98f960 004b3728 0001e000 108c6af0 0001e000 MMIServer!SystemSysGetMem$qqri+0x316 [GETMEM.INC # 2015]
01 0d98f978 004b443f 0d98f988 004890b8 108d5b20 MMIServer!SysReallocMem+0x2dc [GETMEM.INC # 3404]
02 0d98f998 0048910c 0d98f9f4 108c6af0 00482730 MMIServer!ReallocMem+0x13 [System.pas # 3521]
03 0d98f9ac 00488a40 09b60b58 108c6af0 0000f000 MMIServer!TMemoryStreamWrite+0x30 [Classes.pas # 6181]
04 0d98f9bc 00488b0e 0d98fa04 00488b3c 0d98f9f4 MMIServer!ClassesTStreamWriteBuffer+0x18 [Classes.pas # 5789]
05 0d98f9f4 0046d910 0000fc40 00000000 0d98fa10 MMIServer!ClassesTStreamCopyFrom+0xae [Classes.pas # 5814]
06 0d98fa38 0046dc85 0d98faec 0046dcec 0d98fad8 MMIServer!TOPToSoapDomConvertDOMToStream+0x80 [..\..\Patches\BDS2009\TByteDynArrayThroughSOAP\OPToSOAPDomConv.pas # 895]
07 0d98fad8 0052c272 01ca3930 10680b90 10d4daa8 MMIServer!TOPToSoapDomConvertMakeResponse+0x31d [..\..\Patches\BDS2009\TByteDynArrayThroughSOAP\OPToSOAPDomConv.pas # 987]
08 0d98fb94 0052eff9 0d98fcfb 10680b90 10680870 MMIServer!TSoapPascalInvokerInvoke+0x34e [SOAPPasInv.pas # 230]
09 0d98fc24 00526edb 0d98fcfb 10680b90 10680870 MMIServer!THTTPSoapPascalInvokerDispatchSOAP+0x20d [soaphttppasinv.pas # 82]
0a 0d98fd2c 00566d5b 10d1fee0 0d98fd6c 00566d83 MMIServer!THTTPSoapDispatcherDispatchRequest+0x28b [WebBrokerSOAP.pas # 223]
0b 0d98fd5c 00566e88 00000000 10d1fee0 0d98fda4 MMIServer!DispatchHandler+0x8b [HTTPApp.pas # 1511]
0c 0d98fd98 00567063 0046b308 0d98fdb0 0046b321 MMIServer!TCustomWebDispatcherDispatchAction+0xf0 [HTTPApp.pas # 1546]
0d 0d98fe10 0046b205 08dae450 02e3f360 09b21b70 MMIServer!TCustomWebDispatcherHandleRequest+0xb [HTTPApp.pas # 1594]
0e 0d98fe28 005b6e1f 08dae450 0d98fe40 005b6e43 MMIServer!TIdHTTPWebBrokerBridgeDoCommandGet+0x2d [..\..\Patches\Indy10\IdHTTPWebBrokerBridge.pas # 964]
0f 0d98fefc 005d86be 01c23370 005c7939 005c7783 MMIServer!IdcustomhttpserverTIdCustomHTTPServerDoExecute$qqrp20IdcontextTIdContext+0x683
10 0d98ff70 0048da71 0d98ff84 0048da7b 0d98ffa0 MMIServer!IdcontextTIdContextRun$qqrv+0x12
11 0d98ffa0 004b63f2 0d98ffdc 004b5e74 0d98ffb4 MMIServer!ThreadProc+0x45 [Classes.pas # 10892]
12 0d98ffb4 7c80b729 08b3e090 030afabc 00000000 MMIServer!ThreadWrapper+0x2a [System.pas # 13819]
CodeGear\RAD Studio\6.0\source\Win32\rtl\sys\getmem.inc:
2004 #GotBinAndGroup:
2005 {ebx = block size, ecx = bin number, edx = group number}
2006 push esi
2007 push edi
2008 {Get a pointer to the bin in edi}
2009 lea edi, [MediumBlockBins + ecx * 8]
2010 {Get the free block in esi}
2011 mov esi, TMediumFreeBlock[edi].NextFreeBlock
2012 {Remove the first block from the linked list (LIFO)}
2013 mov eax, TMediumFreeBlock[esi].NextFreeBlock
2014 mov TMediumFreeBlock[edi].NextFreeBlock, eax
>2015 mov TMediumFreeBlock[eax].PreviousFreeBlock, edi
2016 {Is this bin now empty?}
2017 cmp edi, eax
2018 jne #MediumBinNotEmptyForMedium
2019 {eax = bin group number, ecx = bin number, edi = #bin, esi = free block, ebx = block size}
2020 {Flag this bin as empty}
2021 mov eax, -2
2022 rol eax, cl
2023 and dword ptr [MediumBlockBinBitmaps + edx * 4], eax
2024 jnz #MediumBinNotEmptyForMedium
2025 {Flag the group as empty}
2026 btr MediumBlockBinGroupBitmap, edx
2027 #MediumBinNotEmptyForMedium:
Disassembly:
004b31d4 56 push esi
004b31d5 57 push edi
004b31d6 8d3ccd00be3201 lea edi,MMIServer!NeverSleepOnMMThreadContention+0x1ef (0132be00)[ecx*8]
004b31dd 8b7704 mov esi,dword ptr [edi+4]
004b31e0 8b4604 mov eax,dword ptr [esi+4]
004b31e3 894704 mov dword ptr [edi+4],eax
>004b31e6 8938 mov dword ptr [eax],edi ds:0023:00000000=????????
004b31e8 39c7 cmp edi,eax
004b31ea 7517 jne MMIServer!SystemSysGetMem$qqri+0x333 (004b3203)
004b31ec b8feffffff mov eax,0FFFFFFFEh
004b31f1 d3c0 rol eax,cl
004b31f3 21049580bd3201 and dword ptr MMIServer!NeverSleepOnMMThreadContention+0x16f (0132bd80)[edx*4],eax
004b31fa 7507 jne MMIServer!SystemSysGetMem$qqri+0x333 (004b3203)
004b31fc 0fb3157cbd3201 btr dword ptr [MMIServer!NeverSleepOnMMThreadContention+0x16b (0132bd7c)],edx
My understanding of this piece of code of getmem.inc is:
There is a double-linked list of free memory blocks.
One block is taken from this list.
The Double Linked List is reconnected.
In drawing:
|----------|--------------------->|----------|--------------------->|----------|
| | NextFreeBlock | | NextFreeBlock | |
| 132d408 | | 11290132 | | 00000000 |
| |<---------------------| |<---------------------| |
|----------| PreviousFreeBlock |----------| PreviousFreeBlock |----------|
^ ^ ^
| | |
edi esi eax
|----------|--------------------->|----------| |----------|
| | NextFreeBlock | | | |
| 132d408 | | 00000000 | | 11290132 |
| |<---------------------| | | |
|----------| PreviousFreeBlock |----------| |----------|
^ ^ ^
| | |
edi eax esi
Some registers:
eax 0
ebx 1e030
ecx 2c1
edx 16
edi 132d408
esi 11290132
ebp 1e000
eip 4b31e6
esp d98f958
There is a NULL pointer in the Double Linked Lists of the free memory blocks.
When writing the PreviousFreeBlock to this address the access violation occurred.
How can there be a NULL pointer in the Double Linked Lists of the free memory blocks?
Was the memory already corrupted?
Have anyone experienced the same problem in getmem.inc?
What can i do to investigate this crash further?

Windows DLL user space calling process

I'm trying to understand how Windows recursively loads DLLs in user space.
Tracing kernel32.ReadProcessMemory as an example:
The first step for ReadProcessMemory is the IAT of kernel32:
00007FF901F6AFA0 | 48:FF25 21D20500 | jmp qword ptr ds:[<&ReadProcessMemory>] |
Which jmp's to kernelbase.ReadProcessMemory:
00007FF9002D22F0 | 48:83EC 48 | sub rsp,48 |
00007FF9002D22F4 | 48:8D4424 30 | lea rax,qword ptr ss:[rsp+30] |
00007FF9002D22F9 | 48:894424 20 | mov qword ptr ss:[rsp+20],rax |
00007FF9002D22FE | 48:FF15 C3521400 | call qword ptr ds:[<&ZwReadVirtualMemory>] |
<snip>
Which call's ntdll.ZwReadVirtualMemory:
00007FF902F5C840 | 4C:8BD1 | mov r10,rcx |
00007FF902F5C843 | B8 3F000000 | mov eax,3F | 3F:'?'
00007FF902F5C848 | F60425 0803FE7F 01 | test byte ptr ds:[7FFE0308],1 |
00007FF902F5C850 | 75 03 | jne ntdll.7FF902F5C855 |
00007FF902F5C852 | 0F05 | syscall |
00007FF902F5C854 | C3 | ret |
00007FF902F5C855 | CD 2E | int 2E |
00007FF902F5C857 | C3 | ret |
So the flow from user mode in this example is:
kernel32.ReadProcessMemory
kernelbase.ReadProcessMemory
ntdll.ZwReadVirtualMemory
The expectation would be that each of the above DLLs can 'find' the appropriate function based on their IAT/imported functions from other DLLs when they are loaded.
Using dumpbin and tracing the IMPORTS this is true for kernel32.ReadProcessMemory (where api-ms-win-core-memory-l1-1-0.dll is an ApiSet for kernelbase.dll):
api-ms-win-core-memory-l1-1-0.dll
180078178 Import Address Table
18009E120 Import Name Table
0 time date stamp
0 Index of first forwarder reference
35 VirtualQueryEx
<snip>
1C ReadProcessMemory
However, this is not true for kernelbase.dll - NtReadVirtualMemory is imported, however ZwReadVirtualMemory is not imported:
ntdll.dll
1801A67C8 Import Address Table
180262A48 Import Name Table
0 time date stamp
0 Index of first forwarder reference
893 __C_specific_handler
<snip>
205 NtReadVirtualMemory
So, my question is: during the DLL load process, how does kernelbase.dll identify the 'location' of ZwReadVirtualMemory if it isn't imported?
The ZwReadVirtualMemory function is being called by kernelbase.dll, so it must have been resolved/stored in the IAT at some point, but how does this happen technically?
Is there some indirection where the loader maps NtReadVirtualMemory to ZwReadVirtualMemory as these functions resolve to the same address?

Meaning of a Common String In Executables?

There appear to be some similar-looking long alphanumeric strings that commonly occur in Mach-O 64 bit executables and ELF 64-bit LSB executables among other symbols that are not alphanumeric:
cat /bin/bash | grep -c "AWAVAUATSH"
has 181 results, and
cat /usr/bin/gzip | grep -c "AWAVAUATSH"
has 9 results.
What are these strings?
Interesting question. Since I didn't know the answer, here are the steps I took to figure it out:
Where in the file does the string occur?
strings -otx /bin/gzip | grep AWAVAUATUSH
35e0 AWAVAUATUSH
69a0 AWAVAUATUSH
7920 AWAVAUATUSH
8900 AWAVAUATUSH
92a0 AWAVAUATUSH
Which section is that in?
readelf -WS /bin/gzip
There are 28 section headers, starting at offset 0x16860:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 0000000000400238 000238 00001c 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 000254 000020 00 A 0 0 4
[ 3] .note.gnu.build-id NOTE 0000000000400274 000274 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 000298 000038 00 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002d0 0002d0 000870 18 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400b40 000b40 000360 00 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400ea0 000ea0 0000b4 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400f58 000f58 000080 00 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400fd8 000fd8 000090 18 A 5 0 8
[10] .rela.plt RELA 0000000000401068 001068 0007e0 18 A 5 12 8
[11] .init PROGBITS 0000000000401848 001848 00001a 00 AX 0 0 4
[12] .plt PROGBITS 0000000000401870 001870 000550 10 AX 0 0 16
[13] .text PROGBITS 0000000000401dc0 001dc0 00f1ba 00 AX 0 0 16
[14] .fini PROGBITS 0000000000410f7c 010f7c 000009 00 AX 0 0 4
... etc.
From above output, we see that all instances of AWAVAUATUSH are in .text section (which covers [0x1dc0, 0x10f7a) offsets of the file.
Since this is .text, we expect to find executable instructions there. The address we are interested in is 0x401dc0 (.text address) + 0x35e0 (offset of AWAVAUATUSH in the file) - 0x1dc0 (offset of .text in the file) == 0x4035e0.
First, let's check that the above arithmetic is correct:
gdb -q /bin/gzip
(gdb) x/s 0x4035e0
0x4035e0: "AWAVAUATUSH\203\354HdH\213\004%("
Yes, it is. Next, what are the instructions there?
(gdb) x/20i 0x4035e0
0x4035e0: push %r15
0x4035e2: push %r14
0x4035e4: push %r13
0x4035e6: push %r12
0x4035e8: push %rbp
0x4035e9: push %rbx
0x4035ea: sub $0x48,%rsp
0x4035ee: mov %fs:0x28,%rax
0x4035f7: mov %rax,0x38(%rsp)
0x4035fc: xor %eax,%eax
0x4035fe: mov 0x213363(%rip),%rax # 0x616968
0x403605: mov %rdi,(%rsp)
0x403609: mov %rax,0x212cf0(%rip) # 0x616300
0x403610: cmpb $0x7a,(%rax)
0x403613: je 0x403730
0x403619: mov $0x616300,%ebx
0x40361e: mov (%rsp),%rdi
0x403622: callq 0x4019f0 <strlen#plt>
0x403627: cmp $0x20,%eax
0x40362a: mov %rax,0x8(%rsp)
These indeed look like normal executable instructions. What is the opcode of push %r15? This table shows that 0x41, 0x57 is indeed push %r15, and these opcodes just happen to spell AW in ASCII. Similarly, push %r14 is encoded as 0x41, 0x56, which just happens spell AV. Etc.
P.S. My version of gzip is fully stripped, which is why GDB shows no symbols in the above disassembly. If I use a non-stripped version instead, I see:
strings -o -tx gzip | grep AWAVAUATUSH | head -1
6be0 AWAVAUATUSH
readelf -WS gzip | grep text
[13] .text PROGBITS 0000000000401b00 001b00 00d102 00 AX 0 0 16
So the string is still in .text.
gdb -q ./gzip
(gdb) p/a 0x0000000000401b00 + 0x6be0 - 0x001b00
$1 = 0x406be0 <inflate_dynamic>
(gdb) disas/r 0x406be0
Dump of assembler code for function inflate_dynamic:
0x0000000000406be0 <+0>: 41 57 push %r15
0x0000000000406be2 <+2>: 41 56 push %r14
0x0000000000406be4 <+4>: 41 55 push %r13
0x0000000000406be6 <+6>: 41 54 push %r12
0x0000000000406be8 <+8>: 55 push %rbp
0x0000000000406be9 <+9>: 53 push %rbx
0x0000000000406bea <+10>: 48 81 ec 38 05 00 00 sub $0x538,%rsp
...
Now you can clearly see the ASCII 0x4157415641554154... sequence of opcodes.
P.P.S. The original question asks about AWAVAUATSH, which does appear in my Mach-O bash and gzip, but not in Linux ones. Conversely, AWAVAUATUSH does not appear in my Mach-O binaries.
The answer is however the same. The AWAVAUATSH sequence is the same as AWAVAUATUSH, but with push %rbp omitted.
P.P.P.S Here are some other "fun" strings of the same nature:
strings /bin/bash | grep '^A.A.A.' | sort | uniq -c | sort -nr | head
44 AWAVAUATUSH
27 AVAUATUSH
16 AWAVAUA
15 AVAUATUH
14 AWAVAUI
14 AWAVAUATUH
12 AWAVAUATI
8 AWAVAUE1
8 AVAUATI
6 AWAVAUATU

Mifare 1k value block operations

I have a problem with a Mifare Standard 1k card. I made a value block (00000001FFFFFFFE0000000100FF00FF - valid?) on the data block with address 62. The value of the value block is supposed to be 1, and address of the value block is 0.
I've changed the access bits for the data block 2 to be:
C1=1
C2=1
C3=0
The other 2 data blocks have factory access bits. Access bits for the sector trailer are also changed and are:
C1=0
C2=1
C3=1
So, access bits for the corresponding sector (16th sector) are 3B478C69 (valid?).
The problem is that I can't do any of the value block specific functions on that block (increment, decrement, etc), I always get 6A81 as response -> "Card is blocked or command not supported".
The APDU I'm using is FFF5C13E0400000001.
OMNIKEY readers have extensions to the PC/SC API for contactless memory cards. The commands defined by these extensions for increment and decrement of MIFARE Classic value blocks are:
Increment:
+------+------+------+------+------+-------------+
| CLA | INS | P1 | P2 | Lc | DATA |
+------+------+------+------+------+-------------+
| 0xFF | 0xD4 | BLOCK# | 0x04 | XX 00 00 00 |
+------+------+------+------+------+-------------+
or (depending on the firmware version???) the same command with a 1-byte data field:
+------+------+------+------+------+----+
| 0xFF | 0xD4 | BLOCK# | 0x01 | XX |
+------+------+------+------+------+----+
Decrement:
+------+------+------+------+------+-------------+
| CLA | INS | P1 | P2 | Lc | DATA |
+------+------+------+------+------+-------------+
| 0xFF | 0xD8 | BLOCK# | 0x04 | XX 00 00 00 |
+------+------+------+------+------+-------------+
or (depending on the firmware version???) the same command with a 1-byte data field:
+------+------+------+------+------+----+
| 0xFF | 0xD8 | BLOCK# | 0x01 | XX |
+------+------+------+------+------+----+
BLOCK#:
P1 is the MSB of the block number (always zero) and P2 is the LSB of the block number.
XX:
The increment/decrement value.
The commands are documented in OMNIKEY Contactless Smart Card Readers Developer Guide.
It seems as if both commands implicitly issue a transfer command to commit the operation. A restore command is not documented for the PC/SC extensions, however, the restore command is available through the OMNIKEY synchronous API.

Debugging assembly to find a static pointer for reference to a value in a game

I previously asked a question on here, but I was unregistered and wasn't able to edit my entry (not sure if you can) or add any information about the issue. I'm going to attempt to be more thorough this time so I can hopefully get an answer...
I'm trying to find a static pointer and a list of offsets so that I can easily find information in a game every time it's restarted. I've been successful with every piece of information but one...
Currently, I'm using CheatEngine to help me debug and find the pointer paths needed.
The address of the value I want (which changes with each game start) is currently: 849576A For reference, this is the first inventory slot of my first character. I know that each slot is offset by 20h and each character by 550h. So character two's first inventory slot is 849576A+550h. Again, these addresses change each restart, but the offsets do not.
Using CE, I can see what access this address... it returns the following opcodes:
These two are returned before doing anything in-game:
004b7ef9 - 0f bf 08 - movsx ecx,word ptr [eax]
004b542b - 0f bf 04 0a - movsx eax,word ptr [edx+ecx]
Then when shifting items in my inventory I get these:
74be5008 - 72 2a - jb memcpy+84
004bfc3a - 0f bf 4c 02 60 - movsx ecx,word ptr [edx+eax+60]
004bf43f - 8d 7d 9c - lea edi,[ebp-64]
I'm unsure of which to use, so I just pick one and set a breakpoint on one of them, I chose 004b542b, here's the complete code section:
004B53F0 | 55 | PUSH EBP |
004B53F1 | 8BEC | MOV EBP, ESP |
004B53F3 | 83EC 0C | SUB ESP, C |
004B53F6 | 894D F4 | MOV DWORD PTR [EBP-C], ECX |
004B53F9 | C745 FC 00000000 | MOV DWORD PTR [EBP-4], 0 |
004B5400 | 837D 08 00 | CMP DWORD PTR [EBP+8], 0 |
004B5404 | 7F 04 | JG 004B540A |
004B5406 | 33C0 | XOR EAX, EAX |
004B5408 | EB 43 | JMP 004B544D |
004B540A | C745 F8 0F000000 | MOV DWORD PTR [EBP-8], F |
004B5411 | EB 09 | JMP 004B541C |
004B5413 | 8B45 F8 | MOV EAX, DWORD PTR [EBP-8] |
004B5416 | 83C0 01 | ADD EAX, 1 |
004B5419 | 8945 F8 | MOV DWORD PTR [EBP-8], EAX |
004B541C | 837D F8 19 | CMP DWORD PTR [EBP-8], 19 |
004B5420 | 7D 28 | JGE 004B544A |
004B5422 | 8B4D F8 | MOV ECX, DWORD PTR [EBP-8] |
004B5425 | C1E1 05 | SHL ECX, 5 |
004B5428 | 8B55 F4 | MOV EDX, DWORD PTR [EBP-C] |
004B542B | 0FBF040A | MOVSX EAX, WORD PTR [EDX+ECX] |
004B542F | 3B45 08 | CMP EAX, DWORD PTR [EBP+8] |
004B5432 | 75 14 | JNZ 004B5448 |
004B5434 | 8B4D F8 | MOV ECX, DWORD PTR [EBP-8] |
004B5437 | C1E1 05 | SHL ECX, 5 |
004B543A | 8B55 F4 | MOV EDX, DWORD PTR [EBP-C] |
004B543D | 0FBF440A 02 | MOVSX EAX, WORD PTR [EDX+ECX+2] |
004B5442 | 0345 FC | ADD EAX, DWORD PTR [EBP-4] |
004B5445 | 8945 FC | MOV DWORD PTR [EBP-4], EAX |
004B5448 | EB C9 | JMP 004B5413 |
004B544A | 8B45 FC | MOV EAX, DWORD PTR [EBP-4] |
004B544D | 8BE5 | MOV ESP, EBP |
004B544F | 5D | POP EBP |
004B5450 | C2 0400 | RETN 4 |
I decide to set a breakpoint so I can see the register values before and after the line that supposedly accesses my value (004B542B | 0FBF040A | MOVSX EAX, WORD PTR [EDX+ECX]).
BEFORE:
EAX: 00000000
EBX: 00000000
ECX: 000001E0
EDX: 0849558C
ESI: 000000D0
EDI: 013A38A8
EBP: 00189CE0
ESP: 00189CD4
EIP: 004B542B
AFTER:
EAX: 00000DAD
EBX: 00000000
ECX: 000001E0
EDX: 0849558C
ESI: 000000D0
EDI: 013A38A8
EBP: 00189CE0
ESP: 00189CD4
EIP: 004B542F
To me, this means EDX 0849558C should be the value I'm looking to find and then apply an offset of 1E0. However. When searching memory for hex values matching EDX, I get no results which means there aren't any pointers to that address.
I've used the same methods I'm attempting to use here, to successfully gather each static address then apply the offsets. For example, here's the static address + offsets to find my health: 01263FC8 +284 +C +30 +90
I've finally figured it out. Unfortunately, the debugging was leading me nowhere so I started looking at the pointers I had found previously for my characters. Particularly health and mana as these were closest to the addresses I was getting for my inventory. I did some math based on the address I was trying to find and pointer closest to my health and I found an offset. Using that offset and the same static pointer I had found for my health I was able to find my inventory each time.

Resources