[ida插件]QtMetaParser

解析staticMetaObject
vftable-->metaObject()-->staticMetaObject

相关链接:
Qt Internals & Reversing
[翻译]Qt内部机制及逆向
Qt5 程序初步逆向分析+解析脚本


from idc import *
from idc import __EA64__
from idc import get_strlit_contents as GetString
from idaapi import *

from ida_idaapi import *
from ida_bytes import *
from ida_bytes import off_flag as offflag
from ida_bytes import  FF_QWORD as FF_QWRD 
from ida_bytes import  FF_DWORD as FF_DWRD 

from idc_bc695 import *

import ida_kernwin
import idaapi
import idc
# 清空输出窗口
# ida_kernwin.msg_clear()


# finfo = idaapi.get_inf_structure()
finfo = get_inf_structure()
DEBUG = False
ADDR_SZ = 4 # Default: 32-bit
GOVER = ""
MAX_EA = finfo.max_ea
CPU_ARCH = "x86" # Default CPU Arch
ENDIAN = 0 # 0: little endian; 1: big endian

if finfo.is_64bit():
    ADDR_SZ = 8

proc_name = finfo.procname.lower()
if proc_name == "metapc":
    if ADDR_SZ == 8:
        CPU_ARCH = "x64"
else:
    CPU_ARCH = proc_name

try:
    is_be = finfo.is_be()
except:
    is_be = finfo.mf
ENDIAN = 1 if is_be else 0
if __EA64__:
    ARCH_F = FF_QWRD | FF_DATA
else:
    ARCH_F = FF_DWRD | FF_DATA


def struct_adder(cls, mapper):
    if GetStrucIdByName(cls.__name__) == BADADDR:
        # idx = GetLastStrucIdx() + 1
        idx = -1
        sid = AddStruc(idx, cls.__name__)
        cls.sid = sid
        for member in mapper:
            type_flag = member[1]
            if isOff0(type_flag):
                reftype = REF_OFF64 if isQwrd(ARCH_F) else REF_OFF32
                AddStrucMember(sid, member[0], -1, type_flag, 0, get_bytes_size(type_flag), reftype=reftype)
            else:
                AddStrucMember(sid, member[0], -1, type_flag, -1, get_bytes_size(type_flag))
    else:
        cls.sid = GetStrucIdByName(cls.__name__)


def struct_maker(obj, off):
    struct_adder(obj.__class__, obj.c_struct)
    MakeUnknown(off, GetStrucSize(obj.__class__.sid), DOUNK_EXPAND)
    MakeStruct(off, GetStrucName(obj.__class__.sid))
    # idaapi.auto_wait()
    auto_wait()


# noinspection PyPep8Naming
class QMetaObjectPrivate:
    """
struct QMetaObjectPrivate
{
    // revision 7 is Qt 5.0 everything lower is not supported
    enum { OutputRevision = 7 }; // Used by moc, qmetaobjectbuilder and qdbus

    int revision;
    int className;
    int classInfoCount, classInfoData;
    int methodCount, methodData;
    int propertyCount, propertyData;
    int enumeratorCount, enumeratorData;
    int constructorCount, constructorData;
    int flags;
    int signalCount;
    enum DisconnectType { DisconnectAll, DisconnectOne };
};

QMetaMethod QMetaObject::method(int index) const
{
    int i = index;
    i -= methodOffset();
    if (i < 0 && d.superdata)
        return d.superdata->method(index);

    QMetaMethod result;
    if (i >= 0 && i < priv(d.data)->methodCount) {
        result.mobj = this;
        result.handle = priv(d.data)->methodData + 5*i;
    }
    return result;
}


"""
    c_struct = [("revision", FF_DATA | FF_DWRD),
                ("className", FF_DATA | FF_DWRD),
                ("classInfoCount", FF_DATA | FF_DWRD),
                ("classInfoData", FF_DATA | FF_DWRD),
                ("methodCount", FF_DATA | FF_DWRD),
                ("methodData", FF_DATA | FF_DWRD),
                ("propertyCount", FF_DATA | FF_DWRD),
                ("propertyData", FF_DATA | FF_DWRD),
                ("enumeratorCount", FF_DATA | FF_DWRD),
                ("enumeratorData", FF_DATA | FF_DWRD),
                ("constructorCount", FF_DATA | FF_DWRD),
                ("constructorData", FF_DATA | FF_DWRD),
                ("flags", FF_DATA | FF_DWRD),
                ("signalCount", FF_DATA | FF_DWRD)]

    # todo: when superdata is not null
    def __init__(self, offset, str_data):
        self.offset = offset
        struct_map(self, self.c_struct, offset)
        struct_maker(self, offset)
        cmmt = """CLASS: %s
MethodCount: %d PropertyCount: %d EnumCount: %d
ConstructorCount: %d SignalCount: %d""" % (str_data[self.className].string,
    self.methodCount, self.propertyCount, self.enumeratorCount,
    self.constructorCount, self.signalCount)
        # print(cmmt)
        # S = 'ExtLinB(%d, 0, "%s")' % (offset, cmmt)
        # print(S)
        # idaapi.run_statements(S)
        MakeComm(offset, cmmt)


