├── LICENSE ├── README.md └── memory.lua /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android-XScript-Memory 2 | Memory module for Android XScript written in lua and ffi. 3 | 4 | It should work on Ubuntu too. 5 | 6 | Test it with luajit 2.1.0 beta on Ubuntu machine first. 7 | 8 | # TODO 9 | - Implement pidof 10 | - add memory read 11 | - add memory write 12 | -------------------------------------------------------------------------------- /memory.lua: -------------------------------------------------------------------------------- 1 | local ffi = require("ffi") 2 | 3 | local export = {} 4 | 5 | ffi.cdef [[ 6 | typedef union value_t { 7 | int8_t int8_value; 8 | int16_t int16_value; 9 | int32_t int32_value; 10 | int64_t int64_value; 11 | float float32_value; 12 | double float64_value; 13 | uint8_t bytes[8]; 14 | }; 15 | ]] 16 | 17 | local function getTypeMaxMin(type) 18 | local width = 8 19 | if type == "int16_t" then width = 16 end 20 | if type == "int32_t" then width = 32 end 21 | if type == "int64_t" then width = 64 end 22 | local result = math.pow(2, width-1) 23 | return result-1, -result 24 | end 25 | 26 | -- Cache. Reduces time of calling, thus improve performance when region is large. 27 | local int8_max, int8_min = getTypeMaxMin("int8_t") 28 | local int16_max, int16_min = getTypeMaxMin("int16_t") 29 | local int32_max, int32_min = getTypeMaxMin("int32_t") 30 | local int64_max, int64_min = getTypeMaxMin("int64_t") 31 | 32 | ------------------------------------------------------------------------------ 33 | local function assertProcessExists(pid) 34 | local procFile = io.open("/proc/" .. pid .. "/maps", "r") 35 | if procFile == nil then 36 | error("'pid' is not correct, process does not exists") -- the process should be dead 37 | end 38 | io.close(procFile) 39 | end 40 | ------------------------------------------------------------------------------ 41 | local function filterRegions(line, regionName) 42 | local startAddr, endAddr, permission, offset, dev1, dev2, length, _, from = string.match(line, "(%x+)-(%x+) (.+) (%x+) (%x+):(%x+) (%d+) (%s*) (.*)") 43 | if startAddr == nil then -- if the "from" field is nil, all variables will be nil. Filter again. 44 | startAddr, endAddr, permission, offset, dev1, dev2, length, from = string.match(line, "(%x+)-(%x+) (.+) (%x+) (%x+):(%x+) (%d+) (%s*)") 45 | end 46 | if permission ~= "rw-p" then return nil end 47 | if from ~= regionName then return nil end 48 | return startAddr, endAddr 49 | end 50 | 51 | local function readMaps(pid, regionName) 52 | local mapsFile = io.open("/proc/" .. pid .. "/maps", "r") 53 | if mapsFile == nil then 54 | error("nil mapsFile with pid: " .. pid) 55 | end 56 | 57 | local region = {} 58 | for line in mapsFile:lines() do 59 | local startAddr, endAddr = filterRegions(line, regionName) 60 | if startAddr ~= nil and endAddr ~= nil then 61 | region[#region+1] = {} 62 | region[#region][0] = tonumber(startAddr,16) 63 | region[#region][1] = tonumber(endAddr,16) 64 | end 65 | end 66 | mapsFile:close() 67 | return region 68 | end 69 | ------------------------------------------------------------------------------ 70 | local function search(pid, value, regionName) 71 | assertProcessExists(pid) -- Checks if process exists 72 | local regions = readMaps(pid, regionName) -- Read the valid maps regions 73 | 74 | local memFile = io.open("/proc/" .. pid .. "/mem", "r") 75 | if memFile == nil then 76 | error("nil memFile with pid: " .. pid) 77 | end 78 | 79 | if value == math.floor(value) then -- int value 80 | local paramValue = ffi.new("union value_t", { int64_value = value }) 81 | local paramValueFirstByte = paramValue.bytes[0] -- MUST cache!!! 82 | local scannedValue = ffi.new("union value_t", {}) 83 | 84 | for mapsIndex=1,#regions do 85 | local startAddr = regions[mapsIndex][0] 86 | local endAddr = regions[mapsIndex][1] 87 | local length = endAddr-startAddr 88 | memFile:seek("set", startAddr) 89 | local data = memFile:read(length) 90 | if data ~= nil then 91 | for offset=1,length,4 do 92 | if paramValueFirstByte == data:byte(offset) then 93 | scannedValue.bytes = data:sub(offset, offset+ffi.sizeof("int64_t")) 94 | if not (value < int8_min or value > int8_max) then 95 | if value == scannedValue.int8_value then 96 | print("found at " .. string.format("%x", startAddr+offset-1) .. " type: int8") 97 | end 98 | end 99 | if not (value < int16_min or value > int16_max) then 100 | if value == scannedValue.int16_value then 101 | print("found at " .. string.format("%x", startAddr+offset-1) .. " type: int16") 102 | end 103 | end 104 | if not (value < int32_min or value > int32_max) then 105 | if value == scannedValue.int32_value then 106 | print("found at " .. string.format("%x", startAddr+offset-1) .. " type: int32") 107 | end 108 | end 109 | if not (value < int64_min or value > int64_max) then 110 | if value == scannedValue.int64_value then 111 | print("found at " .. string.format("%x", startAddr+offset-1) .. " type: int64") 112 | end 113 | end 114 | end 115 | end 116 | end 117 | end 118 | else -- float value 119 | local paramFloat32Value = ffi.new("union value_t", { float32_value = value }) 120 | local paramFloat64Value = ffi.new("union value_t", { float64_value = value }) 121 | local paramFloat32ValueFirstByte = paramFloat32Value.bytes[0] -- MUST cache!!! 122 | local paramFloat32ValueAccurate = paramFloat32Value.float32_value -- MUST cache!!! 123 | local paramFloat64ValueFirstByte = paramFloat64Value.bytes[0] -- MUST cache!!! 124 | local scannedValue = ffi.new("union value_t", {}) 125 | 126 | for mapsIndex=1,#regions do 127 | local startAddr = regions[mapsIndex][0] 128 | local endAddr = regions[mapsIndex][1] 129 | local length = endAddr-startAddr 130 | memFile:seek("set", startAddr) 131 | local data = memFile:read(length) 132 | if data ~= nil then 133 | for offset=1,length,ffi.sizeof("float") do 134 | local byte = data:byte(offset) 135 | if byte == paramFloat32ValueFirstByte then 136 | scannedValue.bytes = data:sub(offset, offset+ffi.sizeof("float")) 137 | if paramFloat32ValueAccurate == scannedValue.float32_value then 138 | print("found at " .. string.format("%x", startAddr+offset-1) .. " type: float32") 139 | end 140 | end 141 | if byte == paramFloat64ValueFirstByte then 142 | scannedValue.bytes = data:sub(offset, offset+ffi.sizeof("double")) 143 | if value == scannedValue.float64_value then 144 | print("found at " .. string.format("%x", startAddr+offset-1) .. " type: float64") 145 | end 146 | end 147 | end 148 | end 149 | end 150 | end 151 | 152 | memFile:close() 153 | return regions 154 | end 155 | 156 | ------------------------------------------------------------------------------ 157 | -- Exports 158 | ------------------------------------------------------------------------------ 159 | function export.search(pid, value, regionName) 160 | search(pid, value, regionName) 161 | end 162 | 163 | function export.isProcessRunning(pid) 164 | assertProcessExists(pid) 165 | end 166 | 167 | return export 168 | --------------------------------------------------------------------------------