打赏网站开发,纯flash网站欣赏,北京大型网站优化,做响应式网站的价格目标#xff1a;
C写一个Demo版本的游戏由浅入深#xff0c;了解外挂原理Linux/Android下实现内存读取ptrace实现内存修改#xff08;依赖第三方库#xff09;
先准备一个C写的小游戏
#include stdio.h
#include string.hstruct Role
{float pos_x; // …目标
C写一个Demo版本的游戏由浅入深了解外挂原理Linux/Android下实现内存读取ptrace实现内存修改依赖第三方库
先准备一个C写的小游戏
#include stdio.h
#include string.hstruct Role
{float pos_x; // 位置xfloat pos_y;float pos_z;int level; // 等级long money; // 金钱
};struct Account
{char name[20]; // 名字long ID; // IDstruct Role *role; // 多个角色信息
};void decMoney(struct Role* r){r-money - rand()%100;
}void main(){printf(GameBoy tester!\r\n);struct Account ac;strcpy(ac.name, 我是张三啊);ac.ID 20240318;// rolesstruct Role *rl (struct Role*)malloc(sizeof(struct Role)*4);ac.role rl;for(int i0;i4;i){rl[i].level i*210;rl[i].money 5000;rl[i].pos_x i*1000;rl[i].pos_y i*1100;rl[i].pos_z i*1200;}while (1){printf(\r\nInput [l,m,p,c] to change value:);char input getchar();switch (input){case l:for(int i0;i4;i){rl[i].level rand()%50/10;}break;case m:for(int i0;i4;i){rl[i].money rand()%20;}break;case p:for(int i0;i4;i){rl[i].pos_x rand()%100 * 0.1;rl[i].pos_y rand()%20 * 0.2;rl[i].pos_z rand()%10;}break;case c:for(int i0;i4;i){decMoney(rl[i]);}break;default:break;}// printprintf(\r\nAccount: [%ld] %s\r\n, ac.ID, ac.name);for(int i0;i4;i){printf(Role[%d] Level:%d, Money: %ld, Pos:[%.2f,%.2f,%.2f]\r\n, i, rl[i].level, rl[i].money, rl[i].pos_x,rl[i].pos_y,rl[i].pos_z);}printf(Cheat: Account [0x%lX], ID [0x%lX], Name [0x%lX], role [%lX]\r\n, ac, (ac.ID), ac.name, (ac.role));printf(Cheat: Role [0x%lX], pos_x [0x%lX], level [0x%lX], money [%lX]\r\n, rl, (rl[0].pos_x), (rl[0].level), (rl[0].money));printf(Cheat: decMoney [0x%lX], main: {0x%lX}\r\n,decMoney, main); }
}// gcc -o gamebox main.c
// ./gamebox
Input [l,m,p,c] to change value:只要输入 l/m/p/c 就可以随机改变其中的等级金钱位置信息。其中输入c会调用函数随机减少角色的金钱。
Rust修改gamebox
有个出名的技术叫hook还有一个技术叫修改内存。这也就是简单的游戏外挂范围的技术。这里我们使用rust去模拟一下修改上面用C写的gamebox。
在Linux下如何搜索内存找到我们需要的数据不在这里讨论我们根据gamebox提供的地址直接定位。一定要自己找可以使用 PINCE。(类似CE)
备注ceserver wine CE GUI 可以在Linux下进行搜索。
首先反推几个重要信息的地址关系: (假设字节对齐)
[BaseAddr0] name 首地址, char[20]
[BaseAddr20] ID, long
[BaseAddr208] Role的首地址一个 Role size3*44824Role [BaseAddr208]
[[BaseAddr208]24*i 0] Role.pos_x, float
[[BaseAddr208]24*i 4] Role.pos_y, float
[[BaseAddr208]24*i 8] Role.pos_z, float
[[BaseAddr208]24*i 12] Role.level, int
[[BaseAddr208]24*i 16] Role.money, long
虽然这个地址信息每次启动时会变化但是他们的关系应该是固定的。
实际上字节没有对齐和我们上面预期有差异
BaseAddr 0x7FFEE5125640
[BaseAddr0] name 首地址, char[20] 0x7FFEE5125640 实际占用是0x1824
[BaseAddr24] ID, long 0x7FFEE5125658
[BaseAddr248] Role的首地址一个 Role size3*44824 0x7FFEE5125660 Role [BaseAddr248] [0x7FFEE5125660] 0x5585F6BFE6B0
[[BaseAddr248]24*i 0] Role.pos_x, float 0x5585F6BFE6B0
[[BaseAddr248]24*i 4] Role.pos_y, float
[[BaseAddr248]24*i 8] Role.pos_z, float
[[BaseAddr248]24*i 12] Role.level, int 0x5585F6BFE6BC
[[BaseAddr248]24*i 16] Role.money, long 0x5585F6BFE6C0
准备
https://blog.csdn.net/guojin08/article/details/9454467
https://blog.csdn.net/hhhlizhao/article/details/77930009
https://zhuanlan.zhihu.com/p/348171413
https://zhuanlan.zhihu.com/p/674139021
https://www.52pojie.cn/thread-1355860-1-1.html
Linux上一切皆文件只要有权限读写其他程序的内存很简单。
sudo权限强制读写/proc/ID/mem自身为内核空间直接读写mem能获取到proc的mem的物理地址读写物理内存dbg相关函数
如果想要注入修改代码需要使用ptrace一类的方式。
https://www.52pojie.cn/thread-1568457-1-1.html
Linux和Android的方法都是类似的.
简易内存修改
1.直接读取内存固定位置
读文件方式实现
andyandy-pc:/proc/22093$ cat maps
5593869d4000-5593869d5000 r--p 00000000 08:12 6160449 /home/andy/Work/rs_prj/d8/C/gamebox
5593869d5000-5593869d6000 r-xp 00001000 08:12 6160449 /home/andy/Work/rs_prj/d8/C/gamebox
5593869d6000-5593869d7000 r--p 00002000 08:12 6160449 /home/andy/Work/rs_prj/d8/C/gamebox
5593869d7000-5593869d8000 r--p 00002000 08:12 6160449 /home/andy/Work/rs_prj/d8/C/gamebox
5593869d8000-5593869d9000 rw-p 00003000 08:12 6160449 /home/andy/Work/rs_prj/d8/C/gamebox
559386d8f000-559386db0000 rw-p 00000000 00:00 0 [heap]
7f914db19000-7f914db1c000 rw-p 00000000 00:00 0
7f914db1c000-7f914db44000 r--p 00000000 08:12 12848718 /usr/lib/x86_64-linux-gnu/libc.so.6
7f914db44000-7f914dcd9000 r-xp 00028000 08:12 12848718 /usr/lib/x86_64-linux-gnu/libc.so.6
7f914dcd9000-7f914dd31000 r--p 001bd000 08:12 12848718 /usr/lib/x86_64-linux-gnu/libc.so.6
7f914dd31000-7f914dd32000 ---p 00215000 08:12 12848718 /usr/lib/x86_64-linux-gnu/libc.so.6
7f914dd32000-7f914dd36000 r--p 00215000 08:12 12848718 /usr/lib/x86_64-linux-gnu/libc.so.6
7f914dd36000-7f914dd38000 rw-p 00219000 08:12 12848718 /usr/lib/x86_64-linux-gnu/libc.so.6
7f914dd38000-7f914dd45000 rw-p 00000000 00:00 0
7f914dd5e000-7f914dd60000 rw-p 00000000 00:00 0
7f914dd60000-7f914dd62000 r--p 00000000 08:12 12848709 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f914dd62000-7f914dd8c000 r-xp 00002000 08:12 12848709 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f914dd8c000-7f914dd97000 r--p 0002c000 08:12 12848709 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f914dd98000-7f914dd9a000 r--p 00037000 08:12 12848709 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f914dd9a000-7f914dd9c000 rw-p 00039000 08:12 12848709 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ffed5856000-7ffed5877000 rw-p 00000000 00:00 0 [stack]
7ffed596e000-7ffed5972000 r--p 00000000 00:00 0 [vvar]
7ffed5972000-7ffed5974000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]# gamebox
Account: [20240318] 我是张三啊
Role[0] Level:14, Money: 4926, Pos:[9.00,3.80,3.00]
Role[1] Level:15, Money: 4915, Pos:[1002.60,1100.00,1206.00]
Role[2] Level:17, Money: 4925, Pos:[2007.20,2203.20,2401.00]
Role[3] Level:20, Money: 4992, Pos:[3006.80,3301.40,3609.00]
Cheat: Account [0x7FFF90D18540], ID [0x7FFF90D18558], Name [0x7FFF90D18540], role [7FFF90D18560]
Cheat: Role [0x558CE6B4F6B0], pos_x [0x558CE6B4F6B0], level [0x558CE6B4F6BC], money [558CE6B4F6C0]
Cheat: decMoney [0x558CE63E11C9]根据地址信息数据主要存在 heap和stack上函数存在代码段。
use std::{ffi::{CStr, OsString},fs::File,io::Read,os::unix::fs::FileExt,path, u8,
};// 64 bit program
fn main() {let pid find_id_by_name(gamebox);println!(gamebox pid: {:?}, pid);if pid.len() 0 {let memf format!(/proc/{}/mem, pid.first().unwrap());println!(gamebox mem: {}, memf);// 需要root权限if let Ok(f) File::open(memf) {let base_addr:u64 0x7FFF90D18540;// accountlet id read_at_mem(f, base_addr 24, 8);let name read_at_mem(f, base_addr 0, 20);let role_addr read_at_mem(f, base_addr 24 8, 8);let name2 unsafe { CStr::from_ptr(name.as_ptr() as *const i8) };let role_addr2 u64::from_le_bytes(role_addr.try_into().unwrap());println!(Account ID: {}, Name: {}, role addr: 0x{:#X},u64::from_le_bytes(id.try_into().unwrap()),name2.to_str().unwrap(),role_addr2,);// rolefor i in 0..4 {let level read_at_mem(f, role_addr2 24*i 12, 4);let money read_at_mem(f, role_addr2 24*i 16, 8);let pos_x read_at_mem(f, role_addr2 24*i 0, 4);let pos_y read_at_mem(f, role_addr2 24*i 4, 4);let pos_z read_at_mem(f, role_addr2 24*i 8, 4);println!( Role[{}] Level: {}, Money: {}, Pos: [{:.2}, {:.2}, {:.2}],i,u32::from_le_bytes(level.try_into().unwrap()),u64::from_le_bytes(money.try_into().unwrap()),f32::from_le_bytes(pos_x.try_into().unwrap()),f32::from_le_bytes(pos_y.try_into().unwrap()),f32::from_le_bytes(pos_z.try_into().unwrap()),);}} else {println!(need root permission!);}}
}// 读取指定大小的内存数据
fn read_at_mem(f: File, addr: u64, sz: usize) - Vecu8 {let mut buf Vec::new();buf.resize(sz, 0);f.read_at(mut buf, addr).unwrap();return buf;
}// 忽略大小写进行进程查找
fn find_id_by_name(name: str) - VecString {// 遍历和读取 /proc/xxx/commlet root path::Path::new(/proc/);let mut pids: Vec_ Vec::new();for sub in root.read_dir().unwrap() {if let Ok(id_dir) sub {let fp id_dir.path();if fp.is_dir() {let comm fp.join(comm);if comm.exists() {let txt file_read_content(comm.to_str().unwrap());// println!(try file: {:?}, {}, comm, txt);if txt.to_ascii_lowercase() name.to_ascii_lowercase() {let pid OsString::from(fp.file_name().unwrap()).into_string().unwrap();pids.push(pid);}}}}}pids
}fn file_read_content(filepath: str) - String {let mut txt String::new();if let Ok(mut f) File::open(filepath) {f.read_to_string(mut txt).unwrap();}// 默认内容有换行txt txt.trim().to_string();return txt;
}
2.寻找基地址真正外挂
基地址的本质是全局变量所以我们原本的代码无法实现。
修改一下, 重新编译
struct Account ac;void main(){printf(GameBoy tester!\r\n); // struct Account ac; // 移动这个到全局变量strcpy(ac.name, 我是张三啊);ac.ID 20240318;...
}另外程序每次加载到内存中并不一定是固定地址的。
在Windows系统中xpwin7时代是这样每次固定加载在固定的地址上所以每次程序运行的地址都是固定的。到了后来windows系统和Linux都使用了动态基地址的flag, 每次的初始地址不固定所以寻找基地址变得麻烦。
思路
程序加载到内存时有导出表或者maps信息可以看到code的初始地址。main函数相对程序初始地址一般是固定的与第一步地址一起计算可以得到main的动态地址。程序全局变量等的地址相对main是固定的所以就可以动态计算出来当前变量的地址。即动态的基地址
Linux上的获取基地址方法
启动gamebox得到全局变量偏移信息
Account: [20240318] 我是张三啊
Role[0] Level:10, Money: 5000, Pos:[0.00,0.00,0.00]
Role[1] Level:12, Money: 5000, Pos:[1000.00,1100.00,1200.00]
Role[2] Level:14, Money: 5000, Pos:[2000.00,2200.00,2400.00]
Role[3] Level:16, Money: 5000, Pos:[3000.00,3300.00,3600.00]
Cheat: Account [0x55AF86AE7040], ID [0x55AF86AE7058], Name [0x55AF86AE7040], role [55AF86AE7060]
Cheat: Role [0x55AF87D676B0], pos_x [0x55AF87D676B0], level [0x55AF87D676BC], money [55AF87D676C0]
Cheat: decMoney [0x55AF86AE41C9], main: {0x55AF86AE421E}所以Account Offset Account[0x55AF86AE7040] - main[0x55AF86AE421E] 0x2E22
通过gamebox进程maps信息获取main偏移
55af86ae3000-55af86ae4000 r--p 00000000 08:12 6160449 /home/andy/Work/rs_prj/d8/C/gamebox
55af86ae4000-55af86ae5000 r-xp 00001000 08:12 6160449 /home/andy/Work/rs_prj/d8/C/gamebox
55af86ae5000-55af86ae6000 r--p 00002000 08:12 6160449 /home/andy/Work/rs_prj/d8/C/gamebox
55af86ae6000-55af86ae7000 r--p 00002000 08:12 6160449 /home/andy/Work/rs_prj/d8/C/gamebox
55af86ae7000-55af86ae8000 rw-p 00003000 08:12 6160449 /home/andy/Work/rs_prj/d8/C/gamebox
55af87d67000-55af87d88000 rw-p 00000000 00:00 0 [heap]所以main Offset main[0x55AF86AE421E] - gamebox[0x55af86ae3000] 0x121E
计算基地址偏移
BaseAddr gamebox 基地址 0x121E 0x2E22
Code
use std::{ffi::{CStr, OsString},fs::File,io::{BufRead, BufReader, Cursor, Read},os::unix::fs::FileExt,path, u8,
};// 64 bit program
fn main() {let pids find_id_by_name(gamebox);println!(gamebox pid: {:?}, pids);if pids.len() 0 {let pid pids.first().unwrap();let memf format!(/proc/{}/mem, pid);let mut base_addr:u64 process_get_base_addr(pid);if base_addr 0 {println!(gamebox find base addr failed: {}, pid);return;}base_addr 0x121E 0x2E22; // Account ac 全局变量偏移println!(gamebox mem: {}, base addr: {:X}, memf, base_addr);// 需要root权限if let Ok(f) File::open(memf) { // accountlet id read_at_mem(f, base_addr 24, 8);let name read_at_mem(f, base_addr 0, 20);let role_addr read_at_mem(f, base_addr 24 8, 8);let name2 unsafe { CStr::from_ptr(name.as_ptr() as *const i8) };let role_addr2 u64::from_le_bytes(role_addr.try_into().unwrap());println!(Account ID: {}, Name: {}, role addr: 0x{:#X},u64::from_le_bytes(id.try_into().unwrap()),name2.to_str().unwrap(),role_addr2,);// rolefor i in 0..4 {let level read_at_mem(f, role_addr2 24*i 12, 4);let money read_at_mem(f, role_addr2 24*i 16, 8);let pos_x read_at_mem(f, role_addr2 24*i 0, 4);let pos_y read_at_mem(f, role_addr2 24*i 4, 4);let pos_z read_at_mem(f, role_addr2 24*i 8, 4);println!(\tRole[{}] Level: {}, Money: {}, Pos: [{:.2}, {:.2}, {:.2}],i,u32::from_le_bytes(level.try_into().unwrap()),u64::from_le_bytes(money.try_into().unwrap()),f32::from_le_bytes(pos_x.try_into().unwrap()),f32::from_le_bytes(pos_y.try_into().unwrap()),f32::from_le_bytes(pos_z.try_into().unwrap()),);}} else {println!(need root permission!);}}
}fn read_at_mem(f: File, addr: u64, sz: usize) - Vecu8 {let mut buf Vec::new();buf.resize(sz, 0);f.read_at(mut buf, addr).unwrap();return buf;
}// 忽略大小写进行进程查找
fn find_id_by_name(name: str) - VecString {// 遍历和读取 /proc/xxx/commlet root path::Path::new(/proc/);let mut pids: Vec_ Vec::new();for sub in root.read_dir().unwrap() {if let Ok(id_dir) sub {let fp id_dir.path();if fp.is_dir() {let comm fp.join(comm);if comm.exists() {let txt file_read_content(comm.to_str().unwrap());// println!(try file: {:?}, {}, comm, txt);if txt.to_ascii_lowercase() name.to_ascii_lowercase() {let pid OsString::from(fp.file_name().unwrap()).into_string().unwrap();pids.push(pid);}}}}}pids
}fn file_read_content(filepath: str) - String {let mut txt String::new();if let Ok(mut f) File::open(filepath) {f.read_to_string(mut txt).unwrap();}// 默认内容有换行txt txt.trim().to_string();return txt;
}// 通过 /proc/pid/maps 查找 program 地址
fn process_get_base_addr(pid:str) - u64{let mut addr 0u64;if let Ok(f) File::open(format!(/proc/{}/maps, pid)){let mut txt String::new();let mut br BufReader::new(f);br.read_line(mut txt).unwrap();//55af86ae3000-55af86ae4000 r--p 00000000 08:12 6160449 /xx/gamebox if let Some(pos) txt.find(-) {addr u64::from_str_radix(txt[..pos], 16).unwrap();}}return addr;
}cargo build
sudo ./target/debug/d8gamebox pid: [7586]
gamebox mem: /proc/7586/mem, base addr: 55AF86AE7040
Account ID: 20240318, Name: 我是张三啊, role addr: 0x0x55AF87D676B0Role[0] Level: 10, Money: 5000, Pos: [0.00, 0.00, 0.00]Role[1] Level: 12, Money: 5000, Pos: [1000.00, 1100.00, 1200.00]Role[2] Level: 14, Money: 5000, Pos: [2000.00, 2200.00, 2400.00]Role[3] Level: 16, Money: 5000, Pos: [3000.00, 3300.00, 3600.00]然后尝试关闭gamebox然后再重启继续使用程序读取一下依然有效。
到此一个只读取gamebox的外挂做好了。
3.修改程序调用逻辑
如果我们想把代码中decMoney函数的减少钱的逻辑改成增加钱的逻辑怎么办
root权限强制改写 decMoney 函数的汇编代码可以实现简单功能root权限在内存中新写一个 函数 替代decMoney实现任意功能。这个难度很高。
我们做一个简单的版本
// decMoney 函数汇编代码
.text:00000000000011C9 public decMoney
.text:00000000000011C9 decMoney proc near ; CODE XREF: main4E3↓p
.text:00000000000011C9 ; DATA XREF: main68E↓o
.text:00000000000011C9
.text:00000000000011C9 var_8 qword ptr -8
.text:00000000000011C9
.text:00000000000011C9 ; __unwind {
.text:00000000000011C9 F3 0F 1E FA endbr64
.text:00000000000011CD 55 push rbp
.text:00000000000011CE 48 89 E5 mov rbp, rsp
.text:00000000000011D1 48 83 EC 10 sub rsp, 10h
.text:00000000000011D5 48 89 7D F8 mov [rbpvar_8], rdi
.text:00000000000011D9 B8 00 00 00 00 mov eax, 0
.text:00000000000011DE E8 ED FE FF FF call _rand
.text:00000000000011DE
.text:00000000000011E3 48 63 D0 movsxd rdx, eax
.text:00000000000011E6 48 69 D2 1F 85 EB 51 imul rdx, 51EB851Fh
.text:00000000000011ED 48 C1 EA 20 shr rdx, 20h
.text:00000000000011F1 C1 FA 05 sar edx, 5
.text:00000000000011F4 89 C1 mov ecx, eax
.text:00000000000011F6 C1 F9 1F sar ecx, 1Fh
.text:00000000000011F9 29 CA sub edx, ecx
.text:00000000000011FB 6B CA 64 imul ecx, edx, 64h ; d
.text:00000000000011FE 29 C8 sub eax, ecx
.text:0000000000001200 89 C2 mov edx, eax
.text:0000000000001202 48 8B 45 F8 mov rax, [rbpvar_8]
.text:0000000000001206 48 8B 40 10 mov rax, [rax10h]
.text:000000000000120A 48 63 CA movsxd rcx, edx
.text:000000000000120D 48 29 C8 sub rax, rcx ; 核心是这里减法处理
.text:0000000000001210 48 89 C2 mov rdx, rax ; v1
.text:0000000000001213 48 8B 45 F8 mov rax, [rbpvar_8]
.text:0000000000001217 48 89 50 10 mov [rax10h], rdx ; 最终v1赋值
.text:000000000000121B 90 nop
.text:000000000000121C C9 leave
.text:000000000000121D C3 retn
.text:000000000000121D ; } // starts at 11C9
.text:000000000000121D
.text:000000000000121D decMoney endp// 反汇编的C代码
__int64 __fastcall decMoney(__int64 a1)
{__int64 v1; // rdx__int64 result; // raxv1 *(_QWORD *)(a1 16) - rand() % 100; // 需要修改这里的减法操作result a1;*(_QWORD *)(a1 16) v1;return result;
}函数 DecMoney 的偏移为 00000000000011C9sub rax, rcx的偏移为 000000000000120D。offset0x44.
; https://shell-storm.org/online/Online-Assembler-and-Disassembler/
sub rax, rcx ; 48 29 c8
add rax, rcx ; 48 01 c8所以修改 函数地址加偏移 0x44 1 的值, 从 29 改为 01 就可以啦。
fn_ofset 0x55AF86AE41C9 − 0x55AF86AE421E -0x55
说明函数地址在main函数地址之前。
fn_addr program 0x121E - 0x55
理论代码如下
use std::{ffi::{CStr, OsString},fs::File,io::{self, BufRead, BufReader, Cursor, Read},os::unix::fs::FileExt,path, u8,
};// 64 bit program
fn main() {let pids find_id_by_name(gamebox);println!(gamebox pid: {:?}, pids);if pids.len() 0 {let pid pids.first().unwrap();let memf format!(/proc/{}/mem, pid);let mut fn_addr 0u64;let mut base_addr:u64 process_get_base_addr(pid);if base_addr 0 {println!(gamebox find base addr failed: {}, pid);return;}fn_addr base_addr 0x121E - 0x55; // decMoney 地址base_addr 0x121E 0x2E22; // Account ac 全局变量偏移println!(gamebox mem: {}, base addr: {:X}, memf, base_addr);// 需要root权限if let Ok(f) File::options().write(true).open(memf) { // accountlet id read_at_mem(f, base_addr 24, 8);let name read_at_mem(f, base_addr 0, 20);let role_addr read_at_mem(f, base_addr 24 8, 8);let name2 unsafe { CStr::from_ptr(name.as_ptr() as *const i8) };let role_addr2 u64::from_le_bytes(role_addr.try_into().unwrap());println!(Account ID: {}, Name: {}, role addr: 0x{:#X},u64::from_le_bytes(id.try_into().unwrap()),name2.to_str().unwrap(),role_addr2,);// rolefor i in 0..4 {let level read_at_mem(f, role_addr2 24*i 12, 4);let money read_at_mem(f, role_addr2 24*i 16, 8);let pos_x read_at_mem(f, role_addr2 24*i 0, 4);let pos_y read_at_mem(f, role_addr2 24*i 4, 4);let pos_z read_at_mem(f, role_addr2 24*i 8, 4);println!(\tRole[{}] Level: {}, Money: {}, Pos: [{:.2}, {:.2}, {:.2}],i,u32::from_le_bytes(level.try_into().unwrap()),u64::from_le_bytes(money.try_into().unwrap()),f32::from_le_bytes(pos_x.try_into().unwrap()),f32::from_le_bytes(pos_y.try_into().unwrap()),f32::from_le_bytes(pos_z.try_into().unwrap()),);}let mut buf String::new();print!(Input any key to modify decMoney function ....);io::stdin().read_line(mut buf).unwrap();// modify function decMoneylet asm_addr fn_addr 0x45; // sub rax, rcx add rax, rcxlet asm [0x01u8,];f.write_at(asm, asm_addr).unwrap();} else {println!(need root permission!);}}
}fn read_at_mem(f: File, addr: u64, sz: usize) - Vecu8 {let mut buf Vec::new();buf.resize(sz, 0);f.read_at(mut buf, addr).unwrap();return buf;
}// 忽略大小写进行进程查找
fn find_id_by_name(name: str) - VecString {// 遍历和读取 /proc/xxx/commlet root path::Path::new(/proc/);let mut pids: Vec_ Vec::new();for sub in root.read_dir().unwrap() {if let Ok(id_dir) sub {let fp id_dir.path();if fp.is_dir() {let comm fp.join(comm);if comm.exists() {let txt file_read_content(comm.to_str().unwrap());// println!(try file: {:?}, {}, comm, txt);if txt.to_ascii_lowercase() name.to_ascii_lowercase() {let pid OsString::from(fp.file_name().unwrap()).into_string().unwrap();pids.push(pid);}}}}}pids
}fn file_read_content(filepath: str) - String {let mut txt String::new();if let Ok(mut f) File::open(filepath) {f.read_to_string(mut txt).unwrap();}// 默认内容有换行txt txt.trim().to_string();return txt;
}// 通过 /proc/pid/maps 查找 program 地址
fn process_get_base_addr(pid:str) - u64{let mut addr 0u64;if let Ok(f) File::open(format!(/proc/{}/maps, pid)){let mut txt String::new();let mut br BufReader::new(f);br.read_line(mut txt).unwrap();//55af86ae3000-55af86ae4000 r--p 00000000 08:12 6160449 /xx/gamebox if let Some(pos) txt.find(-) {addr u64::from_str_radix(txt[..pos], 16).unwrap();}}return addr;
}看起来似乎可行实际执行发现即便是root权限也无法直接修改其他进程的内存信息。
其他思路有
ptrace 接口dbg方式修改内核空间修改能找到对应的物理内存地址修改物理内存
上面几种方法只有第一种难度最低后续研究这种。
4.ptrace 版本
ptrace 提供了一种机制使得父进程可以观察和控制子进程的执行过程ptrace 还可以检查和修改子进程的可执行文件在内存中的image及子进程所使用的寄存器中的值。通常来说主要用于实现对进程插入断点和跟踪子进程的系统调用。
我们在/proc/xxx/mem 可以root读取但是写入失败。但是ptrace可以实现这个写入功能。
long ptrace(enum __ptrace_request request,pid_t pid,void *addr,void *data);
/*
PTRACE_TRACEME, 本进程被其父进程所跟踪。其父进程应该希望跟踪子进程
PTRACE_PEEKTEXT, 从内存地址中读取一个LONG长度数据内存地址由addr给出
PTRACE_PEEKDATA, 同上
PTRACE_PEEKUSER, 可以检查用户态内存区域(USER area),从USER区域中读取一个字节偏移量为addr
PTRACE_POKETEXT, 往内存地址中写入一个LONG长度数据。内存地址由addr给出
PTRACE_POKEDATA, 往内存地址中写入一个LONG长度数据。内存地址由addr给出
PTRACE_POKEUSER, 往USER区域中写入一个LONG长度数据偏移量为addr
PTRACE_GETREGS, 读取寄存器
PTRACE_GETFPREGS, 读取浮点寄存器
PTRACE_SETREGS, 设置寄存器
PTRACE_SETFPREGS, 设置浮点寄存器
PTRACE_CONT, 重新运行
PTRACE_SYSCALL, 重新运行
PTRACE_SINGLESTEP, 设置单步执行标志
PTRACE_ATTACH追踪指定pid的进程
PTRACE_DETACH 结束追踪
*/// ptrace Demo
#include stdio.h
#include stdlib.h
#include sys/ptrace.h
int main(int argc, char* argv[])
{pid_t attack_pid -1;long val 66;if (argc 2 || argv[1] 0){printf(usage: ./main pid(pid 0)\n);return 0;}attack_pid strtoul(argv[1], 0, 10);if (ptrace(PTRACE_ATTACH, attack_pid, NULL, NULL) 0){printf(attach failed\n);return 0;}//读取数据printf(global1 %d\n, ptrace(PTRACE_PEEKDATA , attack_pid, (void*)0x804a028, NULL));printf(stack_var %d\n, ptrace(PTRACE_PEEKDATA , attack_pid, (void*)0xbfa4195c, NULL));//修改数据ptrace(PTRACE_POKEDATA , attack_pid, (void*)0x804a028, val);ptrace(PTRACE_POKEDATA , attack_pid, (void*)0xbfa4195c, val);ptrace (PTRACE_DETACH, attack_pid, NULL, NULL);waitpid(attack_pid, NULL, WUNTRACED);return 0;
}//main.c被改写的进程
#include stdio.hint global1 11; // int main(void)
{long stack_var 10;char c a;while(1){printf(global1 addrss 0x%lx, global1%d\n, global1, global1);printf(stack_var addrss 0x%lx, stack_var%d\n, stack_var, stack_var);scanf(%c, c);getchar();if (c ! c){break;}}return 0;
}https://dev59.com/unix/s3VD5IYBdhLWcg3wWaRh
完整的Rust版本的ptrace代码如下
use std::{ffi::{c_void, CStr, OsString},fs::File,io::{self, BufRead, BufReader, Read, Write},os::unix::fs::FileExt,path, u8,
};use nix::{sys::{ptrace, wait::waitpid}, unistd::Pid};// 64 bit program
fn main() {let pids find_id_by_name(gamebox);println!(gamebox pid: {:?}, pids);if pids.len() 0 {let pid pids.first().unwrap();let mut fn_addr 0u64;let mut base_addr: u64 process_get_base_addr(pid);if base_addr 0 {println!(gamebox find base addr failed: {}, pid);return;}fn_addr base_addr 0x121E - 0x55; // decMoney 地址base_addr 0x121E 0x2E22; // Account ac 全局变量偏移println!(gamebox base addr: {:#X}, fn addr: {:#X},base_addr, fn_addr);// 需要root权限let ppid Pid::from_raw(i32::from_str_radix(pid, 10).unwrap());if let Ok(_) ptrace::attach(ppid) {waitpid(ppid, None).unwrap();// accountlet id ptrace_read_at(ppid, base_addr 24, 8);let name ptrace_read_at(ppid, base_addr 0, 20);let role_addr ptrace_read_at(ppid, base_addr 24 8, 8);let name2 unsafe { CStr::from_ptr(name.as_ptr() as *const i8) };let role_addr2 u64::from_le_bytes(role_addr.try_into().unwrap());println!(Account ID: {}, Name: {:?}, role addr: 0x{:#X},u64::from_le_bytes(id.try_into().unwrap()),name2.to_str(),role_addr2,);// rolefor i in 0..4 {let level ptrace_read_at(ppid, role_addr2 24 * i 12, 4);let money ptrace_read_at(ppid, role_addr2 24 * i 16, 8);let pos_x ptrace_read_at(ppid, role_addr2 24 * i 0, 4);let pos_y ptrace_read_at(ppid, role_addr2 24 * i 4, 4);let pos_z ptrace_read_at(ppid, role_addr2 24 * i 8, 4);println!(\tRole[{}] Level: {}, Money: {}, Pos: [{:.2}, {:.2}, {:.2}],i,u32::from_le_bytes(level.try_into().unwrap()),u64::from_le_bytes(money.try_into().unwrap()),f32::from_le_bytes(pos_x.try_into().unwrap()),f32::from_le_bytes(pos_y.try_into().unwrap()),f32::from_le_bytes(pos_z.try_into().unwrap()),);}// modify function decMoneylet asm_addr fn_addr 0x44;// ptrace 每次写入也是 8 字节的数据, 所以先读取然后再写入let mut old_asm ptrace_read_at(ppid, asm_addr, 8);println!(old_asm: {:?}, old_asm);// 修改 sub rax, rcx add rax, rcxold_asm[1] 0x01;let new_val u64::from_ne_bytes(old_asm.try_into().unwrap());unsafe { ptrace::write(ppid, asm_addr as *mut c_void, new_val as *mut c_void).unwrap();};let old_asm2 ptrace_read_at(ppid, asm_addr, 8);println!(old_asm2: {:?}, old_asm2);ptrace::detach(ppid, None).unwrap();} else {println!(need root permission!);}}
}fn read_at_mem(f: File, addr: u64, sz: usize) - Vecu8 {let mut buf Vec::new();buf.resize(sz, 0);f.read_at(mut buf, addr).unwrap();return buf;
}fn ptrace_read_at(ppid: Pid, addr: u64, sz: usize) - Vecu8 {let mut buf Vec::new();buf.resize(sz, 0);// println!(Begin read {:#X}, sz{}, addr, sz);let mut sz2 sz/8;if sz%8 ! 0 {sz2 1;}for i in 0..sz2 {let paddr (addr (i*8) as u64) as *mut c_void;// ptrace 每次读取 8 个字节的数据let val ptrace::read(ppid, paddr).unwrap();//println!(read: [{}] {:#X}, addr (i*8) as u64,val);let mut idx i*8;for b in val.to_ne_bytes(){if idx sz {buf[idx] b;idx 1;}else{break;} }}return buf;
}// 忽略大小写进行进程查找
fn find_id_by_name(name: str) - VecString {// 遍历和读取 /proc/xxx/commlet root path::Path::new(/proc/);let mut pids: Vec_ Vec::new();for sub in root.read_dir().unwrap() {if let Ok(id_dir) sub {let fp id_dir.path();if fp.is_dir() {let comm fp.join(comm);if comm.exists() {let txt file_read_content(comm.to_str().unwrap());// println!(try file: {:?}, {}, comm, txt);if txt.to_ascii_lowercase() name.to_ascii_lowercase() {let pid OsString::from(fp.file_name().unwrap()).into_string().unwrap();pids.push(pid);}}}}}pids
}fn file_read_content(filepath: str) - String {let mut txt String::new();if let Ok(mut f) File::open(filepath) {f.read_to_string(mut txt).unwrap();}// 默认内容有换行txt txt.trim().to_string();return txt;
}// 通过 /proc/pid/maps 查找 program 地址
fn process_get_base_addr(pid: str) - u64 {let mut addr 0u64;if let Ok(f) File::open(format!(/proc/{}/maps, pid)) {let mut txt String::new();let mut br BufReader::new(f);br.read_line(mut txt).unwrap();//55af86ae3000-55af86ae4000 r--p 00000000 08:12 6160449 /xx/gameboxif let Some(pos) txt.find(-) {addr u64::from_str_radix(txt[..pos], 16).unwrap();}}return addr;
}/*
gamebox pid: [101595]
gamebox base addr: 0x55698A849040, fn addr: 0x55698A8461C9
Account ID: 20240318, Name: Ok(我是张三啊), role addr: 0x0x55698B1156B0Role[0] Level: 10, Money: 5000, Pos: [0.00, 0.00, 0.00]Role[1] Level: 12, Money: 5000, Pos: [1000.00, 1100.00, 1200.00]Role[2] Level: 14, Money: 5000, Pos: [2000.00, 2200.00, 2400.00]Role[3] Level: 16, Money: 5000, Pos: [3000.00, 3300.00, 3600.00]
old_asm: [72, 41, 200, 72, 137, 194, 72, 139]
Input any key to modify decMoney function ....
old_asm2: [72, 1, 200, 72, 137, 194, 72, 139]
*/proc
https://blog.csdn.net/murphy_ma123456/article/details/16117577 https://blog.csdn.net/m0_37315653/article/details/82693108 https://zhuanlan.zhihu.com/p/378388389