def displayMetaData(data_addr,rename=False):
    parser = QtMetaParser(data_addr,bool(rename))
    methods=parser.make_qmetaobjecprivate()
    for i,v in enumerate(methods):
        print('[case %d],%s'%(i,v))

class Qt4Strdata:
    def __init__(self,data:str) -> None:
        if  isinstance(data,bytes):
            data=data.decode()
        self.string=data
    def __repr__(self) -> str:
        return self.string
        

# TODO: when superdata is not null
class QtMetaParser:
    isQt4=False
    def __init__(self, d_offset,reName=False):
        self.d_offset = d_offset
        self.d = QMetaObject__d(d_offset)
        self.str_data = self.get_str_data(self.d.stringdata)
        self.qmeta_obj_pri = QMetaObjectPrivate(self.d.data, self.str_data)
        class_name = self.str_data[self.qmeta_obj_pri.className].string
        class_spc = class_name + "::"
        if reName:
            MakeName(d_offset, class_name)
            MakeName(self.d.stringdata,  "qt_meta_stringdata_"+class_name)
            MakeName(self.d.data, "qt_meta_data"+class_name)
            if not Name(self.d.metacall).startswith("nullsub"):
                if QtMetaParser.isQt4:
                    MakeName(self.d.metacall, class_spc + "qt_static_metacall_data")
                else:
                    MakeName(self.d.metacall, class_spc + "qt_static_metacall")

    @staticmethod
    def get_str_data(str_off):
        start = str_off
        str_data = []
        #qt5
        while Dword(start) == 0xFFFFFFFF and Dword(start + 8) == 0:
            str_data.append(QArrayData(start))
            start += QArrayData.size
        #qt4
        c_offset=0
        if len(str_data)==0:
            str_data=dict()
            QtMetaParser.isQt4=True
            sz=get_max_strlit_length(start,idc.STRTYPE_C)
            ida_bytes.create_strlit(start,sz,idc.STRTYPE_C)
            cname:bytes=get_strlit_contents(start)
            
            if cname:
                cname=cname.decode()
            str_data[c_offset]=Qt4Strdata(cname)
            str_data[sz]=Qt4Strdata('')
            print('class name:',cname)
            start+=sz+1
            
            while Word(start)!=0:
                sz=get_max_strlit_length(start,idc.STRTYPE_C)
                ida_bytes.create_strlit(start,sz, idc.STRTYPE_C)
                data:bytes=get_strlit_contents(start)
                if data:
                    data=data.decode()
                else:
                    data=''
                c_offset=start-str_off
                str_data[c_offset]=Qt4Strdata(data)
                start+=sz
        print('str_data:',str_data)
        return str_data

    def make_qmetaobjecprivate(self):
        # parse method
        start = self.qmeta_obj_pri.offset + (self.qmeta_obj_pri.methodData << 2)
        method_data = []
        for off in range(start, start + 4 * 5 * self.qmeta_obj_pri.methodCount, 4 * 5):
            if QtMetaParser.isQt4:
                qmthd = QMetaMethod4(off, self.d.data, self.str_data)
            else:
                qmthd = QMetaMethod(off, self.d.data, self.str_data)
            # MakeComm(qmthd.offset, "METHOD_%d " % len(method_data) + Comment(qmthd.offset))
            method_data.append(qmthd)
        return method_data


