mirror of
https://github.com/qilingframework/qiling.git
synced 2026-10-12 02:21:40 +00:00
Implement some syscalls to support Android Runtime
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
|
||||
Qiling is an advanced binary emulation framework, with the following features:
|
||||
|
||||
- Emulate multi-platforms: Windows, MacOS, Linux, BSD, UEFI, DOS, MBR, Ethereum Virtual Machine
|
||||
- Emulate multi-platforms: Windows, MacOS, Linux, Android, BSD, UEFI, DOS, MBR, Ethereum Virtual Machine
|
||||
- Emulate multi-architectures: 8086, X86, X86_64, ARM, ARM64, MIPS, RISCV, PowerPC
|
||||
- Support multiple file formats: PE, MachO, ELF, COM, MBR
|
||||
- Support Windows Driver (.sys), Linux Kernel Module (.ko) & MacOS Kernel (.kext) via [Demigod](https://groundx.io/demigod/)
|
||||
|
||||
+1
-1
Submodule examples/rootfs updated: 76780c9e91...15c8d84287
@@ -10,24 +10,25 @@ from gevent.event import Event
|
||||
from queue import Queue
|
||||
|
||||
class QlLinuxFutexManagement:
|
||||
|
||||
|
||||
FUTEX_BITSET_MATCH_ANY = 0xffffffff
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self._wait_list = {}
|
||||
|
||||
|
||||
@property
|
||||
def wait_list(self):
|
||||
return self._wait_list
|
||||
|
||||
|
||||
def futex_wait(self, ql, uaddr, t, val, bitset=FUTEX_BITSET_MATCH_ANY):
|
||||
EAGAIN = 11
|
||||
def _sched_wait_event(cur_thread):
|
||||
ql.log.debug(f"Wait for notifications.")
|
||||
event.wait()
|
||||
uaddr_value = ql.unpack32(ql.mem.read(uaddr, 4))
|
||||
if uaddr_value != val:
|
||||
ql.log.debug(f"uaddr: {hex(uaddr_value)} != {hex(val)}")
|
||||
return -1
|
||||
return -EAGAIN
|
||||
ql.emu_stop()
|
||||
if uaddr not in self.wait_list.keys():
|
||||
self.wait_list[uaddr] = Queue()
|
||||
@@ -35,7 +36,7 @@ class QlLinuxFutexManagement:
|
||||
self.wait_list[uaddr].put((bitset, t, event))
|
||||
t.sched_cb = _sched_wait_event
|
||||
return 0
|
||||
|
||||
|
||||
def get_futex_wake_list(self, ql, addr, number, bitset=FUTEX_BITSET_MATCH_ANY):
|
||||
wakes = []
|
||||
if addr not in self.wait_list or number == 0:
|
||||
|
||||
@@ -7,9 +7,11 @@ from .futex import *
|
||||
from .ioctl import *
|
||||
from .mman import *
|
||||
from .net import *
|
||||
from .personality import *
|
||||
from .poll import *
|
||||
from .prctl import *
|
||||
from .ptrace import *
|
||||
from .random import *
|
||||
from .resource import *
|
||||
from .sched import *
|
||||
from .select import *
|
||||
@@ -25,4 +27,3 @@ from .uio import *
|
||||
from .unistd import *
|
||||
from .utsname import *
|
||||
from .wait import *
|
||||
from .random import *
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
|
||||
from qiling import Qiling
|
||||
from qiling.const import *
|
||||
|
||||
def ql_syscall_personality(ql: Qiling, persona: int):
|
||||
regreturn = 0
|
||||
return regreturn
|
||||
@@ -19,9 +19,16 @@ import os
|
||||
from qiling import Qiling
|
||||
|
||||
def __getrlimit_common(ql: Qiling, res: int, rlim: int) -> int:
|
||||
rlimit = resource.getrlimit(res)
|
||||
ql.mem.write(rlim, ql.pack32s(rlimit[0]) + ql.pack32s(rlimit[1]))
|
||||
|
||||
RLIMIT_STACK = 3
|
||||
if res == RLIMIT_STACK:
|
||||
if ql.arch.bits == 64:
|
||||
stack_size = int(ql.os.profile.get("OS64", "stack_size"), 16)
|
||||
elif ql.arch.bits == 32:
|
||||
stack_size = int(ql.os.profile.get("OS32", "stack_size"), 16)
|
||||
rlimit = (stack_size, -1)
|
||||
else:
|
||||
rlimit = resource.getrlimit(res)
|
||||
ql.mem.write(rlim, ql.pack64s(rlimit[0]) + ql.pack64s(rlimit[1]))
|
||||
return 0
|
||||
|
||||
def ql_syscall_ugetrlimit(ql: Qiling, res: int, rlim: int):
|
||||
@@ -30,23 +37,29 @@ def ql_syscall_ugetrlimit(ql: Qiling, res: int, rlim: int):
|
||||
def ql_syscall_getrlimit(ql: Qiling, res: int, rlim: int):
|
||||
return __getrlimit_common(ql, res, rlim)
|
||||
|
||||
def ql_syscall_setrlimit(ql: Qiling, setrlimit_resource: int, setrlimit_rlim: int):
|
||||
def ql_syscall_setrlimit(ql: Qiling, res: int, rlim: int):
|
||||
# maybe we can nop the setrlimit
|
||||
tmp_rlim = (ql.unpack32s(ql.mem.read(setrlimit_rlim, 4)), ql.unpack32s(ql.mem.read(setrlimit_rlim + 4, 4)))
|
||||
resource.setrlimit(setrlimit_resource, tmp_rlim)
|
||||
tmp_rlim = (ql.unpack32s(ql.mem.read(rlim, 4)), ql.unpack32s(ql.mem.read(rlim + 4, 4)))
|
||||
resource.setrlimit(res, tmp_rlim)
|
||||
|
||||
return 0
|
||||
|
||||
def ql_syscall_prlimit64(ql: Qiling, pid: int, res: int, new_limit: int, old_limit: int):
|
||||
# setrlimit() and getrlimit()
|
||||
if pid == 0 and new_limit == 0:
|
||||
rlim = resource.getrlimit(res)
|
||||
ql.mem.write(old_limit, ql.packs(rlim[0]) + ql.packs(rlim[1]))
|
||||
|
||||
return 0
|
||||
try:
|
||||
rlim = resource.getrlimit(res)
|
||||
ql.mem.write(old_limit, ql.packs(rlim[0]) + ql.packs(rlim[1]))
|
||||
return 0
|
||||
except:
|
||||
return -1
|
||||
|
||||
# set other process which pid != 0
|
||||
return -1
|
||||
|
||||
def ql_syscall_getpriority(ql: Qiling, getpriority_which: int, getpriority_who: int):
|
||||
return os.getpriority(getpriority_which, getpriority_who)
|
||||
def ql_syscall_getpriority(ql: Qiling, which: int, who: int):
|
||||
try:
|
||||
regreturn = os.getpriority(which, who)
|
||||
except:
|
||||
regreturn = -1
|
||||
return regreturn
|
||||
|
||||
@@ -1208,11 +1208,11 @@ def transform_path(ql: Qiling, dirfd: int, path: int, flags: int = 0):
|
||||
then the target file is the one referred to by the file descriptor dirfd.
|
||||
"""
|
||||
|
||||
dirfd = ql.unpacks(ql.pack(dirfd))
|
||||
dirfd = ql.unpack32s(ql.pack32(dirfd & (1<<32)-1))
|
||||
path = ql.os.utils.read_cstring(path)
|
||||
|
||||
if path.startswith('/'):
|
||||
return None, os.path.join(ql.rootfs, path)
|
||||
return None, os.path.join(ql.rootfs, path.lstrip('/'))
|
||||
|
||||
if dirfd == AT_FDCWD:
|
||||
return None, ql.os.path.transform_to_real_path(path)
|
||||
@@ -1225,15 +1225,26 @@ def transform_path(ql: Qiling, dirfd: int, path: int, flags: int = 0):
|
||||
|
||||
|
||||
def ql_syscall_chmod(ql: Qiling, filename: int, mode: int):
|
||||
file_path = ql.os.utils.read_cstring(filename)
|
||||
real_path = ql.os.path.transform_to_real_path(file_path)
|
||||
try:
|
||||
os.chmod(real_path, mode)
|
||||
regreturn = 0
|
||||
except:
|
||||
regreturn = -1
|
||||
ql.log.debug(f'chmod("{ql.os.utils.read_cstring(filename)}", {mode:d}) = 0')
|
||||
|
||||
return 0
|
||||
return regreturn
|
||||
|
||||
def ql_syscall_fchmod(ql: Qiling, fd: int, mode: int):
|
||||
if fd not in range(NR_OPEN) or ql.os.fd[fd] is None:
|
||||
return -EBADF
|
||||
|
||||
return 0
|
||||
try:
|
||||
os.fchmod(ql.os.fd[fd].fileno(), mode)
|
||||
regreturn = 0
|
||||
except:
|
||||
regreturn = -1
|
||||
ql.log.debug("fchmod(%d, %d) = %d" % (fd, mode, regreturn))
|
||||
return regreturn
|
||||
|
||||
def ql_syscall_fstatat64(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flags: int):
|
||||
dirfd, real_path = transform_path(ql, dirfd, path, flags)
|
||||
@@ -1460,6 +1471,7 @@ def ql_syscall_mknodat(ql: Qiling, dirfd: int, path: int, mode: int, dev: int):
|
||||
except:
|
||||
regreturn = -1
|
||||
|
||||
ql.log.debug("mknodat(%d, %s, 0%o, %d) = %d" % (dirfd, real_path, mode, dev, regreturn))
|
||||
return regreturn
|
||||
|
||||
|
||||
@@ -1474,6 +1486,7 @@ def ql_syscall_mkdir(ql: Qiling, pathname: int, mode: int):
|
||||
except:
|
||||
regreturn = -1
|
||||
|
||||
ql.log.debug("mkdir(%s, 0%o) = %d" % (real_path, mode, regreturn))
|
||||
return regreturn
|
||||
|
||||
def ql_syscall_rmdir(ql: Qiling, pathname: int):
|
||||
|
||||
@@ -116,6 +116,27 @@ def ql_syscall_capset(ql: Qiling, hdrp: int, datap: int):
|
||||
def ql_syscall_kill(ql: Qiling, pid: int, sig: int):
|
||||
return 0
|
||||
|
||||
|
||||
def ql_syscall_fsync(ql: Qiling, fd: int):
|
||||
try:
|
||||
os.fsync(ql.os.fd[fd].fileno())
|
||||
regreturn = 0
|
||||
except:
|
||||
regreturn = -1
|
||||
ql.log.debug("fsync(%d) = %d" % (fd, regreturn))
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_fdatasync(ql: Qiling, fd: int):
|
||||
try:
|
||||
os.fdatasync(ql.os.fd[fd].fileno())
|
||||
regreturn = 0
|
||||
except:
|
||||
regreturn = -1
|
||||
ql.log.debug("fdatasync(%d) = %d" % (fd, regreturn))
|
||||
return regreturn
|
||||
|
||||
|
||||
def ql_syscall_faccessat(ql: Qiling, dfd: int, filename: int, mode: int):
|
||||
access_path = ql.os.utils.read_cstring(filename)
|
||||
real_path = ql.os.path.transform_to_real_path(access_path)
|
||||
@@ -534,8 +555,6 @@ def ql_syscall_dup3(ql: Qiling, fd: int, newfd: int, flags: int):
|
||||
|
||||
def ql_syscall_set_tid_address(ql: Qiling, tidptr: int):
|
||||
if ql.os.thread_management:
|
||||
ql.os.thread_management.cur_thread.set_clear_child_tid_addr(tidptr)
|
||||
|
||||
regreturn = ql.os.thread_management.cur_thread.id
|
||||
else:
|
||||
regreturn = os.getpid()
|
||||
|
||||
@@ -19,7 +19,7 @@ stack_address = 0x7ff0d000
|
||||
stack_size = 0x30000
|
||||
load_address = 0x56555000
|
||||
interp_address = 0x047ba000
|
||||
mmap_address = 0x774bf000
|
||||
mmap_address = 0x90000000
|
||||
|
||||
|
||||
[KERNEL]
|
||||
@@ -48,4 +48,4 @@ current_path = /
|
||||
# To use IPv6 or not, to avoid binary double bind. ipv6 and ipv4 bind the same port at the same time
|
||||
bindtolocalhost = True
|
||||
# Bind to localhost
|
||||
ipv6 = False
|
||||
ipv6 = False
|
||||
|
||||
+39
-9
@@ -4,26 +4,56 @@
|
||||
#
|
||||
|
||||
import os, platform, sys, unittest
|
||||
from collections import defaultdict
|
||||
|
||||
sys.path.append("..")
|
||||
from qiling import Qiling
|
||||
from qiling.const import QL_VERBOSE
|
||||
from qiling.os.mapper import QlFsMappedObject
|
||||
from qiling.os.posix import syscall
|
||||
|
||||
|
||||
class Fake_maps(QlFsMappedObject):
|
||||
def __init__(self, ql):
|
||||
self.ql = ql
|
||||
def read(self, size):
|
||||
stack = next(filter(lambda x : x[3]=='[stack]', self.ql.mem.map_info))
|
||||
return ('%x-%x %s\n' % (stack[0], stack[1], stack[3])).encode()
|
||||
def fstat(self):
|
||||
return defaultdict(int)
|
||||
def close(self):
|
||||
return 0
|
||||
|
||||
def my_syscall_close(ql, fd):
|
||||
if fd in [0, 1, 2]:
|
||||
return 0
|
||||
return syscall.ql_syscall_close(ql, fd)
|
||||
|
||||
|
||||
class TestAndroid(unittest.TestCase):
|
||||
@unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux')
|
||||
def test_android_arm64(self):
|
||||
test_binary = "../examples/rootfs/arm64_android6.0/bin/arm64_android_hello"
|
||||
test_binary = "../examples/rootfs/arm64_android6.0/bin/arm64_android_jniart"
|
||||
rootfs = "../examples/rootfs/arm64_android6.0"
|
||||
env = {"ANDROID_DATA":"/data", "ANDROID_ROOT":"/system"}
|
||||
|
||||
# FUTURE FIX: at this stage, need a file called /proc/self/exe in the rootfs - Android linker calls stat against /proc/self/exe and bails if it can't find it
|
||||
# qiling handles readlink against /proc/self/exe, but doesn't handle it in stat
|
||||
# https://cs.android.com/android/platform/superproject/+/master:bionic/linker/linker_main.cpp;l=221
|
||||
self.assertTrue(os.path.isfile(os.path.join(rootfs, "proc", "self", "exe")), rootfs +
|
||||
"/proc/self/exe not found, Android linker will bail. Need a file at that location (empty is fine)")
|
||||
|
||||
ql = Qiling([test_binary], rootfs, verbose=QL_VERBOSE.DEBUG, multithread=True)
|
||||
ql = Qiling([test_binary], rootfs, env, multithread=True)
|
||||
ql.os.set_syscall("close", my_syscall_close)
|
||||
ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql))
|
||||
ql.run()
|
||||
del ql
|
||||
|
||||
|
||||
#@unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux')
|
||||
#def test_android_arm(self):
|
||||
# test_binary = "../examples/rootfs/arm64_android6.0/bin/arm_android_jniart"
|
||||
# rootfs = "../examples/rootfs/arm64_android6.0"
|
||||
# env = {"ANDROID_DATA":"/data", "ANDROID_ROOT":"/system"}
|
||||
|
||||
# ql = Qiling([test_binary], rootfs, env, multithread=True)
|
||||
# ql.os.set_syscall("close", my_syscall_close)
|
||||
# ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql))
|
||||
# ql.run()
|
||||
# del ql
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user