Log in

View Full Version : Tibia 11 - Understanding QStrings in memory



blackd
10-10-2016, 12:51
Ok, we need to get used to deal with this new kind of String. It is the one used by QT, the new system that Tibia 11 client uses now. The implementation of a QString is probably different depending OS and compiler. However, for us the only important implementation is the Windows implementation for the compiler that Cipsoft have choosen.
In our case, this is how a Tibia QString look:

08 00 00 00 06 00 00 00 07 00 00 00 10 00 00 00 5A 00 61 00 6E 00 65 00 72 00 61 00 00 00
Here we can read "Zanera"
Now, let's divide our example in parts:
PART0 = 08 00 00 00
PART1 = 06 00 00 00
PART2 = 07 00 00 00
PART3 = 10 00 00 00
PART4 = 5A 00 61 00 6E 00 65 00 72 00 61 00
PART5 = 00 00
You should understand the meaning of each part:
PART0 is usually the number of references at this moment.
- If PART0 is 0 then it means you are reading a destroyed string (invalid memory).
- Not confirmed: If PART0 is bigger than 1000 then it means this QString was moved to other memory place and then you should read PART0 as a pointer to the new start of the QString.
- In our example, we have a valid string and there are 8 memory structures that references this string.
- I only had 1 char in Zanera in my character list, however there must be 7 other places that reference it.
- For example: the structure that holds the current server that you are playing ("Qt5Core.dll" + 004555C8 > 8 > 320 > 18 > 60 > C)
- We still miss 6 places that references Zanera, but well, this is not really important here.
- However PART0 might be important in a future moment of your reverse enginering tasks.

PART1 is the size of the string. Well, this is easy to understand. In the case of Zanera the size = 6. This number is important so we can know how many positions we should try to read.

PART2 is the number of allocated positions. In this case it is 7. It means we can do an easy change of the string to something else with max size of 7 before we need to search a bigger place in memory to place the string. If you need to modify strings then you will have to check this.

PART3 is the offset. We should search the content at QString start address + offset. In our case offset = HEX 10 = 16 positions later. In most cases this is HEX 10.

PART4 is the string. It is unicode and Tibia does not really use it so you can ignore the 00 after each char.

PART5 are extra allocated memory positions (not part of the string) in this case PART2-PART1 = 1, that is, we still have enough memory to write 1 more character before this QString need a bigger memory place. Please be aware that we could have trash instead zeroes. Whatever you write in PART5 is not important because it is never read until PART2 says we should read up to this point.

blackd
10-10-2016, 13:07
My VB6 function to read a QString:



Public Function QMemory_ReadString(ByVal pid As Long, ByVal address As Long, Optional maxSize As Long = 2048) As String
Dim msg_size As Long
Dim msg_offset As Long
Dim msg_start As Long
Dim msg_lastc As Long
Dim msg_refs As Long
Dim allbytes() As Byte
Dim b As Byte
Dim res As String
Dim i As Long
Dim auxRes As Long
res = ""
msg_refs = QMemory_Read4Bytes(pid, address)
If (msg_refs > 1000) Then
' This QString was moved to a different place
QMemory_ReadString = QMemory_ReadString(pid, msg_refs, maxSize)
Exit Function
End If
msg_size = QMemory_Read4Bytes(pid, address + 4) ' Size of the QString
msg_offset = QMemory_Read4Bytes(pid, address + 12) ' Offset that we should use to find the String start
msg_start = address + msg_offset
If msg_size > maxSize Then ' Only read up to maxSize characters
msg_size = maxSize
End If
msg_lastc = msg_size - 1
If msg_lastc < 0 Then
QMemory_ReadString = ""
Exit Function
End If
ReDim allbytes((msg_size * 2) - 1)
auxRes = QMemory_ReadNBytes(pid, msg_start, allbytes)
If (auxRes = -1) Then
QMemory_ReadString = ""
Exit Function
End If
For i = 0 To msg_lastc
res = res & Chr(allbytes(i * 2))
Next i
QMemory_ReadString = res
End Function


Aux functions:


Private Const PROCESS_VM_READ = (&H10)
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Any, ByRef lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long

Public Function QMemory_ReadNBytes(ByVal pid As Long, ByVal finalAddress As Long, ByRef Rbuff() As Byte) As Long
Dim usize As Long
Dim TibiaHandle As Long
Dim readtotal As Long
On Error GoTo gotErr
readtotal = 0
usize = UBound(Rbuff) + 1
If (usize < 1) Then
Exit Function
End If
TibiaHandle = OpenProcess(PROCESS_VM_READ, 0, pid)
ReadProcessMemory TibiaHandle, finalAddress, Rbuff(0), usize, readtotal
CloseHandle (TibiaHandle)
If (readtotal = 0) Then
QMemory_ReadNBytes = -1
Else
QMemory_ReadNBytes = 0
End If
Exit Function
gotErr:
QMemory_ReadNBytes = -1
Debug.Print ("Error at QMemory_ReadNBytes:" & Err.Description)
End Function
Public Function QMemory_Read4Bytes(ByVal pid As Long, ByVal finalAddress As Long) As Long
Dim res As Long
Dim TibiaHandle As Long
On Error GoTo gotErr
TibiaHandle = OpenProcess(PROCESS_VM_READ, 0, pid)
ReadProcessMemory TibiaHandle, finalAddress, res, 4, 0
CloseHandle (TibiaHandle)
QMemory_Read4Bytes = res
Exit Function
gotErr:
QMemory_Read4Bytes = -1
End Function

Jo3Bingham
12-15-2016, 11:39
How do you get a pointer path to a QString? I'm able to locate my desired string in memory by searching for it's UTF-16 equivalent as an array of bytes, but doing a pointer scan yields zero results.