基于frida的android游戏内存扫描器_初稿
界面形式的工具我先不提供了,只是贴一些源码细节,源码没有太详细的测试,可能存在bug,对内存扫描工具了解的也不太多,还望大佬们多多指教,我将根据大佬们的意见改进内存扫描工具,在此不胜感激~~~~~
基本思路:首次扫描采用的是Memory.scanSyn(addr, size, pattern)函数去匹配扫描结果,然后将其结果存入map中,再次扫描就是基于该map的基础上不断的筛选需要的数据,源码只提供扫描,提炼数据,hook功能并没有贴上来。
以下是frida的js脚本:
一:首先是一些3个工具函数
function arraybuffer2hexstr(buffer)
{
var hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function (bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join(' ');
}
function generate_pattern(input, byte_length)
{
var pattern = null;
var addr = 0;
var array_buffer = null;
switch(byte_length)
{
case 1: //byte
if(input >= 0) //无符号
{
addr = Memory.alloc(1)
Memory.writeU8(addr, input)
array_buffer = Memory.readByteArray(addr, 1)
pattern = arraybuffer2hexstr(array_buffer)
}else{ //有符号
addr = Memory.alloc(1)
Memory.writeS8(addr, input)
array_buffer = Memory.readByteArray(addr, 1)
pattern = arraybuffer2hexstr(array_buffer)
}
break;
case 2: //short
if(input >= 0)
{
addr = Memory.alloc(2)
Memory.writeU16(addr, input)
array_buffer = Memory.readByteArray(addr, 2)
pattern = arraybuffer2hexstr(array_buffer)
}else{
addr = Memory.alloc(2)
Memory.writeS16(addr, input)
array_buffer = Memory.readByteArray(addr, 2)
pattern = arraybuffer2hexstr(array_buffer)
}
break;
case 4:
if(parseInt(input) == input) //int long
{
if(input >= 0)
{
addr = Memory.alloc(4)
Memory.writeU32(addr, input)
array_buffer = Memory.readByteArray(addr, 4)
pattern = arraybuffer2hexstr(array_buffer)
}else{
addr = Memory.alloc(4)
Memory.writeS32(addr, input)
array_buffer = Memory.readByteArray(addr, 4)
pattern = arraybuffer2hexstr(array_buffer)
}
}else{ //float
addr = Memory.alloc(4)
Memory.writeFloat(addr, input)
array_buffer = Memory.readByteArray(addr, 4)
pattern = arraybuffer2hexstr(array_buffer)
}
break
case 8:
if(parseInt(input) == input) //longlong
{
if(input >= 0)
{
addr = Memory.alloc(8)
Memory.writeU64(addr, input)
array_buffer = Memory.readByteArray(addr, 8)
pattern = arraybuffer2hexstr(array_buffer)
}else{
addr = Memory.alloc(8)
Memory.writeS64(addr, input)
array_buffer = Memory.readByteArray(addr, 8)
pattern = arraybuffer2hexstr(array_buffer)
}
}else{ //double
addr = Memory.alloc(8)
Memory.writeDouble(addr, input)
array_buffer = Memory.readByteArray(addr, 8)
pattern = arraybuffer2hexstr(array_buffer)
}
break;
case undefined: //string
var encoder = new TextEncoder('utf-8')
array_buffer = encoder.encode(input)
pattern = arraybuffer2hexstr(array_buffer)
break
default:
pattern = 'error'
}
return pattern
}
function readValue(addr, input, byte_length)
{
var result = 0;
_addr = new NativePointer(addr)
switch(byte_length)
{
case 1: //byte
if(input >= 0) //无符号
{
result = Memory.readU8(_addr)
}else{ //有符号
result = Memory.readS8(_addr)
}
break;
case 2: //short
if(input >= 0)
{
result = Memory.readU16(_addr)
}else{
result = Memory.readS16(_addr)
}
break;
case 4:
if(parseInt(input) == input) //int long
{
if(input >= 0)
{
result = Memory.readU32(_addr)
}else{
result = Memory.readS32(_addr)
}
}else{ //float
result = Memory.readFloat(_addr)
}
break
case 8:
if(parseInt(input) == input) //longlong
{
if(input >= 0)
{
result = Memory.readU64(_addr)
}else{
result = Memory.readS64(_addr)
}
}else{ //double
result = Memory.readDouble(_addr)
}
break;
case undefined: //string
result = Memory.readUtf8String(_addr)
break
default:
pattern = 'error'
}
return result;
}
generate_pattern函数负责生成pattern,根据输入的input,byte_length转换为pattern,支持将任何基础类型数据。e.g. input = 10,byte_length = 4就可以识别为四字节正整数模型,input = 15.2,byte_length = 4 识别为float模型,input = ‘a string’ 不需要输入byte_length识别为字符串模型。
readValue函数负责从一个地址中读取基础类型数据,参数input和byte_length仅用来标识和确定内存中的数据类型,并无实际含义。
二:初始化扫描范围
function init_scan_range()
{
var buffer_length = 1024
var result = []
addr = Module.findExportByName('libc.so', 'popen')
var popen = new NativeFunction(addr, 'pointer', ['pointer', 'pointer']);
addr = Module.findExportByName('libc.so', 'fgets')
var fgets = new NativeFunction(addr, "pointer", ["pointer", "int", "pointer"]);
addr = Module.findExportByName('libc.so', 'pclose')
var pclose = new NativeFunction(addr, "int", ["pointer"]);
var pid = Process.id
var command = 'cat /proc/' + pid + '/maps |grep LinearAlloc'
var pfile = popen(Memory.allocUtf8String(command), Memory.allocUtf8String('r'))
if(pfile == null)
{
console.log("\033[1;31;40mpopen open failed...\033[0m");
return;
}
var buffer = Memory.alloc(buffer_length);
while (fgets(buffer, buffer_length, pfile) > 0) {
var str = Memory.readUtf8String(buffer);
result.push()
}
pclose(pfile);
return result
}
该函数返回一个数组,数组中的每一项都是目标扫描范围。
三:首次扫描----精确值
var g_data = {};
var init_value = 0;
var init_byte_length = 0;
function new_scan_by_addr(addr_start, addr_end, input, byte_length)
{
var m_count = 0
g_data = {}
init_value = input
init_byte_length = byte_length
var _addr_start = new NativePointer(addr_start)
var _addr_end = new NativePointer(addr_end)
var pattern = generate_pattern(init_value, init_byte_length)
if(pattern == 'error')
{
console.log('ERROR:The byte_length can only be 1, 2, 4, 8 and undefined?')
}
var searchResult_list = Memory.scanSync(_addr_start, _addr_end - _addr_start, pattern)
for(index in searchResult_list)
{
g_data.address] = input
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
首次扫描----精确值:根据内存属性确定扫描范围, e.g.比如我只扫描可读可写分区,则参数protection = ‘rw’
function new_scan_by_protect(protection, input, byte_length)
{
var m_count = 0
var searchResult_list = []
g_data = {}
init_value = input
init_byte_length = byte_length
var pattern = generate_pattern(ininit_valueput, init_byte_length)
if(pattern == 'error')
{
console.log('ERROR:The byte_length can only be 1, 2, 4, 8 and undefined?')
}
var range_list = Process.enumerateRangesSync(protection)
for(index in range_list)
{
try{
searchResult_list = Memory.scanSync(range_list.base, range_list.size, pattern)
}catch(e){
continue
}
for(index1 in searchResult_list)
{
g_data.address] = input
}
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
首次扫描----未知扫描,该函数效率不太高,需要优化,我是以字节对齐的方式进行扫描的,可能存在某些数据漏掉的情况,总之,需要优化
function new_scan_by_addr_unknownValue(addr_start, addr_end, reference, byte_length)
{
var m_count = 0
g_data = {}
init_value = reference
init_byte_length = byte_length
var _addr_start = new NativePointer(addr_start)
var _addr_end = new NativePointer(addr_end)
while(_addr_start.toInt32() < _addr_end.toInt32())
{
g_data = readValue(_addr_start, init_value, init_byte_length)
_addr_start = _addr_start.add(byte_length)
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
首次扫描---大于某个值,小于某个值,某两个值之间
function new_scan_by_addr_larger(addr_start, addr_end, value, byte_length)
{
var m_count = 0
g_data = {}
init_value = value
init_byte_length = byte_length
var _addr_start = new NativePointer(addr_start)
var _addr_end = new NativePointer(addr_end)
while(_addr_start.toInt32() < _addr_end.toInt32())
{
var new_value= readValue(_addr_start, init_value, init_byte_length)
if(new_value > value)
{
g_data = new_value
}
_addr_start = _addr_start.add(byte_length)
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
function new_scan_by_addr_littler(addr_start, addr_end, value, byte_length)
{
var m_count = 0
g_data = {}
init_value = value
init_byte_length = byte_length
var _addr_start = new NativePointer(addr_start)
var _addr_end = new NativePointer(addr_end)
while(_addr_start.toInt32() < _addr_end.toInt32())
{
var new_value= readValue(_addr_start, init_value, init_byte_length)
if(new_value < value)
{
g_data = new_value
}
_addr_start = _addr_start.add(byte_length)
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
function new_scan_by_addr_between(addr_start, addr_end, value1, value2, byte_length)
{
var m_count = 0
g_data = {}
init_value = value1
init_byte_length = byte_length
var _addr_start = new NativePointer(addr_start)
var _addr_end = new NativePointer(addr_end)
while(_addr_start.toInt32() < _addr_end.toInt32())
{
var new_value= readValue(_addr_start, init_value, init_byte_length)
if(new_value >= value1 && new_value <= value2)
{
g_data = new_value
}
_addr_start = _addr_start.add(byte_length)
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
四:筛选数据,再次扫描,各种模式,相信聪明的你们一看就懂,
function next_scan_equal(value)
{
var m_count = 0;
for(key in g_data)
{
if(readValue(key, init_value, init_byte_length) != value)
{
delete g_data
}else{
g_data = value
}
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
function next_scan_unchange()
{
var m_count = 0;
for(key in g_data)
{
if(readValue(key, init_value, init_byte_length) != g_data)
{
delete g_data
}
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
function next_scan_change()
{
var m_count = 0;
for(key in g_data)
{
var new_value = readValue(key, init_value, init_byte_length)
if(new_value == g_data)
{
delete g_data
}else{
g_data = new_value
}
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
function next_scan_littler(value)
{
var m_count = 0;
for(key in g_data)
{
var new_value = readValue(key, init_value, init_byte_length)
if(new_value >= value)
{
delete g_data
}else{
g_data = new_value
}
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
function next_scan_larger(value)
{
var m_count = 0;
for(key in g_data)
{
var new_value = readValue(key, init_value, init_byte_length)
if(new_value <= value)
{
delete g_data
}else{
g_data = new_value
}
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
function next_scan_between(value1, value2)
{
var m_count = 0;
for(key in g_data)
{
var new_value = readValue(key, init_value, init_byte_length)
if(new_value >= value1 && new_value <= value2)
{
g_data = new_value
}else{
delete g_data
}
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
function next_scan_increase()
{
var m_count = 0;
for(key in g_data)
{
var new_value = readValue(key, init_value, init_byte_length)
if(new_value <= g_data)
{
delete g_data
}else{
g_data = new_value
}
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
function next_scan_decrease()
{
var m_count = 0;
for(key in g_data)
{
var new_value = readValue(key, init_value, init_byte_length)
if(new_value >= g_data)
{
delete g_data
}else{
g_data = new_value
}
}
m_count= Object.keys(g_data).length
if(m_count < 100)
{
for(var key in g_data)
{
console.log("\033 + '\033[0m');
}
}
return m_count;
}
下个版本优化:
1. 对参数的取值做一个限制byte_length
2. 支持对找到的部分结果进行修改和不同格式的显示
3. 费时间的函数readvalue,解决办法 读一块大内存,本地处理利用arraybuffer databuffer
4. 修改某些操作为位移操作
5. 优化查找数据源
6. 内部使用字节去比较和遍历,输出结果的时候进行转化
以上所有函数都支持在frida cli中直接执行,执行一下你们就知道其中的意思了,我表达能力欠佳。
我的表达能力实在堪忧,你们有啥疑问直接问我就好啦~~我有时间就一一解答。
该版本只是一个雏形,会有许多bug的,也希望大家多多提出来自己的见解,我会一直更新下去的,谢谢~~~~
建议一下,做一个图形界面,你现在源码有了,但是这还不算是软件,做出图形界面就真成了软件了。没做图形的话难火,毕竟我们现在大部分人都用的GG。当然还是支持一下,期待你做成软件的那一天,币币和热心给了,希望这能成为动力。 期待楼主的作品能免费分享啊 扫描软件相对好做,对游戏隐藏才是重点。。。。毕竟现在已经到了矛与盾的时代。。。哈哈哈,加油加油!
楼主做出界面了吗 楼主有没有 更新过得
页:
[1]