class Enum:
    def __init__(self, **entries): self.__dict__.update(entries)


class QMeraMethodBase:
    PropertyFlags = Enum(
        Invalid=0x00000000, Readable=0x00000001, Writable=0x00000002, Resettable=0x00000004,
        EnumOrFlag=0x00000008, StdCppSet=0x00000100, Override=0x00000200, Constant=0x00000400,
        Final=0x00000800, Designable=0x00001000, ResolveDesignable=0x00002000, Scriptable=0x00004000,
        ResolveScriptable=0x00008000, Stored=0x00010000, ResolveStored=0x00020000, Editable=0x00040000,
        ResolveEditable=0x00080000, User=0x00100000, ResolveUser=0x00200000, Notify=0x00400000,
        Revisioned=0x00800000
    )
    MethodFlags = Enum(
        AccessPrivate=0x00, AccessProtected=0x01, AccessPublic=0x02, AccessMask=0x03,
        MethodMethod=0x00, MethodSignal=0x04, MethodSlot=0x08, MethodConstructor=0x0c, MethodTypeMask=0x0c,
        MethodCompatibility=0x10, MethodCloned=0x20, MethodScriptable=0x40, MethodRevisioned=0x80
    )
    MethodTypesDict = {0x00: "METHOD", 0x04: "SIGNAL", 0x08: "SLOT", 0x0c: "CONSTRUCTOR"}
    MethodAccessDict = {0x00: "Private", 0x01: "Protected", 0x02: "Public"}

    QMetaType_map = {
        0: "UnknownType", 1: "Bool", 2: "Int", 3: "UInt", 4: "LongLong", 5: "ULongLong", 6: "Double",
        7: "QChar", 8: "QVariantMap", 9: "QVariantList", 10: "QString", 11: "QStringList",
        12: "QByteArray", 13: "QBitArray", 14: "QDate", 15: "QTime", 16: "QDateTime", 17: "QUrl",
        18: "QLocale", 19: "QRect", 20: "QRectF", 21: "QSize", 22: "QSizeF", 23: "QLine", 24: "QLineF",
        25: "QPoint", 26: "QPointF", 27: "QRegExp", 28: "QVariantHash", 29: "QEasingCurve", 30: "QUuid",
        31: "VoidStar", 32: "Long", 33: "Short", 34: "Char", 35: "ULong", 36: "UShort", 37: "UChar",
        38: "Float", 39: "QObjectStar", 40: "SChar", 41: "QVariant", 42: "QModelIndex", 43: "Void",
        44: "QRegularExpression", 45: "QJsonValue", 46: "QJsonObject", 47: "QJsonArray", 
        48: "QJsonDocument", 49: "QByteArrayList", 64: "QFont", 65: "QPixmap", 66: "QBrush", 
        67: "QColor", 68: "QPalette", 69: "QIcon", 70: "QImage", 71: "QPolygon", 72: "QRegion",
        73: "QBitmap", 74: "QCursor", 75: "QKeySequence", 76: "QPen", 77: "QTextLength",
        78: "QTextFormat", 79: "QMatrix", 80: "QTransform", 81: "QMatrix4x4", 82: "QVector2D",
        83: "QVector3D", 84: "QVector4D", 85: "QQuaternion", 86: "QPolygonF", 121: "QSizePolicy",
        1024: "User"
    }

    def get_type_str(self):
        method_type = self.flag & self.MethodFlags.MethodTypeMask
        cmmt = self.MethodTypesDict[method_type]
        access = self.flag & self.MethodFlags.AccessMask
        cmmt += " " + self.MethodAccessDict[access]
        if self.flag & self.MethodFlags.MethodCompatibility:
            cmmt += " Compatibility"
        elif self.flag & self.MethodFlags.MethodCloned:
            cmmt += " Cloned"
        elif self.flag & self.MethodFlags.MethodScriptable:
            cmmt += " Sciptable"
        elif self.flag & self.MethodFlags.MethodRevisioned:
            cmmt += " Revisioned"
        return cmmt
