From cb8793c3ab45d409dfc72cfd0d2559db7c9ab805 Mon Sep 17 00:00:00 2001 From: retrocpugeek Date: Sun, 21 Jun 2026 20:46:00 +1000 Subject: [PATCH 1/3] Add MIPS64 (n64 ABI) Linux support Introduce QL_ARCH.MIPS64 so Qiling can emulate MIPS64 Linux user-space shellcode in both big- and little-endian. The native backends already support it (Unicorn UC_MODE_MIPS64, Capstone/Keystone CS/KS_MODE_MIPS64). - QlArchMIPS64: 64-bit arch class reusing the shared MIPS register map - n64 calling convention and syscall ABI: the first eight arguments are passed in registers (a0-a3 and a4-a7, the physical registers $8-$11) with no on-stack shadow space, unlike o32 - n64 syscall table (base 5000) generated from the Linux uapi header asm/unistd_n64.h - wire MIPS64 through the Linux/blob OS layers, ELF machine detection (EM_MIPS + ELFCLASS64) and n64 doubleword stack alignment - add big- and little-endian MIPS64 shellcode tests Co-Authored-By: Claude Opus 4.8 (1M context) --- qiling/arch/mips64.py | 72 ++++++ qiling/cc/mips.py | 16 +- qiling/const.py | 1 + qiling/loader/elf.py | 2 +- qiling/os/blob/blob.py | 1 + qiling/os/linux/linux.py | 5 +- qiling/os/linux/map_syscall.py | 371 ++++++++++++++++++++++++++++ qiling/os/posix/posix.py | 1 + qiling/os/posix/syscall/abi/mips.py | 33 ++- qiling/utils.py | 8 +- tests/test_shellcode.py | 28 ++- 11 files changed, 529 insertions(+), 9 deletions(-) create mode 100644 qiling/arch/mips64.py diff --git a/qiling/arch/mips64.py b/qiling/arch/mips64.py new file mode 100644 index 000000000..3633f47d6 --- /dev/null +++ b/qiling/arch/mips64.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from functools import cached_property +from typing import Optional + +from unicorn import Uc, UC_ARCH_MIPS, UC_MODE_MIPS64, UC_MODE_BIG_ENDIAN, UC_MODE_LITTLE_ENDIAN +from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS64, CS_MODE_BIG_ENDIAN, CS_MODE_LITTLE_ENDIAN +from keystone import Ks, KS_ARCH_MIPS, KS_MODE_MIPS64, KS_MODE_BIG_ENDIAN, KS_MODE_LITTLE_ENDIAN + +from qiling import Qiling +from qiling.arch.arch import QlArch +from qiling.arch import mips_const +from qiling.arch.models import MIPS_CPU_MODEL +from qiling.arch.register import QlRegisterManager +from qiling.const import QL_ARCH, QL_ENDIAN + + +class QlArchMIPS64(QlArch): + type = QL_ARCH.MIPS64 + bits = 64 + + def __init__(self, ql: Qiling, *, cputype: Optional[MIPS_CPU_MODEL], endian: QL_ENDIAN): + super().__init__(ql, cputype=cputype) + + self._init_endian = endian + + @cached_property + def uc(self) -> Uc: + endian = { + QL_ENDIAN.EB: UC_MODE_BIG_ENDIAN, + QL_ENDIAN.EL: UC_MODE_LITTLE_ENDIAN + }[self.endian] + + return Uc(UC_ARCH_MIPS, UC_MODE_MIPS64 + endian) + + @cached_property + def regs(self) -> QlRegisterManager: + # the register names are shared with MIPS32; unicorn widens them to + # 64 bits under UC_MODE_MIPS64 + regs_map = dict( + **mips_const.reg_map + ) + + pc_reg = 'pc' + sp_reg = 'sp' + + return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg) + + @cached_property + def disassembler(self) -> Cs: + endian = { + QL_ENDIAN.EL: CS_MODE_LITTLE_ENDIAN, + QL_ENDIAN.EB: CS_MODE_BIG_ENDIAN + }[self.endian] + + return Cs(CS_ARCH_MIPS, CS_MODE_MIPS64 + endian) + + @cached_property + def assembler(self) -> Ks: + endian = { + QL_ENDIAN.EL: KS_MODE_LITTLE_ENDIAN, + QL_ENDIAN.EB: KS_MODE_BIG_ENDIAN + }[self.endian] + + return Ks(KS_ARCH_MIPS, KS_MODE_MIPS64 + endian) + + @property + def endian(self) -> QL_ENDIAN: + return self._init_endian diff --git a/qiling/cc/mips.py b/qiling/cc/mips.py index 472b2a3ec..0532504e2 100644 --- a/qiling/cc/mips.py +++ b/qiling/cc/mips.py @@ -2,7 +2,10 @@ # # Cross Platform and Multi Architecture Advanced Binary Emulation Framework -from unicorn.mips_const import UC_MIPS_REG_V0, UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3 +from unicorn.mips_const import ( + UC_MIPS_REG_V0, UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3, + UC_MIPS_REG_T0, UC_MIPS_REG_T1, UC_MIPS_REG_T2, UC_MIPS_REG_T3 +) from qiling.cc import QlCommonBaseCC, make_arg_list @@ -25,3 +28,14 @@ def getNumSlots(argbits: int): def unwind(self, nslots: int) -> int: # TODO: stack frame unwiding? return self.arch.regs.ra + + +class mips64n64(mipso32): + # n64 passes the first 8 arguments in registers: a0-a3 followed by a4-a7, + # which are the physical registers $8-$11 (unicorn names them t0-t3). unlike + # o32, n64 reserves no shadow space on the stack. + _argregs = make_arg_list( + UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3, + UC_MIPS_REG_T0, UC_MIPS_REG_T1, UC_MIPS_REG_T2, UC_MIPS_REG_T3 + ) + _shadow = 0 diff --git a/qiling/const.py b/qiling/const.py index b54359693..f7ca85214 100644 --- a/qiling/const.py +++ b/qiling/const.py @@ -23,6 +23,7 @@ class QL_ARCH(IntEnum): RISCV = 110 RISCV64 = 111 PPC = 112 + MIPS64 = 113 class QL_OS(IntEnum): diff --git a/qiling/loader/elf.py b/qiling/loader/elf.py index a5a8ac6fe..abd8278c1 100644 --- a/qiling/loader/elf.py +++ b/qiling/loader/elf.py @@ -374,7 +374,7 @@ def __push_str(top: int, s: str) -> int: sp_align = self.ql.arch.pointersize # mips requires doubleword alignment - if self.ql.arch.type is QL_ARCH.MIPS: + if self.ql.arch.type in (QL_ARCH.MIPS, QL_ARCH.MIPS64): sp_align *= 2 new_stack = self.ql.mem.align(new_stack - len(elf_table), sp_align) diff --git a/qiling/os/blob/blob.py b/qiling/os/blob/blob.py index af52fa74a..e8d4bff1d 100644 --- a/qiling/os/blob/blob.py +++ b/qiling/os/blob/blob.py @@ -33,6 +33,7 @@ def __init__(self, ql: Qiling): QL_ARCH.ARM : arm.aarch32, QL_ARCH.ARM64 : arm.aarch64, QL_ARCH.MIPS : mips.mipso32, + QL_ARCH.MIPS64 : mips.mips64n64, QL_ARCH.RISCV : riscv.riscv, QL_ARCH.RISCV64 : riscv.riscv, QL_ARCH.PPC : ppc.ppc, diff --git a/qiling/os/linux/linux.py b/qiling/os/linux/linux.py index 0313218d6..56b8d39cb 100644 --- a/qiling/os/linux/linux.py +++ b/qiling/os/linux/linux.py @@ -37,6 +37,7 @@ def __init__(self, ql: Qiling): QL_ARCH.ARM : arm.aarch32, QL_ARCH.ARM64 : arm.aarch64, QL_ARCH.MIPS : mips.mipso32, + QL_ARCH.MIPS64 : mips.mips64n64, QL_ARCH.RISCV : riscv.riscv, QL_ARCH.RISCV64 : riscv.riscv, QL_ARCH.PPC : ppc.ppc, @@ -64,8 +65,8 @@ def load(self): 'get_tls': 0xffff0fe0 }) - # MIPS32 - elif self.ql.arch.type == QL_ARCH.MIPS: + # MIPS32 / MIPS64 (same syscall exception number) + elif self.ql.arch.type in (QL_ARCH.MIPS, QL_ARCH.MIPS64): self.ql.hook_intno(self.hook_syscall, 17) self.thread_class = thread.QlLinuxMIPS32Thread diff --git a/qiling/os/linux/map_syscall.py b/qiling/os/linux/map_syscall.py index c3a1289e7..957efacc1 100644 --- a/qiling/os/linux/map_syscall.py +++ b/qiling/os/linux/map_syscall.py @@ -18,6 +18,7 @@ def get_syscall_mapper(archtype: QL_ARCH): QL_ARCH.X8664 : x8664_syscall_table, QL_ARCH.X86 : x86_syscall_table, QL_ARCH.MIPS : mips_syscall_table, + QL_ARCH.MIPS64 : mips64_n64_syscall_table, QL_ARCH.RISCV : riscv32_syscall_table, QL_ARCH.RISCV64 : riscv64_syscall_table, QL_ARCH.PPC : ppc_syscall_table @@ -1935,6 +1936,376 @@ def __mapper(syscall_num: int) -> str: 4448: "process_mrelease", } +# MIPS64 n64 ABI (syscall base 5000), generated from the Linux kernel uapi +# header asm/unistd_n64.h +mips64_n64_syscall_table = { + 5000: "read", + 5001: "write", + 5002: "open", + 5003: "close", + 5004: "stat", + 5005: "fstat", + 5006: "lstat", + 5007: "poll", + 5008: "lseek", + 5009: "mmap", + 5010: "mprotect", + 5011: "munmap", + 5012: "brk", + 5013: "rt_sigaction", + 5014: "rt_sigprocmask", + 5015: "ioctl", + 5016: "pread64", + 5017: "pwrite64", + 5018: "readv", + 5019: "writev", + 5020: "access", + 5021: "pipe", + 5022: "_newselect", + 5023: "sched_yield", + 5024: "mremap", + 5025: "msync", + 5026: "mincore", + 5027: "madvise", + 5028: "shmget", + 5029: "shmat", + 5030: "shmctl", + 5031: "dup", + 5032: "dup2", + 5033: "pause", + 5034: "nanosleep", + 5035: "getitimer", + 5036: "setitimer", + 5037: "alarm", + 5038: "getpid", + 5039: "sendfile", + 5040: "socket", + 5041: "connect", + 5042: "accept", + 5043: "sendto", + 5044: "recvfrom", + 5045: "sendmsg", + 5046: "recvmsg", + 5047: "shutdown", + 5048: "bind", + 5049: "listen", + 5050: "getsockname", + 5051: "getpeername", + 5052: "socketpair", + 5053: "setsockopt", + 5054: "getsockopt", + 5055: "clone", + 5056: "fork", + 5057: "execve", + 5058: "exit", + 5059: "wait4", + 5060: "kill", + 5061: "uname", + 5062: "semget", + 5063: "semop", + 5064: "semctl", + 5065: "shmdt", + 5066: "msgget", + 5067: "msgsnd", + 5068: "msgrcv", + 5069: "msgctl", + 5070: "fcntl", + 5071: "flock", + 5072: "fsync", + 5073: "fdatasync", + 5074: "truncate", + 5075: "ftruncate", + 5076: "getdents", + 5077: "getcwd", + 5078: "chdir", + 5079: "fchdir", + 5080: "rename", + 5081: "mkdir", + 5082: "rmdir", + 5083: "creat", + 5084: "link", + 5085: "unlink", + 5086: "symlink", + 5087: "readlink", + 5088: "chmod", + 5089: "fchmod", + 5090: "chown", + 5091: "fchown", + 5092: "lchown", + 5093: "umask", + 5094: "gettimeofday", + 5095: "getrlimit", + 5096: "getrusage", + 5097: "sysinfo", + 5098: "times", + 5099: "ptrace", + 5100: "getuid", + 5101: "syslog", + 5102: "getgid", + 5103: "setuid", + 5104: "setgid", + 5105: "geteuid", + 5106: "getegid", + 5107: "setpgid", + 5108: "getppid", + 5109: "getpgrp", + 5110: "setsid", + 5111: "setreuid", + 5112: "setregid", + 5113: "getgroups", + 5114: "setgroups", + 5115: "setresuid", + 5116: "getresuid", + 5117: "setresgid", + 5118: "getresgid", + 5119: "getpgid", + 5120: "setfsuid", + 5121: "setfsgid", + 5122: "getsid", + 5123: "capget", + 5124: "capset", + 5125: "rt_sigpending", + 5126: "rt_sigtimedwait", + 5127: "rt_sigqueueinfo", + 5128: "rt_sigsuspend", + 5129: "sigaltstack", + 5130: "utime", + 5131: "mknod", + 5132: "personality", + 5133: "ustat", + 5134: "statfs", + 5135: "fstatfs", + 5136: "sysfs", + 5137: "getpriority", + 5138: "setpriority", + 5139: "sched_setparam", + 5140: "sched_getparam", + 5141: "sched_setscheduler", + 5142: "sched_getscheduler", + 5143: "sched_get_priority_max", + 5144: "sched_get_priority_min", + 5145: "sched_rr_get_interval", + 5146: "mlock", + 5147: "munlock", + 5148: "mlockall", + 5149: "munlockall", + 5150: "vhangup", + 5151: "pivot_root", + 5152: "_sysctl", + 5153: "prctl", + 5154: "adjtimex", + 5155: "setrlimit", + 5156: "chroot", + 5157: "sync", + 5158: "acct", + 5159: "settimeofday", + 5160: "mount", + 5161: "umount2", + 5162: "swapon", + 5163: "swapoff", + 5164: "reboot", + 5165: "sethostname", + 5166: "setdomainname", + 5167: "create_module", + 5168: "init_module", + 5169: "delete_module", + 5170: "get_kernel_syms", + 5171: "query_module", + 5172: "quotactl", + 5173: "nfsservctl", + 5174: "getpmsg", + 5175: "putpmsg", + 5176: "afs_syscall", + 5177: "reserved177", + 5178: "gettid", + 5179: "readahead", + 5180: "setxattr", + 5181: "lsetxattr", + 5182: "fsetxattr", + 5183: "getxattr", + 5184: "lgetxattr", + 5185: "fgetxattr", + 5186: "listxattr", + 5187: "llistxattr", + 5188: "flistxattr", + 5189: "removexattr", + 5190: "lremovexattr", + 5191: "fremovexattr", + 5192: "tkill", + 5193: "reserved193", + 5194: "futex", + 5195: "sched_setaffinity", + 5196: "sched_getaffinity", + 5197: "cacheflush", + 5198: "cachectl", + 5199: "sysmips", + 5200: "io_setup", + 5201: "io_destroy", + 5202: "io_getevents", + 5203: "io_submit", + 5204: "io_cancel", + 5205: "exit_group", + 5206: "lookup_dcookie", + 5207: "epoll_create", + 5208: "epoll_ctl", + 5209: "epoll_wait", + 5210: "remap_file_pages", + 5211: "rt_sigreturn", + 5212: "set_tid_address", + 5213: "restart_syscall", + 5214: "semtimedop", + 5215: "fadvise64", + 5216: "timer_create", + 5217: "timer_settime", + 5218: "timer_gettime", + 5219: "timer_getoverrun", + 5220: "timer_delete", + 5221: "clock_settime", + 5222: "clock_gettime", + 5223: "clock_getres", + 5224: "clock_nanosleep", + 5225: "tgkill", + 5226: "utimes", + 5227: "mbind", + 5228: "get_mempolicy", + 5229: "set_mempolicy", + 5230: "mq_open", + 5231: "mq_unlink", + 5232: "mq_timedsend", + 5233: "mq_timedreceive", + 5234: "mq_notify", + 5235: "mq_getsetattr", + 5236: "vserver", + 5237: "waitid", + 5239: "add_key", + 5240: "request_key", + 5241: "keyctl", + 5242: "set_thread_area", + 5243: "inotify_init", + 5244: "inotify_add_watch", + 5245: "inotify_rm_watch", + 5246: "migrate_pages", + 5247: "openat", + 5248: "mkdirat", + 5249: "mknodat", + 5250: "fchownat", + 5251: "futimesat", + 5252: "newfstatat", + 5253: "unlinkat", + 5254: "renameat", + 5255: "linkat", + 5256: "symlinkat", + 5257: "readlinkat", + 5258: "fchmodat", + 5259: "faccessat", + 5260: "pselect6", + 5261: "ppoll", + 5262: "unshare", + 5263: "splice", + 5264: "sync_file_range", + 5265: "tee", + 5266: "vmsplice", + 5267: "move_pages", + 5268: "set_robust_list", + 5269: "get_robust_list", + 5270: "kexec_load", + 5271: "getcpu", + 5272: "epoll_pwait", + 5273: "ioprio_set", + 5274: "ioprio_get", + 5275: "utimensat", + 5276: "signalfd", + 5277: "timerfd", + 5278: "eventfd", + 5279: "fallocate", + 5280: "timerfd_create", + 5281: "timerfd_gettime", + 5282: "timerfd_settime", + 5283: "signalfd4", + 5284: "eventfd2", + 5285: "epoll_create1", + 5286: "dup3", + 5287: "pipe2", + 5288: "inotify_init1", + 5289: "preadv", + 5290: "pwritev", + 5291: "rt_tgsigqueueinfo", + 5292: "perf_event_open", + 5293: "accept4", + 5294: "recvmmsg", + 5295: "fanotify_init", + 5296: "fanotify_mark", + 5297: "prlimit64", + 5298: "name_to_handle_at", + 5299: "open_by_handle_at", + 5300: "clock_adjtime", + 5301: "syncfs", + 5302: "sendmmsg", + 5303: "setns", + 5304: "process_vm_readv", + 5305: "process_vm_writev", + 5306: "kcmp", + 5307: "finit_module", + 5308: "getdents64", + 5309: "sched_setattr", + 5310: "sched_getattr", + 5311: "renameat2", + 5312: "seccomp", + 5313: "getrandom", + 5314: "memfd_create", + 5315: "bpf", + 5316: "execveat", + 5317: "userfaultfd", + 5318: "membarrier", + 5319: "mlock2", + 5320: "copy_file_range", + 5321: "preadv2", + 5322: "pwritev2", + 5323: "pkey_mprotect", + 5324: "pkey_alloc", + 5325: "pkey_free", + 5326: "statx", + 5327: "rseq", + 5328: "io_pgetevents", + 5424: "pidfd_send_signal", + 5425: "io_uring_setup", + 5426: "io_uring_enter", + 5427: "io_uring_register", + 5428: "open_tree", + 5429: "move_mount", + 5430: "fsopen", + 5431: "fsconfig", + 5432: "fsmount", + 5433: "fspick", + 5434: "pidfd_open", + 5435: "clone3", + 5436: "close_range", + 5437: "openat2", + 5438: "pidfd_getfd", + 5439: "faccessat2", + 5440: "process_madvise", + 5441: "epoll_pwait2", + 5442: "mount_setattr", + 5443: "quotactl_fd", + 5444: "landlock_create_ruleset", + 5445: "landlock_add_rule", + 5446: "landlock_restrict_self", + 5448: "process_mrelease", + 5449: "futex_waitv", + 5450: "set_mempolicy_home_node", + 5451: "cachestat", + 5452: "fchmodat2", + 5453: "map_shadow_stack", + 5454: "futex_wake", + 5455: "futex_wait", + 5456: "futex_requeue", + 5457: "statmount", + 5458: "listmount", + 5459: "lsm_get_self_attr", + 5460: "lsm_set_self_attr", + 5461: "lsm_list_modules", +} + riscv32_syscall_table = { 0: "io_setup", 1: "io_destroy", diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index a0a7b6290..c86c0776d 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -70,6 +70,7 @@ def __init__(self, ql: Qiling): QL_ARCH.ARM64: arm.QlAArch64, QL_ARCH.ARM: arm.QlAArch32, QL_ARCH.MIPS: mips.QlMipsO32, + QL_ARCH.MIPS64: mips.QlMips64N64, QL_ARCH.X86: intel.QlIntel32, QL_ARCH.X8664: intel.QlIntel64, QL_ARCH.RISCV: riscv.QlRiscV32, diff --git a/qiling/os/posix/syscall/abi/mips.py b/qiling/os/posix/syscall/abi/mips.py index a8e3a562c..f1294160e 100644 --- a/qiling/os/posix/syscall/abi/mips.py +++ b/qiling/os/posix/syscall/abi/mips.py @@ -3,7 +3,10 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework from typing import Tuple -from unicorn.mips_const import UC_MIPS_REG_V0, UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3 +from unicorn.mips_const import ( + UC_MIPS_REG_V0, UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3, + UC_MIPS_REG_T0, UC_MIPS_REG_T1, UC_MIPS_REG_T2, UC_MIPS_REG_T3 +) from qiling.os.posix.syscall.abi import QlSyscallABI @@ -39,3 +42,31 @@ def set_return_value(self, value: int) -> None: self.arch.regs.v0 = v0 self.arch.regs.a3 = a3 + + +class QlMips64N64(QlMipsO32): + """System call ABI for MIPS64 n64 systems. + + n64 passes the first 8 arguments in registers (a0-a3 followed by a4-a7, + which are the physical registers $8-$11) and, unlike o32, reserves no + shadow space for them on the stack. + + See: https://www.linux-mips.org/wiki/Syscall + """ + + _idreg = UC_MIPS_REG_V0 + _argregs = ( + UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3, + UC_MIPS_REG_T0, UC_MIPS_REG_T1, UC_MIPS_REG_T2, UC_MIPS_REG_T3 + ) + _retreg = UC_MIPS_REG_V0 + + def get_params(self, count: int) -> Tuple[int, ...]: + num_reg_args = len(self._argregs) + + reg_args = QlSyscallABI.get_params(self, min(count, num_reg_args)) + stack_args = tuple( + self.arch.stack_read(self.arch.pointersize * i) for i in range(max(0, count - num_reg_args)) + ) + + return reg_args + stack_args diff --git a/qiling/utils.py b/qiling/utils.py index 72e5c32df..57ef3e628 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -148,7 +148,8 @@ def __emu_env_from_elf(path: str) -> Tuple[Optional[QL_ARCH], Optional[QL_OS], O machines64 = { EM_X86_64 : QL_ARCH.X8664, EM_AARCH64 : QL_ARCH.ARM64, - EM_RISCV : QL_ARCH.RISCV64 + EM_RISCV : QL_ARCH.RISCV64, + EM_MIPS : QL_ARCH.MIPS64 } classes = { @@ -381,8 +382,8 @@ def select_arch(archtype: QL_ARCH, cputype: Optional[QL_CPU], endian: QL_ENDIAN, kwargs['endian'] = endian kwargs['thumb'] = thumb - # set endianness for mips arch - elif archtype is QL_ARCH.MIPS: + # set endianness for mips archs + elif archtype in (QL_ARCH.MIPS, QL_ARCH.MIPS64): kwargs['endian'] = endian module = { @@ -392,6 +393,7 @@ def select_arch(archtype: QL_ARCH, cputype: Optional[QL_CPU], endian: QL_ENDIAN, QL_ARCH.ARM : r'arm', QL_ARCH.ARM64 : r'arm64', QL_ARCH.MIPS : r'mips', + QL_ARCH.MIPS64 : r'mips64', QL_ARCH.CORTEX_M : r'cortex_m', QL_ARCH.RISCV : r'riscv', QL_ARCH.RISCV64 : r'riscv64', diff --git a/tests/test_shellcode.py b/tests/test_shellcode.py index 9b2b4e054..5ea75acc9 100644 --- a/tests/test_shellcode.py +++ b/tests/test_shellcode.py @@ -9,7 +9,7 @@ sys.path.append("..") from qiling import Qiling -from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT, QL_VERBOSE +from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT, QL_VERBOSE, QL_ENDIAN # test = bytes.fromhex('cccc') @@ -22,6 +22,22 @@ 2f7368 ''') +# MIPS64 n64 shellcode: write(1, "MIPS64 hello\n", 13) then exit_group(0). +# uses the n64 syscall numbers (write=5001, exit_group=5205) and computes the +# message address with a bal/daddiu pc-relative trick. assembled with binutils +# mips64-linux-gnuabi64-as. +MIPS64EB_LIN = bytes.fromhex(''' + 2402138924040001041100010000000067e500182406000d0000000c24021455 + 240400000000000c4d49505336342068656c6c6f0a +''') + +# little-endian counterpart of MIPS64EB_LIN: instruction words byte-swapped, +# the trailing string left as-is +MIPS64EL_LIN = bytes.fromhex(''' + 891302240100042401001104000000001800e5670d0006240c00000055140224 + 000004240c0000004d49505336342068656c6c6f0a +''') + X86_WIN = bytes.fromhex(''' fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c 617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b5920 @@ -105,6 +121,16 @@ def test_linux_mips32(self): ql.os.set_syscall('execve', graceful_execve, QL_INTERCEPT.EXIT) ql.run() + def test_linux_mips64eb(self): + print("Linux MIPS 64bit EB Shellcode") + ql = Qiling(code=MIPS64EB_LIN, archtype=QL_ARCH.MIPS64, ostype=QL_OS.LINUX, endian=QL_ENDIAN.EB, verbose=QL_VERBOSE.OFF) + ql.run() + + def test_linux_mips64el(self): + print("Linux MIPS 64bit EL Shellcode") + ql = Qiling(code=MIPS64EL_LIN, archtype=QL_ARCH.MIPS64, ostype=QL_OS.LINUX, endian=QL_ENDIAN.EL, verbose=QL_VERBOSE.OFF) + ql.run() + # This shellcode needs to be changed to something non-blocking def test_linux_arm(self): print("Linux ARM 32bit Shellcode") From fd5d9aa36fad7e85dd886e4fb5d8db92536bef37 Mon Sep 17 00:00:00 2001 From: retrocpugeek Date: Sun, 21 Jun 2026 20:59:57 +1000 Subject: [PATCH 2/3] Add examples/shellcode_mips64_run.py Demonstrate MIPS64 (n64 ABI) Linux shellcode emulation in both big- and little-endian, using a write/exit_group shellcode that prints to stdout. Co-Authored-By: Claude Opus 4.8 (1M context) --- examples/shellcode_mips64_run.py | 53 ++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 examples/shellcode_mips64_run.py diff --git a/examples/shellcode_mips64_run.py b/examples/shellcode_mips64_run.py new file mode 100644 index 000000000..5435545d1 --- /dev/null +++ b/examples/shellcode_mips64_run.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import sys + +sys.path.append("..") +from qiling import Qiling +from qiling.const import QL_ARCH, QL_OS, QL_ENDIAN, QL_VERBOSE + +# MIPS64 n64 shellcode: write(1, "MIPS64 hello\n", 13) then exit_group(0). +# +# it uses the n64 syscall numbers (write=5001, exit_group=5205) and computes the +# address of the message with a bal/daddiu pc-relative trick. assembled with +# binutils mips64-linux-gnuabi64-as: +# +# .set noreorder +# __start: +# li $v0, 5001 # __NR_write +# li $a0, 1 # fd = stdout +# bal load +# nop +# load: +# daddiu $a1, $ra, (msg - load) # a1 = &msg +# li $a2, 13 # len +# syscall +# li $v0, 5205 # __NR_exit_group +# li $a0, 0 +# syscall +# msg: +# .ascii "MIPS64 hello\n" +MIPS64EB_LIN = bytes.fromhex(''' + 2402138924040001041100010000000067e500182406000d0000000c24021455 + 240400000000000c4d49505336342068656c6c6f0a +''') + +# little-endian counterpart of MIPS64EB_LIN: the instruction words are +# byte-swapped while the trailing string is left as-is +MIPS64EL_LIN = bytes.fromhex(''' + 891302240100042401001104000000001800e5670d0006240c00000055140224 + 000004240c0000004d49505336342068656c6c6f0a +''') + + +if __name__ == "__main__": + print("\nLinux MIPS 64bit EB (big-endian) Shellcode") + ql = Qiling(code=MIPS64EB_LIN, archtype=QL_ARCH.MIPS64, ostype=QL_OS.LINUX, endian=QL_ENDIAN.EB, verbose=QL_VERBOSE.DEFAULT) + ql.run() + + print("\nLinux MIPS 64bit EL (little-endian) Shellcode") + ql = Qiling(code=MIPS64EL_LIN, archtype=QL_ARCH.MIPS64, ostype=QL_OS.LINUX, endian=QL_ENDIAN.EL, verbose=QL_VERBOSE.DEFAULT) + ql.run() From a55a2946f82b25d4774914a3b1151d752a4980d3 Mon Sep 17 00:00:00 2001 From: retrocpugeek Date: Sun, 21 Jun 2026 21:11:52 +1000 Subject: [PATCH 3/3] Route MIPS64 through the MIPS arch-specific syscall constants The Linux/POSIX syscall layer has several arch-specific code paths keyed on QL_ARCH.MIPS (open flags, socket type/domain/level/option/ip-option tables, mmap flags, SHMLBA, the sigset/sigaction layout, SIGSTOP=23, the MIPS sigprocmask variant, the MIPS stat struct, the pipe()-returns-in-registers convention and set_thread_area). These values are MIPS-arch-specific and shared by the n64 ABI, so MIPS64 must take the same paths. Notably the MIPS64 stat struct already existed but was reached via QL_ARCH.MIPS + bits==64; with a distinct QL_ARCH.MIPS64 type that branch was missed and stat fell back to the x86 layout. The struct definitions are parameterised on arch.bits/native_type, so the n64 layouts fall out correctly once the branch fires. pread64's o32 stack-offset hack is intentionally left MIPS-only: under n64 the 64-bit offset arrives in a register, so the generic argument handling is already correct. Co-Authored-By: Claude Opus 4.8 (1M context) --- qiling/arch/utils.py | 3 ++- qiling/os/linux/syscall.py | 2 +- qiling/os/posix/const_mapping.py | 17 ++++++++++++----- qiling/os/posix/syscall/mman.py | 2 +- qiling/os/posix/syscall/shm.py | 1 + qiling/os/posix/syscall/signal.py | 6 ++++-- qiling/os/posix/syscall/socket.py | 2 +- qiling/os/posix/syscall/stat.py | 2 +- qiling/os/posix/syscall/unistd.py | 2 +- 9 files changed, 24 insertions(+), 13 deletions(-) diff --git a/qiling/arch/utils.py b/qiling/arch/utils.py index 4d44b545f..4a6945615 100644 --- a/qiling/arch/utils.py +++ b/qiling/arch/utils.py @@ -12,7 +12,7 @@ from functools import lru_cache from keystone import (Ks, KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_MIPS, KS_ARCH_X86, KS_ARCH_PPC, - KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_MIPS32, KS_MODE_PPC32, KS_MODE_16, KS_MODE_32, KS_MODE_64, + KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_MIPS32, KS_MODE_MIPS64, KS_MODE_PPC32, KS_MODE_16, KS_MODE_32, KS_MODE_64, KS_MODE_LITTLE_ENDIAN, KS_MODE_BIG_ENDIAN) from qiling import Qiling @@ -126,6 +126,7 @@ def assembler(arch: QL_ARCH, endianness: QL_ENDIAN, is_thumb: bool) -> Ks: QL_ARCH.ARM: (KS_ARCH_ARM, KS_MODE_ARM + endian + thumb), QL_ARCH.ARM64: (KS_ARCH_ARM64, KS_MODE_ARM), QL_ARCH.MIPS: (KS_ARCH_MIPS, KS_MODE_MIPS32 + endian), + QL_ARCH.MIPS64: (KS_ARCH_MIPS, KS_MODE_MIPS64 + endian), QL_ARCH.A8086: (KS_ARCH_X86, KS_MODE_16), QL_ARCH.X86: (KS_ARCH_X86, KS_MODE_32), QL_ARCH.X8664: (KS_ARCH_X86, KS_MODE_64), diff --git a/qiling/os/linux/syscall.py b/qiling/os/linux/syscall.py index d97de91bf..0e8125454 100644 --- a/qiling/os/linux/syscall.py +++ b/qiling/os/linux/syscall.py @@ -56,7 +56,7 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr: int): ql.log.warning(f"Wrong index {index} from address {hex(u_info_addr)}") return -1 - elif ql.arch.type == QL_ARCH.MIPS: + elif ql.arch.type in (QL_ARCH.MIPS, QL_ARCH.MIPS64): CONFIG3_ULR = (1 << 13) ql.arch.regs.cp0_config3 = CONFIG3_ULR ql.arch.regs.cp0_userlocal = u_info_addr diff --git a/qiling/os/posix/const_mapping.py b/qiling/os/posix/const_mapping.py index dd95f717e..fde1cc73b 100644 --- a/qiling/os/posix/const_mapping.py +++ b/qiling/os/posix/const_mapping.py @@ -45,6 +45,7 @@ def get_open_flags_class(archtype: QL_ARCH, ostype: QL_OS) -> Union[Type[Flag], QL_ARCH.ARM: linux_arm_open_flags, QL_ARCH.ARM64: linux_arm_open_flags, QL_ARCH.MIPS: linux_mips_open_flags, + QL_ARCH.MIPS64: linux_mips_open_flags, QL_ARCH.RISCV: linux_riscv_open_flags, QL_ARCH.RISCV64: linux_riscv_open_flags, QL_ARCH.PPC: linux_ppc_open_flags @@ -135,7 +136,8 @@ def socket_type_mapping(value: int, archtype: QL_ARCH) -> str: QL_ARCH.X8664: linux_x86_socket_types, QL_ARCH.ARM: linux_arm_socket_types, QL_ARCH.ARM64: linux_arm_socket_types, - QL_ARCH.MIPS: linux_mips_socket_types + QL_ARCH.MIPS: linux_mips_socket_types, + QL_ARCH.MIPS64: linux_mips_socket_types }[archtype] # https://code.woboq.org/linux/linux/net/socket.c.html#1363 @@ -148,7 +150,8 @@ def socket_domain_mapping(value: int, archtype: QL_ARCH, ostype: QL_OS) -> str: QL_ARCH.X8664: macos_x86_socket_domain if ostype is QL_OS.MACOS else linux_x86_socket_domain, QL_ARCH.ARM: linux_arm_socket_domain, QL_ARCH.ARM64: linux_arm_socket_domain, - QL_ARCH.MIPS: linux_mips_socket_domain + QL_ARCH.MIPS: linux_mips_socket_domain, + QL_ARCH.MIPS64: linux_mips_socket_domain }[archtype] return socket_domain(value).name @@ -161,6 +164,7 @@ def socket_tcp_option_mapping(value: int, archtype: QL_ARCH) -> str: QL_ARCH.ARM: linux_socket_tcp_options, QL_ARCH.ARM64: linux_socket_tcp_options, QL_ARCH.MIPS: linux_socket_tcp_options, + QL_ARCH.MIPS64: linux_socket_tcp_options, }[archtype] return socket_option(value).name @@ -172,7 +176,8 @@ def socket_level_mapping(value: int, archtype: QL_ARCH) -> str: QL_ARCH.X8664: linux_x86_socket_level, QL_ARCH.ARM: linux_arm_socket_level, QL_ARCH.ARM64: linux_arm_socket_level, - QL_ARCH.MIPS: linux_mips_socket_level + QL_ARCH.MIPS: linux_mips_socket_level, + QL_ARCH.MIPS64: linux_mips_socket_level }[archtype] return socket_level(value).name @@ -184,7 +189,8 @@ def socket_ip_option_mapping(value: int, archtype: QL_ARCH, ostype: QL_OS) -> st QL_ARCH.X8664: macos_socket_ip_options if ostype is QL_OS.MACOS else linux_socket_ip_options, QL_ARCH.ARM: linux_socket_ip_options, QL_ARCH.ARM64: macos_socket_ip_options if ostype is QL_OS.MACOS else linux_socket_ip_options, - QL_ARCH.MIPS: linux_mips_socket_ip_options + QL_ARCH.MIPS: linux_mips_socket_ip_options, + QL_ARCH.MIPS64: linux_mips_socket_ip_options }[archtype] return socket_ip_option(value).name @@ -196,7 +202,8 @@ def socket_option_mapping(value: int, archtype: QL_ARCH) -> str: QL_ARCH.X8664: linux_x86_socket_options, QL_ARCH.ARM: linux_arm_socket_options, QL_ARCH.ARM64: linux_arm_socket_options, - QL_ARCH.MIPS: linux_mips_socket_options + QL_ARCH.MIPS: linux_mips_socket_options, + QL_ARCH.MIPS64: linux_mips_socket_options }[archtype] return socket_option(value).name diff --git a/qiling/os/posix/syscall/mman.py b/qiling/os/posix/syscall/mman.py index a89f98d39..361173a05 100755 --- a/qiling/os/posix/syscall/mman.py +++ b/qiling/os/posix/syscall/mman.py @@ -80,7 +80,7 @@ def __select_mmap_flags(archtype: QL_ARCH, ostype: QL_OS): """ osflags = { - QL_OS.LINUX: mips_mmap_flags if archtype == QL_ARCH.MIPS else linux_mmap_flags, + QL_OS.LINUX: mips_mmap_flags if archtype in (QL_ARCH.MIPS, QL_ARCH.MIPS64) else linux_mmap_flags, QL_OS.FREEBSD: freebsd_mmap_flags, QL_OS.MACOS: macos_mmap_flags, QL_OS.QNX: qnx_mmap_flags # FIXME: only for arm? diff --git a/qiling/os/posix/syscall/shm.py b/qiling/os/posix/syscall/shm.py index 5578feeae..24ad35d2c 100644 --- a/qiling/os/posix/syscall/shm.py +++ b/qiling/os/posix/syscall/shm.py @@ -88,6 +88,7 @@ def ql_syscall_shmat(ql: Qiling, shmid: int, shmaddr: int, shmflg: int): # select the appropriate SHMLBA value, based on the platform shmlba = { QL_ARCH.MIPS: 0x40000, + QL_ARCH.MIPS64: 0x40000, QL_ARCH.ARM: ql.mem.pagesize * 4, QL_ARCH.ARM64: ql.mem.pagesize * 4, QL_ARCH.X86: ql.mem.pagesize, diff --git a/qiling/os/posix/syscall/signal.py b/qiling/os/posix/syscall/signal.py index 1591cb558..bbbf08df8 100644 --- a/qiling/os/posix/syscall/signal.py +++ b/qiling/os/posix/syscall/signal.py @@ -29,6 +29,7 @@ def __make_sigset(arch: QlArch): QL_ARCH.ARM: native_type, QL_ARCH.ARM64: native_type, QL_ARCH.MIPS: ctypes.c_uint32 * (128 // (4 * 8)), + QL_ARCH.MIPS64: ctypes.c_uint32 * (128 // (4 * 8)), QL_ARCH.CORTEX_M: native_type } @@ -108,6 +109,7 @@ class mips_sigaction(Struct): QL_ARCH.ARM: arm_sigaction, QL_ARCH.ARM64: arm_sigaction, QL_ARCH.MIPS: mips_sigaction, + QL_ARCH.MIPS64: mips_sigaction, QL_ARCH.CORTEX_M: arm_sigaction } @@ -119,7 +121,7 @@ class mips_sigaction(Struct): def ql_syscall_rt_sigaction(ql: Qiling, signum: int, act: int, oldact: int): SIGKILL = 9 - SIGSTOP = 23 if ql.arch.type is QL_ARCH.MIPS else 19 + SIGSTOP = 23 if ql.arch.type in (QL_ARCH.MIPS, QL_ARCH.MIPS64) else 19 if signum not in range(NSIG) or signum in (SIGKILL, SIGSTOP): return -1 # EINVAL @@ -182,7 +184,7 @@ def __sigprocmask_mips(ql: Qiling, how: int, newset: int, oldset: int): def ql_syscall_rt_sigprocmask(ql: Qiling, how: int, newset: int, oldset: int): - impl = __sigprocmask_mips if ql.arch.type is QL_ARCH.MIPS else __sigprocmask + impl = __sigprocmask_mips if ql.arch.type in (QL_ARCH.MIPS, QL_ARCH.MIPS64) else __sigprocmask return impl(ql, how, newset, oldset) diff --git a/qiling/os/posix/syscall/socket.py b/qiling/os/posix/syscall/socket.py index ea6ee93ba..9774ac774 100644 --- a/qiling/os/posix/syscall/socket.py +++ b/qiling/os/posix/syscall/socket.py @@ -146,7 +146,7 @@ def __host_socket_option(vsock_level: int, vsock_opt: int, arch_type: QL_ARCH, o vsock_opt_name = socket_option_mapping(vsock_opt, arch_type) # Fix for mips - if arch_type == QL_ARCH.MIPS: + if arch_type in (QL_ARCH.MIPS, QL_ARCH.MIPS64): if vsock_opt_name.endswith(('_NEW', '_OLD')): vsock_opt_name = vsock_opt_name[:-4] diff --git a/qiling/os/posix/syscall/stat.py b/qiling/os/posix/syscall/stat.py index 5b153100a..4786366ed 100644 --- a/qiling/os/posix/syscall/stat.py +++ b/qiling/os/posix/syscall/stat.py @@ -1115,7 +1115,7 @@ def get_stat_struct(ql: Qiling): return LinuxX8664Stat() elif ql.arch.type == QL_ARCH.X86: return LinuxX86Stat() - elif ql.arch.type == QL_ARCH.MIPS: + elif ql.arch.type in (QL_ARCH.MIPS, QL_ARCH.MIPS64): if ql.arch.bits == 64: if ql.arch.endian == QL_ENDIAN.EL: return LinuxMips64Stat() diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index 09b86c380..9676de94b 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -751,7 +751,7 @@ def ql_syscall_pipe(ql: Qiling, pipefd: int): ql.os.fd[idx1] = rd ql.os.fd[idx2] = wd - if ql.arch.type == QL_ARCH.MIPS: + if ql.arch.type in (QL_ARCH.MIPS, QL_ARCH.MIPS64): ql.arch.regs.v1 = idx2 regreturn = idx1 else: