March 3, 2022 | 15:44

Hook Qt's QString using Frida

Recently, I wanted to understand what a Windows program built with Qt 4.7 is doing under the hood, in particular I investigated the use of the QString class. For that I used Frida to hook some of the classes methods.

To get started, I created a simple program that makes use of the two methods fromAscii and append:

#include <QString>
#include <stdio.h>
#include <Windows.h>

int main() {

int i = 1;

 while(1){
	const char* str = "awesome";
	QString qstr = QString::fromAscii(str);
	QString qstr2 = QString("string");
	qstr.append(qstr2);
	
	printf("%d %s\n", i, qstr.toUtf8().data());
	i++;
	Sleep(2000);
 }
 return 0;
}

When executed, the program repeatedly appends one QString to the other and prints the result:

“hello.exe”

With the following Frida script both functions get hooked to log relevant arguments resp. return values to the console, and to modify one of the QStrings. I got inspired by this and this issue on Frida’s Github repo, however both functions didn’t work for me.

//hook.js

var module = "QtCore4.dll";

// get string from QString
function convertQStringToString(qStringInput){
	var toascii = new NativeFunction(Module.findExportByName(module, '_ZNK7QString7toAsciiEv'), 'void', ['pointer', 'pointer']);
	var allocSpace = Memory.alloc(Process.pointerSize);
	toascii(allocSpace,qStringInput);
	var qByteArray = Memory.readPointer(allocSpace);
	var todata = new NativeFunction(Module.findExportByName(module, '_ZN10QByteArray4dataEv'), 'pointer' , ['pointer']);
	var str = todata(allocSpace).readCString();
	return str;
}

// create QString
function convertStringToQString(stringInput){
    var fromutf8 = new NativeFunction(Module.findExportByName(module, '_ZN7QString8fromUtf8EPKci'), 'void', ['pointer', 'pointer', 'int']);
    var cStrPointer = Memory.allocUtf8String(stringInput);
    var retQString = Memory.alloc(Process.pointerSize);
	fromutf8(retQString,cStrPointer, -1);
    return retQString;
}

// hook QString::fromAscii( const char * str, int size = -1 )
var fromAsciiFunc = "_ZN7QString9fromAsciiEPKci";
Interceptor.attach(Module.findExportByName(module, fromAsciiFunc), {
  onEnter: function (args) {
    // print "str" argument to console
    var str = args[1].readCString();
    console.log(fromAsciiFunc + ": \t '" + str + "'");
  }
});

// hook QString & QString::append ( const QString & str )
var appendFunc = "_ZN7QString6appendERKS_";
var newQString = convertStringToQString("frida");
Interceptor.attach(Module.findExportByName(module, appendFunc), {
  onEnter: function (args) {
    //  replace the appended QString with our own
    args[1] = newQString;
  },
  onLeave: function (retval) {
    // print returned QString to console
    var str = convertQStringToString(retval);
    console.log(appendFunc + ": \t '" + str + "'");
  }
});

Run the script with frida.exe -l .\hook.js hello.exe while hello.exe is executed, and string will change:

“hello.exe”

© Pavel Pi 2021

Powered by Hugo & Kiss'Em.