ql.pack()

ql.pack()
  • depends on ql.arch.bits, ql.pack64 for 64-bit and so on
ql.pack64()
  • pack for 64bit data
    • Pack with option "Q":
    • C Type: unsigned long long
    • Size: 8 bytes
ql.pack32()
  • pack for 32bit data
    • Pack with option "I", with endian check:
    • C Type: unsigned int
    • Size: 4 bytes
ql.pack16()
  • pack for 16bit data
    • Pack with option "H", with endian check:
    • C Type: unsigned short
    • Size: 2 bytes

ql.unpack()

ql.unpack()
  • depends on ql.arch.bits, ql.unpack64 for 64-bit and so on
ql.upack64()
  • unpack for 64bit data
    • Unpack with option "Q":
    • C Type: unsigned long long
    • Size: 8 bytes
ql.unpack32()
  • unpack for 32bit data
    • Unpack with option "I", with endian check:
    • C Type: unsigned int
    • Size: 4 bytes
ql.unpack16()
  • unpack for 16bit data
    • Unpack with option "H", with endian check:
    • C Type: unsigned short
    • Size: 2 bytes

ql.packs()

ql.packs()
  • signed packing
  • depends on ql.arch.bits, ql.pack64s for 64-bit and so on
ql.pack64s()
  • packs for 64bit data
    • Pack with option "q":
    • C Type: long
    • Size: 8 bytes
ql.pack32s()
  • packs for 32bit data
    • Pack with option "i", with endian check:
    • C Type: int
    • Size: 4 bytes
ql.pack16s()
  • packs for 16bit data
    • Unpack with option "h", with endian check:
    • C Type: short
    • Size: 2 bytes

ql.unpacks()

ql.unpacks()
  • signed unpacking
  • depends on ql.arch.bits, ql.unpack64s for 64-bit and so on
ql.unpack64s()
  • unpacks for 64bit data
    • Unpack with option "q":
    • C Type: long
    • Size: 8 bytes
ql.unpack32s()
  • unpacks for 32bit data
    • Unpack with option "i", with endian check:
    • C Type: int
    • Size: 4 bytes
ql.unpack16s()
  • packs for 16bit data
    • Unpack with option "h", with endian check:
    • C Type: short
    • Size: 2 bytes

Custom Pack & Unpack

Qiling has some built-in functions to handle Pack & UnPack of the memory, but if you need more flexibility, you should use the python “struct” lib. For someone, the lib struct call recalls the complex memory structure from C ANSI defined by the struct keyword, and yes, you are right.

struc lib

https://docs.python.org/3/library/struct.html#module-struct

Byte Order, Size, and Alignement: https://docs.python.org/3/library/struct.html#byte-order-size-and-alignment

Format Characters: https://docs.python.org/3/library/struct.html#format-characters

If we take a look at the example below, we can see that the unpack function accepts two parameters, the first of which is our format string:

record = b'raymond   \x32\x12\x08\x01\x08'
name, serialnum, school, gradelevel = unpack('<10sHHb', record)

< --> little-endian

10s --> raymond*** --> 10 x char[]

H --> unsigned short

H --> unsigned short

b --> signed char

understand the right structure

To understand how a complex data structure is composed in memory and to be able to pack and/or unpack it, we can find ourselves in front of two scenarios: - Know structure (os, shared software, standard lib) - Unknown structure (close source software, custom lib)

As far as the known structures are concerned, Google or a few books will absolve the job, but for the unknown ones, you should prepare a decompiler (IDA, Ghidra, r2); you will have to get your hands dirty yourself.

example

Info

  • Target: Netgear 6220
  • CPU-Arch: mips32el
  • Endian: el -> endian little
  • API: bind
  • API-Info: https://man7.org/linux/man-pages/man2/bind.2.html
  • Struct: sockaddr_in
  • Struct-Info: https://man7.org/linux/man-pages/man7/ip.7.html (From bind page, AF_INET)

Structure

struct sockaddr_in {
   sa_family_t    sin_family; /* address family: AF_INET */
   in_port_t      sin_port;   /* port in network byte order */
   struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
  uint32_t       s_addr;     /* address in network byte order */
};

Code

def my_bind(ql, *args, **kw):
    params = ql.os.resolve_fcall_params({
        'fd': UINT,
        'addr': POINTER,
        'addrlen': UINT
    })

    bind_fd = params['fd']
    bind_addr = params['addr']
    bind_addrlen = params['addrlen']

    # read from memory (start_address, len)
    data = ql.mem.read(bind_addr, bind_addrlen)
    # custom unpack (your own ql.unpack) of a C struct from memory
    # https://linux.die.net/man/7/ip -> struct
    sin_family = struct.unpack("<h", data[:2])[0] or ql.os.fd[bind_fd].family
    # little-endian short -> format_string -> https://docs.python.org/3/library/struct.html#format-strings
    port, host = struct.unpack(">HI", data[2:8])
    # big-endian unsigned short, unsigned int -> format_string
    return 0

If you're wondering why even though the architecture is little-endian in the second string format, the big-endian notation has been used, remember that everything about network stacks is big-endian (as indicated on the struct library page); double-check the structure reported above and notice the comments.

Full code: https://github.com/qilingframework/qiling/blob/master/examples/netgear_6220_mips32el_linux.py