Skip to content

Commit 253a298

Browse files
committed
Implement some syscalls to support Android Runtime
1 parent aff5f2e commit 253a298

File tree

10 files changed

+131
-40
lines changed

10 files changed

+131
-40
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
Qiling is an advanced binary emulation framework, with the following features:
1414

15-
- Emulate multi-platforms: Windows, MacOS, Linux, BSD, UEFI, DOS, MBR, Ethereum Virtual Machine
15+
- Emulate multi-platforms: Windows, MacOS, Linux, Android, BSD, UEFI, DOS, MBR, Ethereum Virtual Machine
1616
- Emulate multi-architectures: 8086, X86, X86_64, ARM, ARM64, MIPS, RISCV, PowerPC
1717
- Support multiple file formats: PE, MachO, ELF, COM, MBR
1818
- Support Windows Driver (.sys), Linux Kernel Module (.ko) & MacOS Kernel (.kext) via [Demigod](https://groundx.io/demigod/)

examples/rootfs

Submodule rootfs updated 115 files

qiling/os/linux/futex.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,33 @@
1010
from queue import Queue
1111

1212
class QlLinuxFutexManagement:
13-
13+
1414
FUTEX_BITSET_MATCH_ANY = 0xffffffff
15-
15+
1616
def __init__(self):
1717
self._wait_list = {}
18-
18+
1919
@property
2020
def wait_list(self):
2121
return self._wait_list
22-
22+
2323
def futex_wait(self, ql, uaddr, t, val, bitset=FUTEX_BITSET_MATCH_ANY):
24+
EAGAIN = 11
2425
def _sched_wait_event(cur_thread):
2526
ql.log.debug(f"Wait for notifications.")
2627
event.wait()
2728
uaddr_value = ql.unpack32(ql.mem.read(uaddr, 4))
2829
if uaddr_value != val:
2930
ql.log.debug(f"uaddr: {hex(uaddr_value)} != {hex(val)}")
30-
return -1
31+
return -EAGAIN
3132
ql.emu_stop()
3233
if uaddr not in self.wait_list.keys():
3334
self.wait_list[uaddr] = Queue()
3435
event = Event()
3536
self.wait_list[uaddr].put((bitset, t, event))
3637
t.sched_cb = _sched_wait_event
3738
return 0
38-
39+
3940
def get_futex_wake_list(self, ql, addr, number, bitset=FUTEX_BITSET_MATCH_ANY):
4041
wakes = []
4142
if addr not in self.wait_list or number == 0:

qiling/os/posix/syscall/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
from .ioctl import *
88
from .mman import *
99
from .net import *
10+
from .personality import *
1011
from .poll import *
1112
from .prctl import *
1213
from .ptrace import *
14+
from .random import *
1315
from .resource import *
1416
from .sched import *
1517
from .select import *
@@ -25,4 +27,3 @@
2527
from .unistd import *
2628
from .utsname import *
2729
from .wait import *
28-
from .random import *
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
4+
#
5+
6+
import os
7+
8+
9+
from qiling import Qiling
10+
from qiling.const import *
11+
12+
def ql_syscall_personality(ql: Qiling, persona: int):
13+
regreturn = 0
14+
return regreturn

qiling/os/posix/syscall/resource.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,16 @@ def setrlimit(self, resource, rlim):
1919
from qiling import Qiling
2020

2121
def __getrlimit_common(ql: Qiling, res: int, rlim: int) -> int:
22-
rlimit = resource.getrlimit(res)
23-
ql.mem.write(rlim, ql.pack32s(rlimit[0]) + ql.pack32s(rlimit[1]))
24-
22+
RLIMIT_STACK = 3
23+
if res == RLIMIT_STACK:
24+
if ql.arch.bits == 64:
25+
stack_size = int(ql.os.profile.get("OS64", "stack_size"), 16)
26+
elif ql.arch.bits == 32:
27+
stack_size = int(ql.os.profile.get("OS32", "stack_size"), 16)
28+
rlimit = (stack_size, -1)
29+
else:
30+
rlimit = resource.getrlimit(res)
31+
ql.mem.write(rlim, ql.pack64s(rlimit[0]) + ql.pack64s(rlimit[1]))
2532
return 0
2633

2734
def ql_syscall_ugetrlimit(ql: Qiling, res: int, rlim: int):
@@ -30,23 +37,29 @@ def ql_syscall_ugetrlimit(ql: Qiling, res: int, rlim: int):
3037
def ql_syscall_getrlimit(ql: Qiling, res: int, rlim: int):
3138
return __getrlimit_common(ql, res, rlim)
3239

33-
def ql_syscall_setrlimit(ql: Qiling, setrlimit_resource: int, setrlimit_rlim: int):
40+
def ql_syscall_setrlimit(ql: Qiling, res: int, rlim: int):
3441
# maybe we can nop the setrlimit
35-
tmp_rlim = (ql.unpack32s(ql.mem.read(setrlimit_rlim, 4)), ql.unpack32s(ql.mem.read(setrlimit_rlim + 4, 4)))
36-
resource.setrlimit(setrlimit_resource, tmp_rlim)
42+
tmp_rlim = (ql.unpack32s(ql.mem.read(rlim, 4)), ql.unpack32s(ql.mem.read(rlim + 4, 4)))
43+
resource.setrlimit(res, tmp_rlim)
3744

3845
return 0
3946

4047
def ql_syscall_prlimit64(ql: Qiling, pid: int, res: int, new_limit: int, old_limit: int):
4148
# setrlimit() and getrlimit()
4249
if pid == 0 and new_limit == 0:
43-
rlim = resource.getrlimit(res)
44-
ql.mem.write(old_limit, ql.packs(rlim[0]) + ql.packs(rlim[1]))
45-
46-
return 0
50+
try:
51+
rlim = resource.getrlimit(res)
52+
ql.mem.write(old_limit, ql.packs(rlim[0]) + ql.packs(rlim[1]))
53+
return 0
54+
except:
55+
return -1
4756

4857
# set other process which pid != 0
4958
return -1
5059

51-
def ql_syscall_getpriority(ql: Qiling, getpriority_which: int, getpriority_who: int):
52-
return os.getpriority(getpriority_which, getpriority_who)
60+
def ql_syscall_getpriority(ql: Qiling, which: int, who: int):
61+
try:
62+
regreturn = os.getpriority(which, who)
63+
except:
64+
regreturn = -1
65+
return regreturn

qiling/os/posix/syscall/stat.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,11 +1208,11 @@ def transform_path(ql: Qiling, dirfd: int, path: int, flags: int = 0):
12081208
then the target file is the one referred to by the file descriptor dirfd.
12091209
"""
12101210

1211-
dirfd = ql.unpacks(ql.pack(dirfd))
1211+
dirfd = ql.unpack32s(ql.pack32(dirfd & (1<<32)-1))
12121212
path = ql.os.utils.read_cstring(path)
12131213

12141214
if path.startswith('/'):
1215-
return None, os.path.join(ql.rootfs, path)
1215+
return None, os.path.join(ql.rootfs, path.lstrip('/'))
12161216

12171217
if dirfd == AT_FDCWD:
12181218
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):
12251225

12261226

12271227
def ql_syscall_chmod(ql: Qiling, filename: int, mode: int):
1228+
file_path = ql.os.utils.read_cstring(filename)
1229+
real_path = ql.os.path.transform_to_real_path(file_path)
1230+
try:
1231+
os.chmod(real_path, mode)
1232+
regreturn = 0
1233+
except:
1234+
regreturn = -1
12281235
ql.log.debug(f'chmod("{ql.os.utils.read_cstring(filename)}", {mode:d}) = 0')
1229-
1230-
return 0
1236+
return regreturn
12311237

12321238
def ql_syscall_fchmod(ql: Qiling, fd: int, mode: int):
12331239
if fd not in range(NR_OPEN) or ql.os.fd[fd] is None:
12341240
return -EBADF
1235-
1236-
return 0
1241+
try:
1242+
os.fchmod(ql.os.fd[fd].fileno(), mode)
1243+
regreturn = 0
1244+
except:
1245+
regreturn = -1
1246+
ql.log.debug("fchmod(%d, %d) = %d" % (fd, mode, regreturn))
1247+
return regreturn
12371248

12381249
def ql_syscall_fstatat64(ql: Qiling, dirfd: int, path: int, buf_ptr: int, flags: int):
12391250
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):
14601471
except:
14611472
regreturn = -1
14621473

1474+
ql.log.debug("mknodat(%d, %s, 0%o, %d) = %d" % (dirfd, real_path, mode, dev, regreturn))
14631475
return regreturn
14641476

14651477

@@ -1474,6 +1486,7 @@ def ql_syscall_mkdir(ql: Qiling, pathname: int, mode: int):
14741486
except:
14751487
regreturn = -1
14761488

1489+
ql.log.debug("mkdir(%s, 0%o) = %d" % (real_path, mode, regreturn))
14771490
return regreturn
14781491

14791492
def ql_syscall_rmdir(ql: Qiling, pathname: int):

qiling/os/posix/syscall/unistd.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,27 @@ def ql_syscall_capset(ql: Qiling, hdrp: int, datap: int):
116116
def ql_syscall_kill(ql: Qiling, pid: int, sig: int):
117117
return 0
118118

119+
120+
def ql_syscall_fsync(ql: Qiling, fd: int):
121+
try:
122+
os.fsync(ql.os.fd[fd].fileno())
123+
regreturn = 0
124+
except:
125+
regreturn = -1
126+
ql.log.debug("fsync(%d) = %d" % (fd, regreturn))
127+
return regreturn
128+
129+
130+
def ql_syscall_fdatasync(ql: Qiling, fd: int):
131+
try:
132+
os.fdatasync(ql.os.fd[fd].fileno())
133+
regreturn = 0
134+
except:
135+
regreturn = -1
136+
ql.log.debug("fdatasync(%d) = %d" % (fd, regreturn))
137+
return regreturn
138+
139+
119140
def ql_syscall_faccessat(ql: Qiling, dfd: int, filename: int, mode: int):
120141
access_path = ql.os.utils.read_cstring(filename)
121142
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):
534555

535556
def ql_syscall_set_tid_address(ql: Qiling, tidptr: int):
536557
if ql.os.thread_management:
537-
ql.os.thread_management.cur_thread.set_clear_child_tid_addr(tidptr)
538-
539558
regreturn = ql.os.thread_management.cur_thread.id
540559
else:
541560
regreturn = os.getpid()

qiling/profiles/linux.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ stack_address = 0x7ff0d000
1919
stack_size = 0x30000
2020
load_address = 0x56555000
2121
interp_address = 0x047ba000
22-
mmap_address = 0x774bf000
22+
mmap_address = 0x90000000
2323

2424

2525
[KERNEL]
@@ -48,4 +48,4 @@ current_path = /
4848
# To use IPv6 or not, to avoid binary double bind. ipv6 and ipv4 bind the same port at the same time
4949
bindtolocalhost = True
5050
# Bind to localhost
51-
ipv6 = False
51+
ipv6 = False

tests/test_android.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,56 @@
44
#
55

66
import os, platform, sys, unittest
7+
from collections import defaultdict
78

89
sys.path.append("..")
910
from qiling import Qiling
10-
from qiling.const import QL_VERBOSE
11+
from qiling.os.mapper import QlFsMappedObject
12+
from qiling.os.posix import syscall
13+
14+
15+
class Fake_maps(QlFsMappedObject):
16+
def __init__(self, ql):
17+
self.ql = ql
18+
def read(self, size):
19+
stack = next(filter(lambda x : x[3]=='[stack]', self.ql.mem.map_info))
20+
return ('%x-%x %s\n' % (stack[0], stack[1], stack[3])).encode()
21+
def fstat(self):
22+
return defaultdict(int)
23+
def close(self):
24+
return 0
25+
26+
def my_syscall_close(ql, fd):
27+
if fd in [0, 1, 2]:
28+
return 0
29+
return syscall.ql_syscall_close(ql, fd)
1130

1231

1332
class TestAndroid(unittest.TestCase):
1433
@unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux')
1534
def test_android_arm64(self):
16-
test_binary = "../examples/rootfs/arm64_android6.0/bin/arm64_android_hello"
35+
test_binary = "../examples/rootfs/arm64_android6.0/bin/arm64_android_jniart"
1736
rootfs = "../examples/rootfs/arm64_android6.0"
37+
env = {"ANDROID_DATA":"/data", "ANDROID_ROOT":"/system"}
1838

19-
# 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
20-
# qiling handles readlink against /proc/self/exe, but doesn't handle it in stat
21-
# https://cs.android.com/android/platform/superproject/+/master:bionic/linker/linker_main.cpp;l=221
22-
self.assertTrue(os.path.isfile(os.path.join(rootfs, "proc", "self", "exe")), rootfs +
23-
"/proc/self/exe not found, Android linker will bail. Need a file at that location (empty is fine)")
24-
25-
ql = Qiling([test_binary], rootfs, verbose=QL_VERBOSE.DEBUG, multithread=True)
39+
ql = Qiling([test_binary], rootfs, env, multithread=True)
40+
ql.os.set_syscall("close", my_syscall_close)
41+
ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql))
2642
ql.run()
43+
del ql
44+
45+
46+
#@unittest.skipUnless(platform.system() == 'Linux', 'run only on Linux')
47+
#def test_android_arm(self):
48+
# test_binary = "../examples/rootfs/arm64_android6.0/bin/arm_android_jniart"
49+
# rootfs = "../examples/rootfs/arm64_android6.0"
50+
# env = {"ANDROID_DATA":"/data", "ANDROID_ROOT":"/system"}
51+
52+
# ql = Qiling([test_binary], rootfs, env, multithread=True)
53+
# ql.os.set_syscall("close", my_syscall_close)
54+
# ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql))
55+
# ql.run()
56+
# del ql
2757

2858

2959
if __name__ == "__main__":

0 commit comments

Comments
 (0)