salala159 发表于 2019-3-27 17:18

基于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的,也希望大家多多提出来自己的见解,我会一直更新下去的,谢谢~~~~

爱拍阴天 发表于 2019-3-27 20:45

建议一下,做一个图形界面,你现在源码有了,但是这还不算是软件,做出图形界面就真成了软件了。没做图形的话难火,毕竟我们现在大部分人都用的GG。当然还是支持一下,期待你做成软件的那一天,币币和热心给了,希望这能成为动力。

bachelor66 发表于 2019-3-28 08:09

期待楼主的作品能免费分享啊                                 

wlsk888 发表于 2019-11-26 10:12

扫描软件相对好做,对游戏隐藏才是重点。。。。毕竟现在已经到了矛与盾的时代。。。哈哈哈,加油加油!

wesleyxu 发表于 2020-1-2 18:24

楼主做出界面了吗

云城飞将 发表于 2020-11-23 15:16

楼主有没有 更新过得
页: [1]
查看完整版本: 基于frida的android游戏内存扫描器_初稿