class QMetaMethod(QMeraMethodBase):
    c_struct = [("name", FF_DATA | FF_DWRD),
                ("parameterCount", FF_DATA | FF_DWRD),
                ("typesDataIndex", FF_DATA | FF_DWRD),
                ("tag", FF_DATA | FF_DWRD),
                ("flag", FF_DATA | FF_DWRD)]


    def get_type(self, type_off, str_data_off):
        MakeUnknown(type_off, 4, DOUNK_EXPAND)
        type_info = Dword(type_off)
        if type_info in QMetaMethod.QMetaType_map:
            t = self.QMetaType_map[type_info]
        elif type_info & 0x80000000:
            type_info &= 0x7FFFFFFF
            t = str_data_off[type_info].string
        MakeComm(type_off, t)
        MakeDword(type_off)
        return t

    def __init__(self, off, data_off, str_data_off):
        self.offset = off
        struct_map(self, self.c_struct, off)
        struct_maker(self, off)

        ret_type_off = data_off + self.typesDataIndex * 4
        ret_type_str = self.get_type(ret_type_off, str_data_off)
        paras_type_off = ret_type_off + 4
        para_type_strs = []
        for i in range(self.parameterCount):
            para_type_off = paras_type_off + i * 4
            para_type = self.get_type(para_type_off, str_data_off)
            para_type_strs.append(para_type)

        para_name_strs = []
        paras_name_off = paras_type_off + self.parameterCount * 4
        for i in range(self.parameterCount):
            para_name_off = paras_name_off + i * 4
            MakeUnknown(para_name_off, 4, DOUNK_EXPAND)
            MakeDword(para_name_off)
            para_name = str_data_off[Dword(para_name_off)].string
            MakeComm(para_name_off, para_name)
            para_name_strs.append(para_name)

        paras_strs = map(lambda x, y: "%s %s" % (x, y), para_type_strs, para_name_strs)
        self.info="%s %s %s(%s)" % (self.get_type_str(), ret_type_str,
            str_data_off[self.name].string, ", ".join(paras_strs))
        MakeComm(off, self.info)
    def __repr__(self) -> str:
        return self.info

class QMetaMethod4(QMeraMethodBase):  
    c_struct = [("name", FF_DATA | FF_DWRD),
                ("parameterOffset", FF_DATA | FF_DWRD),
                ("typesDataOffset", FF_DATA | FF_DWRD),
                ("tag", FF_DATA | FF_DWRD),
                ("flag", FF_DATA | FF_DWRD)]  
      
    def __init__(self, off, data_off, str_data_off:dict):
        self.offset = off
        struct_map(self, self.c_struct, off)
        struct_maker(self, off)

        method_flag=self.get_type_str()
        if self.typesDataOffset not in str_data_off.keys():
            ret_type_str = GetString(data_off + self.typesDataOffset)
        else:
            ret_type_str=str_data_off[self.typesDataOffset].string
        if not ret_type_str:
            ret_type_str='void'

        paras_strs = str_data_off[self.parameterOffset].string
        self.info="%s %s %s parameterName:(%s)" % (method_flag, ret_type_str,
            str_data_off[self.name].string, paras_strs)
        MakeComm(off, self.info)
    def __repr__(self) -> str:
        return self.info

