首页
社区
课程
招聘
[原创]基于frida的android游戏内存扫描器_初稿
2019-3-27 17:09 10760

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

2019-3-27 17:09
10760

界面形式的工具我先不提供了,只是贴一些源码细节,源码没有太详细的测试,可能存在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([ptr(parseInt(str.substr(0, 8), 16)), ptr(parseInt(str.substr(9, 8), 16))])
    }
	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[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’

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;
}
首次扫描----未知扫描,该函数效率不太高,需要优化,我是以字节对齐的方式进行扫描的,可能存在某些数据漏掉的情况,总之,需要优化
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;
}
首次扫描---大于某个值,小于某个值,某两个值之间
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;
}
四:筛选数据,再次扫描,各种模式,相信聪明的你们一看就懂,
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的,也希望大家多多提出来自己的见解,我会一直更新下去的,谢谢~~~~





[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞8
打赏
分享
打赏 + 2.00雪花
打赏次数 1 雪花 + 2.00
 
赞赏  junkboy   +2.00 2019/03/27 感谢分享~
最新回复 (7)
雪    币: 11716
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
junkboy 2019-3-27 18:11
2
0
支持
雪    币: 916
活跃值: (3409)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
葫芦娃 1 2019-3-27 19:44
3
0
good job!
雪    币: 1376
活跃值: (9742)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
misskings 4 2019-3-31 21:58
4
0
大佬,萌新提个意见啊,可以举个运用场景的例子,然后来个实操小案例。这样看就明白多了。
雪    币: 1759
活跃值: (2309)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
又见飞刀z 2019-4-7 21:37
5
0
mark
雪    币: 25
能力值: (RANK:0 )
在线值:
发帖
回帖
粉丝
mb_prvtdkkf 2019-11-11 13:35
6
0
求楼主联系方式
雪    币: 52
活跃值: (501)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
万里星河 2023-3-3 17:56
7
0
支持
雪    币: 1
活跃值: (150)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
暖心系 2023-4-1 22:06
8
0
大佬,请问接开发吗?可否留个联系方式
游客
登录 | 注册 方可回帖
返回