|
|
@@ -0,0 +1,239 @@
|
|
|
+class Log {
|
|
|
+ static TAG = '[system_server]'
|
|
|
+ static Debug = true
|
|
|
+ static format(...msg) {
|
|
|
+ let m = []
|
|
|
+ for (let i = 0; i < msg.length; i++) {
|
|
|
+ if (typeof msg[i] === 'object') {
|
|
|
+ m.push(msg[i] + '')
|
|
|
+ } else {
|
|
|
+ m.push(msg[i])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ m = m.join(' ')
|
|
|
+ return m
|
|
|
+ }
|
|
|
+ static i(...msg) {
|
|
|
+ if (!this.Debug) return
|
|
|
+ console.log(`\x1b[30m${this.TAG} ${this.format(...msg)}\x1b[0m`)
|
|
|
+ }
|
|
|
+ static w(...msg) {
|
|
|
+ console.log(`\x1b[33m${this.TAG} ${this.format(...msg)}\x1b[0m`)
|
|
|
+ }
|
|
|
+ static e(...msg) {
|
|
|
+ console.log(`\x1b[31m${this.TAG} ${this.format(...msg)}\x1b[0m`)
|
|
|
+ }
|
|
|
+ static s(...msg) {
|
|
|
+ console.log(`\x1b[32m${this.TAG} ${this.format(...msg)}\x1b[0m`)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function trace(tag) {
|
|
|
+ Log.e((tag || '') + Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Throwable').$new()))
|
|
|
+}
|
|
|
+
|
|
|
+function randomMac() {
|
|
|
+ var mac = '00:16:3e'
|
|
|
+ for (var i = 0; i < 3; i++) {
|
|
|
+ mac += ':' + ('00' + Math.floor(Math.random() * 256).toString(16)).slice(-2)
|
|
|
+ }
|
|
|
+ return mac
|
|
|
+}
|
|
|
+
|
|
|
+function buff2json(buf) {
|
|
|
+ console.log(`buffer length: ${buf.byteLength}`)
|
|
|
+ try {
|
|
|
+ var decoded = String.fromCharCode(...new Uint8Array(buf))
|
|
|
+ console.log(`decoded: ${decoded}`)
|
|
|
+ return JSON.parse(decoded.trim())
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e)
|
|
|
+ return null
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class Interaction {
|
|
|
+ failure(err) {
|
|
|
+ console.error(err.message)
|
|
|
+ Java.use('android.util.Log').d('frida-system_server', err.message)
|
|
|
+ }
|
|
|
+
|
|
|
+ accepted(connection) {
|
|
|
+ console.warn('accepted')
|
|
|
+ connection.input.read(2000).then((data) => {
|
|
|
+ Java.use('android.util.Log').d('frida-system_server', data + '')
|
|
|
+ try {
|
|
|
+ const json = buff2json(data)
|
|
|
+ console.log('received', json)
|
|
|
+ this.messageFn && this.messageFn(json)
|
|
|
+ } catch (e) {}
|
|
|
+ connection.close()
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ accept_loop(listener) {
|
|
|
+ var next_iter = this.accept_loop.bind(this, listener)
|
|
|
+ listener
|
|
|
+ .accept()
|
|
|
+ .then(this.accepted.bind(this))
|
|
|
+ .catch(this.failure.bind(this))
|
|
|
+ .finally(function () {
|
|
|
+ setImmediate(next_iter)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ listened(listener) {
|
|
|
+ console.warn('listened')
|
|
|
+ this.accept_loop(listener)
|
|
|
+ }
|
|
|
+
|
|
|
+ start(port, messageFn) {
|
|
|
+ this.messageFn = messageFn
|
|
|
+ console.warn('starting on port', port)
|
|
|
+ Socket.listen({ family: 'ipv4', host: '0.0.0.0', port: port })
|
|
|
+ .then(this.listened.bind(this))
|
|
|
+ .catch(this.failure.bind(this))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+setImmediate(() => {
|
|
|
+ Java.perform(function () {
|
|
|
+ const Uri = Java.use('android.net.Uri')
|
|
|
+ const File = Java.use('java.io.File')
|
|
|
+
|
|
|
+ const BufferedReader = Java.use('java.io.BufferedReader')
|
|
|
+ const FileInputStream = Java.use('java.io.FileInputStream')
|
|
|
+ const FileOutputStream = Java.use('java.io.FileOutputStream')
|
|
|
+ const InputStreamReader = Java.use('java.io.InputStreamReader')
|
|
|
+ const OutputStreamWriter = Java.use('java.io.OutputStreamWriter')
|
|
|
+
|
|
|
+ function getContext() {
|
|
|
+ try {
|
|
|
+ var ActivityThread = Java.use('android.app.ActivityThread')
|
|
|
+ var application = ActivityThread.currentApplication()
|
|
|
+ return application.getApplicationContext()
|
|
|
+ } catch (e) {
|
|
|
+ console.log(e)
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function readFile(file) {
|
|
|
+ if (!file.exists()) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ var fileInputStream = FileInputStream.$new(file)
|
|
|
+
|
|
|
+ var inputStreamReader = InputStreamReader.$new(Java.cast(fileInputStream, Java.use('java.io.InputStream')))
|
|
|
+ var bufferedReader = BufferedReader.$new(inputStreamReader)
|
|
|
+ var line
|
|
|
+ var content = ''
|
|
|
+ while ((line = bufferedReader.readLine()) !== null) {
|
|
|
+ content += line + '\n'
|
|
|
+ }
|
|
|
+
|
|
|
+ bufferedReader.close()
|
|
|
+ inputStreamReader.close()
|
|
|
+ fileInputStream.close()
|
|
|
+
|
|
|
+ return content
|
|
|
+ }
|
|
|
+
|
|
|
+ function writeFile(file, content) {
|
|
|
+ if (!file.exists()) {
|
|
|
+ file.createNewFile()
|
|
|
+ }
|
|
|
+ var fileOutputStream = FileOutputStream.$new(file)
|
|
|
+ var outputStreamWriter = OutputStreamWriter.$new(
|
|
|
+ Java.cast(fileOutputStream, Java.use('java.io.OutputStream'))
|
|
|
+ )
|
|
|
+ outputStreamWriter.write(content, 0, content.length)
|
|
|
+ outputStreamWriter.flush()
|
|
|
+ outputStreamWriter.close()
|
|
|
+ fileOutputStream.close()
|
|
|
+ }
|
|
|
+
|
|
|
+ function readConfig() {
|
|
|
+ const configFile = File.$new('/data/system/config.json')
|
|
|
+ log(`read config from ${configFile.getAbsolutePath()}`)
|
|
|
+ const json = readFile(configFile)
|
|
|
+ if (!json) {
|
|
|
+ return {}
|
|
|
+ } else {
|
|
|
+ log(`config: ${json}`)
|
|
|
+ return JSON.parse(json)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function saveConfig(config) {
|
|
|
+ const configFile = File.$new('/data/system/config.json')
|
|
|
+ log(`save config to ${configFile.getAbsolutePath()}`)
|
|
|
+ const json = JSON.stringify(config)
|
|
|
+ log(`config: ${json}`)
|
|
|
+ writeFile(configFile, json)
|
|
|
+ }
|
|
|
+
|
|
|
+ function queryConfig(key) {
|
|
|
+ const context = getContext()
|
|
|
+ if (!context) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ const cr = context.getContentResolver()
|
|
|
+ const uri = Uri.parse('content://SimInfo')
|
|
|
+ const cursor = cr.query(uri, null, null, null, null)
|
|
|
+ if (!cursor) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ if (!cursor.moveToFirst()) {
|
|
|
+ cursor.close()
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ const idx = cursor.getColumnIndex(key)
|
|
|
+ if (idx < 0) {
|
|
|
+ cursor.close()
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ const value = cursor.getString(idx)
|
|
|
+ cursor.close()
|
|
|
+ return value
|
|
|
+ }
|
|
|
+
|
|
|
+ const classLoaders = Java.enumerateClassLoadersSync()
|
|
|
+ const SettingsProviderClassLoader = classLoaders.find((i) => i.toString().includes('SettingsProvider'))
|
|
|
+ Log.i('SettingsProviderClassLoader: ' + SettingsProviderClassLoader)
|
|
|
+ Java.classFactory.loader = SettingsProviderClassLoader
|
|
|
+ const genRanHex = (size) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('')
|
|
|
+
|
|
|
+ const ssaidGms = genRanHex(16)
|
|
|
+ const ssaidVending = genRanHex(16)
|
|
|
+ const SettingsProvider = Java.use('com.android.providers.settings.SettingsProvider')
|
|
|
+ SettingsProvider.getSecureSetting.overload('java.lang.String', 'int').implementation = function (key, userId) {
|
|
|
+ const res = this.getSecureSetting(key, userId)
|
|
|
+ Log.i(`getSecureSetting(${key}, ${userId}) = ${res}`)
|
|
|
+ return res
|
|
|
+ }
|
|
|
+ SettingsProvider.getGlobalSetting.overload('java.lang.String').implementation = function (key) {
|
|
|
+ const res = this.getGlobalSetting(key)
|
|
|
+ Log.i(`getGlobalSetting(${key}) = ${res}`)
|
|
|
+ return res
|
|
|
+ }
|
|
|
+ SettingsProvider.getSystemSetting.overload('java.lang.String', 'int').implementation = function (key, userId) {
|
|
|
+ const res = this.getSystemSetting(key, userId)
|
|
|
+ Log.i(`getSystemSetting(${key}, ${userId}) = ${res}`)
|
|
|
+ if (key === 'android_id') {
|
|
|
+ Log.e(`getSystemSetting(${key}, ${userId}) = ${ssaidGms}`)
|
|
|
+ }
|
|
|
+ return res
|
|
|
+ }
|
|
|
+ SettingsProvider.query.overload(
|
|
|
+ 'android.net.Uri',
|
|
|
+ '[Ljava.lang.String;',
|
|
|
+ 'java.lang.String',
|
|
|
+ '[Ljava.lang.String;',
|
|
|
+ 'java.lang.String'
|
|
|
+ ).implementation = function (uri, projection, selection, selectionArgs, sortOrder) {
|
|
|
+ Log.i(`query(${uri}, ${projection}, ${selection}, ${selectionArgs}, ${sortOrder})`)
|
|
|
+ return this.query(uri, projection, selection, selectionArgs, sortOrder)
|
|
|
+ }
|
|
|
+ })
|
|
|
+})
|