Implement some syscalls to support Android Runtime

This commit is contained in:
Bet4
2022-05-03 20:24:24 +08:00
parent aff5f2e8b5
commit 253a298d8f
10 changed files with 131 additions and 40 deletions
+1 -1
View File
@@ -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/)
+7 -6
View File
@@ -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:
+2 -1
View File
@@ -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 *
+14
View File
@@ -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
+25 -12
View File
@@ -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
+19 -6
View File
@@ -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):
+21 -2
View File
@@ -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()
+2 -2
View File
@@ -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
View File
@@ -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__":