def get_bytes_size(data_flag):
    if isByte(data_flag):
        bytes_len = 1
    elif isWord(data_flag):
        bytes_len = 2
    elif isDwrd(data_flag):
        bytes_len = 4
    elif isQwrd(data_flag):
        bytes_len = 8
    return bytes_len

type_maker = {1: Byte, 2: Word, 4: Dword, 8: Qword}

def struct_map(obj, stru, off):
    for member in stru:
        bytes_len = get_bytes_size(member[1])
        setattr(obj, member[0], type_maker[bytes_len](off))
        off += bytes_len
    return off


class QMetaObject__d:
    """
struct QMetaObject::d { // private data
    const QMetaObject *superdata;
    const QByteArrayData *stringdata;
    const uint *data;
    StaticMetacallFunction static_metacall;
    const QMetaObject * const *relatedMetaObjects;
    void *extradata; //reserved for future use
} d;
"""
    c_struct = [("superdata", offflag() | FF_DATA | ARCH_F),
                ("stringdata", offflag() | FF_DATA | ARCH_F),
                ("data", offflag() | FF_DATA | ARCH_F),
                ("metacall", offflag() | FF_DATA | ARCH_F),
                ("relatedMetaObjects", offflag() | FF_DATA | ARCH_F),
                ("extradata", offflag() | FF_DATA | ARCH_F)]

    def __init__(self, offset):
        struct_map(self, self.c_struct, offset)
        struct_maker(self, offset)


class QArrayData:
    """
struct QArrayData
{
    QtPrivate::RefCount ref;
    int size;
    uint alloc : 31;
    uint capacityReserved : 1;

    qptrdiff offset; // in bytes from beginning of header
};
static inline const QByteArray stringData(const QMetaObject *mo, int index)
{
    Q_ASSERT(priv(mo->d.data)->revision >= 7);
    const QByteArrayDataPtr data = { const_cast<QByteArrayData*>(&mo->d.stringdata[index]) };
    Q_ASSERT(data.ptr->ref.isStatic());
    Q_ASSERT(data.ptr->alloc == 0);
    Q_ASSERT(data.ptr->capacityReserved == 0);
    Q_ASSERT(data.ptr->size >= 0);
    return data;
}

"""
    if __EA64__:
        size = 24
    else:
        size = 16

    c_struct = [("ref", FF_DATA | FF_DWRD),
            ("size", FF_DATA | ARCH_F),
            ("alloc__capRved", FF_DATA | FF_DWRD),
            ("offset", FF_DATA | ARCH_F) ]


    def __init__(self, beg_off):
        struct_map(self, self.c_struct, beg_off)
        struct_maker(self, beg_off)
        t=GetString(beg_off + self.offset)
        if t:
            self.string = t.decode()
        else:
            self.string=''

        alloc = 0x7FFFFFFF & self.alloc__capRved
        capacityReserved = self.alloc__capRved >> 31

        cmmt = "String: %s, alloc: %d, capRvrsd %d" % (self.string, capacityReserved, alloc)
        MakeComm(beg_off, cmmt)


    def __repr__(self):
        return "%s" % self.string





rename=None
class QtMetaParserPlugin(idaapi.plugin_t):
    flags =idaapi.PLUGIN_SKIP #idaapi.PLUGIN_UNL
    wanted_name = "QtMetaParser"
    wanted_hotkey = "Alt+;"
    comment = "QtMetaParser plugin"
    help = "Something helpful"

    def init(self):
        idaapi.msg('QtMetaParser init')
        return idaapi.PLUGIN_KEEP

    def term(self):
        idaapi.msg('QtMetaParser term')

    def run(self, arg):
        try:
            self.rename
        except:
            self.rename=ida_kernwin.ask_yn(1,"是否重命名?")
            if self.rename==-1:
                return
        addrtoparse = ScreenEA()
        if addrtoparse != 0:
            displayMetaData(addrtoparse,bool(self.rename))

def PLUGIN_ENTRY():
    return QtMetaParserPlugin()
posted @ 2024-02-27 10:32  DirWangK  阅读(99)  评论(0编辑  收藏  举报