吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5951|回复: 5
收起左侧

[Java 原创] 基于frida的android游戏内存扫描器_初稿

[复制链接]
salala159 发表于 2019-3-27 17:18

界面形式的工具我先不提供了,只是贴一些源码细节,源码没有太详细的测试,可能存在bug,对内存扫描工具了解的也不太多,还望大佬们多多指教,我将根据大佬们的意见改进内存扫描工具,在此不胜感激~~~~~
基本思路:首次扫描采用的是Memory.scanSyn(addr, size, pattern)函数去匹配扫描结果,然后将其结果存入map中,再次扫描就是基于该map的基础上不断的筛选需要的数据,源码只提供扫描,提炼数据,hook功能并没有贴上来。

以下是frIDA的js脚本:

一:首先是一些3个工具函数
[JavaScript] 纯文本查看 复制代码
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仅用来标识和确定内存中的数据类型,并无实际含义。
二:初始化扫描范围
[JavaScript] 纯文本查看 复制代码
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([ptr(parseInt(str.substr(0, 8), 16)), ptr(parseInt(str.substr(9, 8), 16))])
    }
	pclose(pfile);
	return result
}


该函数返回一个数组,数组中的每一项都是目标扫描范围。
三:首次扫描----精确值
[JavaScript] 纯文本查看 复制代码
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[searchResult_list[index].address] = input
	}
	m_count  = Object.keys(g_data).length
	if(m_count < 100)
	{
		for(var key in g_data)
		{
			console.log("\033[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\033[0m');
		}
	}
	return m_count;
}


首次扫描----精确值:根据内存属性确定扫描范围, e.g.比如我只扫描可读可写分区,则参数protection = ‘rw’
[JavaScript] 纯文本查看 复制代码
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[index].base, range_list[index].size, pattern)
		}catch(e){
			continue
		}
		for(index1 in searchResult_list)
		{
			g_data[searchResult_list[index1].address] = input
		}
	}
	m_count  = Object.keys(g_data).length
	if(m_count < 100)
	{
		for(var key in g_data)
		{
			console.log("\033[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\033[0m');
		}
	}
	return m_count;
}


首次扫描----未知扫描,该函数效率不太高,需要优化,我是以字节对齐的方式进行扫描的,可能存在某些数据漏掉的情况,总之,需要优化
[JavaScript] 纯文本查看 复制代码
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[_addr_start] = 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[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\033[0m');
		}
	}
	return m_count;
}


首次扫描---大于某个值,小于某个值,某两个值之间
[Asm] 纯文本查看 复制代码
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[_addr_start] = 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[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\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[_addr_start] = 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[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\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[_addr_start] = 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[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\033[0m');
		}
	}
	return m_count;
}


四:筛选数据,再次扫描,各种模式,相信聪明的你们一看就懂,
[JavaScript] 纯文本查看 复制代码
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[key]
		}else{
			g_data[key] = value
		}
	}

	m_count  = Object.keys(g_data).length
	if(m_count < 100)
	{
		for(var key in g_data)
		{
			console.log("\033[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\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[key])
		{
			delete g_data[key]
		}
	}

	m_count  = Object.keys(g_data).length
	if(m_count < 100)
	{
		for(var key in g_data)
		{
			console.log("\033[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\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[key])
		{
			delete g_data[key]
		}else{
			g_data[key] = new_value
		}
	}

	m_count  = Object.keys(g_data).length
	if(m_count < 100)
	{
		for(var key in g_data)
		{
			console.log("\033[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\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[key]
		}else{
			g_data[key] = new_value
		}
	}

	m_count  = Object.keys(g_data).length
	if(m_count < 100)
	{
		for(var key in g_data)
		{
			console.log("\033[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\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[key]
		}else{
			g_data[key] = new_value
		}
	}
	m_count  = Object.keys(g_data).length
	if(m_count < 100)
	{
		for(var key in g_data)
		{
			console.log("\033[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\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[key] = new_value
		}else{
			delete g_data[key]
		}
	}
	m_count  = Object.keys(g_data).length
	if(m_count < 100)
	{
		for(var key in g_data)
		{
			console.log("\033[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\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[key])
		{
			delete g_data[key]
		}else{
			g_data[key] = new_value
		}
	}
	m_count  = Object.keys(g_data).length
	if(m_count < 100)
	{
		for(var key in g_data)
		{
			console.log("\033[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\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[key])
		{
			delete g_data[key]
		}else{
			g_data[key] = new_value
		}
	}
	m_count  = Object.keys(g_data).length
	if(m_count < 100)
	{
		for(var key in g_data)
		{
			console.log("\033[1;32;40maddress: \033[0m" + '\033[1;33;40m' + key + '\033[0m' + "\033[1;32;40m  value: \033[0m"+ '\033[1;33;40m' + g_data[key] + '\033[0m');
		}
	}
	return m_count;
}





下个版本优化:


1. 对参数的取值做一个限制byte_length
2. 支持对找到的部分结果进行修改和不同格式的显示
3. 费时间的函数readvalue,解决办法 读一块大内存,本地处理利用arraybuffer databuffer
4. 修改某些操作为位移操作
5. 优化查找数据源
6. 内部使用字节去比较和遍历,输出结果的时候进行转化

以上所有函数都支持在frida cli中直接执行,执行一下你们就知道其中的意思了,我表达能力欠佳。
我的表达能力实在堪忧,你们有啥疑问直接问我就好啦~~我有时间就一一解答。

该版本只是一个雏形,会有许多bug的,也希望大家多多提出来自己的见解,我会一直更新下去的,谢谢~~~~

免费评分

参与人数 6吾爱币 +11 热心值 +6 收起 理由
平风造雨 + 1 + 1 我很赞同!
guch8017 + 1 + 1 提供了一些思路,THX
wushaominkk + 6 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
笙若 + 1 + 1 谢谢@Thanks!
爱拍阴天 + 1 + 1 加油哦
linfengtai2008 + 1 + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

爱拍阴天 发表于 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
楼主有没有 更新过得
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-5-4 01:35

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表