原文链接:4b9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6^5i4K6u0V1j5K6y4D9L8q4)9J5k6h3N6A6N6r3S2#2j5W2)9J5k6h3W2G2i4K6u0r3M7r3!0K6N6s2y4Q4x3V1k6r3M7X3W2V1j5g2)9J5k6q4m8%4L8W2)9J5k6p5q4V1N6X3g2F1N6s2g2J5k6g2)9J5k6o6y4Q4x3V1j5`.
编译:看雪翻译小组 SpearMint
最近我看到
LiveOverflow 开始播放一系列关于如何“破解“一款游戏的视频,破解该游戏是2015年Ghost in the Shellcode CTF比赛中的一项挑战。在观看了前两、三个视频之后,我决定使用同一款游戏来做一些关于
Frida 的介绍,并且向你展示这个了不起的项目是可以助你一臂之力的。
因此,在本文中,我们要构思一个可以帮助我们破解游戏的作弊方法。读者要注意的如下:
使用Frida hook函数 读内存 写内存 在我们的hook中调用二进制函数 处理类和结构 首先,请参考 此链接 配置服务器。如果你已准备好服务器和客户端,那我们就开玩!:)
第一步是启动客户端,注册新玩家,并探索游戏世界。在你花了几分钟在地图上移动并查看HUD(比如魔法值,生命值,物品等)之后,现在是时候继续下一步,在终端上做一些小动作了。游戏运行时,执行ps-aux并检查客户端二进制文件的名称:PwnAdventure3-Linux-Shipping。它是一个带符号的动态链接二进制文件(使用file查看),因此很有可能我们关心的“核心”位于一个共享对象中。 我们可以使用ldd列出该二进制文件使用的所有共享对象:
mothra@kaiju:~/holydays|⇒ ldd $(locate PwnAdventure3-Linux-Shipping)
linux-vdso.so.1 (0x00007fff5e6c2000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f5af4631000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f5af442d000)
libGameLogic.so => /home/mothra/PwnAdventure3_Data/PwnAdventure3/PwnAdventure3/Binaries/Linux/libGameLogic.so (0x00007f5af3f61000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f5af3d59000)
libopenal.so.1 => /home/mothra/PwnAdventure3_Data/PwnAdventure3/PwnAdventure3/Binaries/Linux/../../../Engine/Binaries/Linux/libopenal.so.1 (0x00007f5af3b02000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f5af3801000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f5af34f6000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f5af32e0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5af2f35000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5af484e000)
libssl.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007f5af2cd4000)
libcrypto.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.0.0 (0x00007f5af28d7000)
mothra@kaiju:~/holydays|⇒ ldd $(locate PwnAdventure3-Linux-Shipping)
linux-vdso.so.1 (0x00007fff5e6c2000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f5af4631000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f5af442d000)
libGameLogic.so => /home/mothra/PwnAdventure3_Data/PwnAdventure3/PwnAdventure3/Binaries/Linux/libGameLogic.so (0x00007f5af3f61000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f5af3d59000)
libopenal.so.1 => /home/mothra/PwnAdventure3_Data/PwnAdventure3/PwnAdventure3/Binaries/Linux/../../../Engine/Binaries/Linux/libopenal.so.1 (0x00007f5af3b02000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f5af3801000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f5af34f6000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f5af32e0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5af2f35000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5af484e000)
libssl.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007f5af2cd4000)
libcrypto.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.0.0 (0x00007f5af28d7000)
看起来“libGameLogic.so”是我们的目标。由于游戏是用C++编写的,我们必须处理 name mangling (名字修饰)。为了转储所有导出函数并做名字解析,我们执行一个使用Frida和cxxfilt的小脚本: # Extract exports & demangle it
import frida
import cxxfilt
session = frida.attach("PwnAdventure3-Linux-Shipping")
script = session.create_script("""
var exports = Module.enumerateExportsSync("libGameLogic.so");
for (i = 0; i < exports.length; i++) {
send(exports[i].name);
}
""");
def on_message(message, data):
print message["payload"] + " - " + cxxfilt.demangle(message["payload"])
script.on('message', on_message)
script.load()
# Extract exports & demangle it
import frida
import cxxfilt
session = frida.attach("PwnAdventure3-Linux-Shipping")
script = session.create_script("""
var exports = Module.enumerateExportsSync("libGameLogic.so");
for (i = 0; i < exports.length; i++) {
send(exports[i].name);
}
""");
def on_message(message, data):
print message["payload"] + " - " + cxxfilt.demangle(message["payload"])
script.on('message', on_message)
script.load()
我们在做什么?我们将自己附加到正在运行的游戏进程(PwnAdventure3-Linux-Shipping),然后创建一个包含主逻辑的脚本(JavaScript)。从该JavaScript代码段我们可以访问Frida API,所有magic都会实现:):使用Module.enumerateExportsSync(libname)我们检索一个包含所有导出函数的数组,然后遍历数组并使用send()传递信息给Python脚本。在Python脚本中,我们只需调用cxxfilt.demangle()来解析名字。
现在我们有了一堆有用的信息,可以从中搜索。例如,让我们搜索与速度(speed)相关的方法:
mothra@kaiju:~/holydays|⇒ python demangle-exports.py > demangled.txt
mothra@kaiju:~/holydays|⇒ cat demangled.txt | grep -i speed
_ZN6Player12GetJumpSpeedEv - Player::GetJumpSpeed()
_ZThn168_N6Player12GetJumpSpeedEv - non-virtual thunk to Player::GetJumpSpeed()
_ZN6Player15GetWalkingSpeedEv - Player::GetWalkingSpeed()
_ZThn168_N6Player15GetWalkingSpeedEv - non-virtual thunk to Player::GetWalkingSpeed()
mothra@kaiju:~/holydays|⇒ python demangle-exports.py > demangled.txt
mothra@kaiju:~/holydays|⇒ cat demangled.txt | grep -i speed
_ZN6Player12GetJumpSpeedEv - Player::GetJumpSpeed()
_ZThn168_N6Player12GetJumpSpeedEv - non-virtual thunk to Player::GetJumpSpeed()
_ZN6Player15GetWalkingSpeedEv - Player::GetWalkingSpeed()
_ZThn168_N6Player15GetWalkingSpeedEv - non-virtual thunk to Player::GetWalkingSpeed()
通常我们不需要一直作弊。也许我们只想增加我们的移动速度以便做长距离移动,但在建筑物内我们想要正常的速度,或者将自己传送到另一个位置我们则需要传递坐标参数。最好的解决方法是使用游戏的聊天功能。 在我们导出的数据中搜索“chat”字样:
mothra@kaiju:~/holydays|⇒ cat demangled.txt | grep -i chat
_ZN6Player11ReceiveChatEPS_RKSs - Player::ReceiveChat(Player*, std::string const&)
_ZN11ClientWorld4ChatEP6PlayerRKSs - ClientWorld::Chat(Player*, std::string const&)
_ZN10LocalWorld13SendChatEventEP6PlayerRKSs - LocalWorld::SendChatEvent(Player*, std::string const&)
_ZN20GameServerConnection11OnChatEventEP6Player - GameServerConnection::OnChatEvent(Player*)
_ZN11ServerWorld4ChatEP6PlayerRKSs - ServerWorld::Chat(Player*, std::string const&)
_ZN11ServerWorld13SendChatEventEP6PlayerRKSs - ServerWorld::SendChatEvent(Player*, std::string const&)
_ZN13ClientHandler4ChatEv - ClientHandler::Chat()
_ZN11ClientWorld13SendChatEventEP6PlayerRKSs - ClientWorld::SendChatEvent(Player*, std::string const&)
_ZN6Player4ChatEPKc - Player::Chat(char const*)
_ZN20GameServerConnection4ChatERKSs - GameServerConnection::Chat(std::string const&)
_ZN6Player11PerformChatERKSs - Player::PerformChat(std::string const&)
_ZThn168_N6Player4ChatEPKc - non-virtual thunk to Player::Chat(char const*)
_ZN10LocalWorld4ChatEP6PlayerRKSs - LocalWorld::Chat(Player*, std::string const&)
mothra@kaiju:~/holydays|⇒ cat demangled.txt | grep -i chat
_ZN6Player11ReceiveChatEPS_RKSs - Player::ReceiveChat(Player*, std::string const&)
_ZN11ClientWorld4ChatEP6PlayerRKSs - ClientWorld::Chat(Player*, std::string const&)
_ZN10LocalWorld13SendChatEventEP6PlayerRKSs - LocalWorld::SendChatEvent(Player*, std::string const&)
_ZN20GameServerConnection11OnChatEventEP6Player - GameServerConnection::OnChatEvent(Player*)
_ZN11ServerWorld4ChatEP6PlayerRKSs - ServerWorld::Chat(Player*, std::string const&)
_ZN11ServerWorld13SendChatEventEP6PlayerRKSs - ServerWorld::SendChatEvent(Player*, std::string const&)
_ZN13ClientHandler4ChatEv - ClientHandler::Chat()
_ZN11ClientWorld13SendChatEventEP6PlayerRKSs - ClientWorld::SendChatEvent(Player*, std::string const&)
_ZN6Player4ChatEPKc - Player::Chat(char const*)
_ZN20GameServerConnection4ChatERKSs - GameServerConnection::Chat(std::string const&)
_ZN6Player11PerformChatERKSs - Player::PerformChat(std::string const&)
_ZThn168_N6Player4ChatEPKc - non-virtual thunk to Player::Chat(char const*)
_ZN10LocalWorld4ChatEP6PlayerRKSs - LocalWorld::Chat(Player*, std::string const&)
那个Player::Chat(char const*)看起来很有趣,因为它收到一个指向字符串的指针(也许是我们的聊天消息?)。为了确认,我们hook它并将该字符串的内容输出为log:
# Log chat
import frida
import sys
session = frida.attach("PwnAdventure3-Linux-Shipping")
script = session.create_script("""
//Find "Player::Chat"
var chat = Module.findExportByName("libGameLogic.so", "_ZN6Player4ChatEPKc");
console.log("Player::Chat() at address: " + chat);
Interceptor.attach(chat, {
onEnter: function (args) { // 0 => this; 1 => cont char* (our text)
var chatMsg = Memory.readCString(args[1]);
console.log("[Chat]: " + chatMsg);
}
});
""")
script.load()
sys.stdin.read()
# Log chat
import frida
import sys
session = frida.attach("PwnAdventure3-Linux-Shipping")
script = session.create_script("""
//Find "Player::Chat"
var chat = Module.findExportByName("libGameLogic.so", "_ZN6Player4ChatEPKc");
console.log("Player::Chat() at address: " + chat);
Interceptor.attach(chat, {
onEnter: function (args) { // 0 => this; 1 => cont char* (our text)
var chatMsg = Memory.readCString(args[1]);
console.log("[Chat]: " + chatMsg);
}
});
""")
script.load()
sys.stdin.read()
通过Module.findExportByName(libname, function),我们得到Player::Chat的地址,然后我们将该地址传递给Interceptor,以便“附加”我们的hook。现在我们可以控制两个事件:onEnter和onLeave(按字面意思理解就行)。在onEnter里面我们可以看到参数(记住第一个参数是
this ,所以第二个参数是我们指向字符串的指针)。最后我们只需要用Memory.readCString(pointer)读取内存来获取字符串。执行之并输入聊天内容:
mothra@kaiju:~/holydays|⇒ python log-chat.py
Player::Chat() at address: 0x7f4ca4d4d850
[Chat]: This Works
mothra@kaiju:~/holydays|⇒ python log-chat.py
Player::Chat() at address: 0x7f4ca4d4d850
[Chat]: This Works
此时,我们可以在游戏聊天中键入并解析命令以触发我们的作弊行为。噢,等等,什么行为呢?继续阅读!
我们要做的第一件事就是加快移动速度。如前所述,二进制文件有符号。让我们转储与Player类相关的符号:
gdb -p $(pidof PwnAdventure3-Linux-Shipping) --batch -ex "ptype Player" -ex "quit" > Player.class
gdb -p $(pidof PwnAdventure3-Linux-Shipping) --batch -ex "ptype Player" -ex "quit" > Player.class
搜索speed:
mothra@kaiju:~/holydays|⇒ cat Player.class | grep -i speed
float m_walkingSpeed;
float m_jumpSpeed;
virtual float GetWalkingSpeed();
virtual float GetJumpSpeed();
mothra@kaiju:~/holydays|⇒ cat Player.class | grep -i speed
float m_walkingSpeed;
float m_jumpSpeed;
virtual float GetWalkingSpeed();
virtual float GetJumpSpeed();
有趣的是,我们找到了m_walkingSpeed(float),看起来像是移动动作的基准速度,还找到一个名为“GetWalkingSpeed()”的方法(如果我们用解析数据做交叉检查)对应于_ZN6Player15GetWalkingSpeedEv - Player::GetWalkingSpeed()。我们可以hook GetWalkingSpeed所以每次调用时m_walkingSpeed的值都会被我们想要的速度值覆盖。
mothra@kaiju:~/holydays|⇒ gdb -p $(pidof PwnAdventure3-Linux-Shipping) --batch
\ -ex "b _ZN6Player15GetWalkingSpeedEv" --ex "c" --ex "print &this->m_walkingSpeed"
\ -ex "print this" -ex "print (int)\$1-(int)\$2" -ex "quit" 2>/dev/null | awk '/\$3/ {print $3 }'
736
mothra@kaiju:~/holydays|⇒ gdb -p $(pidof PwnAdventure3-Linux-Shipping) --batch
\ -ex "b _ZN6Player15GetWalkingSpeedEv" --ex "c" --ex "print &this->m_walkingSpeed"
\ -ex "print this" -ex "print (int)\$1-(int)\$2" -ex "quit" 2>/dev/null | awk '/\$3/ {print $3 }'
736
所以我们的步骤是:1、hook ___ZN6Player15GetWalkingSpeedEv;2、获取onEnter的this指针并添加736(偏移量)来获取m_walkingSpeed的内存地址; 3、用我们想要的速度值覆盖该浮点数(不大于9999)。
// Find Player::GetWalkingSpeed()
var walkSpeed = Module.findExportByName("libGameLogic.so", "_ZN6Player15GetWalkingSpeedEv");
console.log("Player::GetWalkingSpeed() at address: " + walkSpeed);
// Check Speed
Interceptor.attach(walkSpeed,
{
// Get Player * this location
onEnter: function (args) {
console.log("Player at address: " + args[0]);
this.walkingSpeedAddr = ptr(args[0]).add(736) // Offset m_walkingSpeed
console.log("WalkingSpeed at address: " + this.walkingSpeedAddr);
},
// Find Player::GetWalkingSpeed()
var walkSpeed = Module.findExportByName("libGameLogic.so", "_ZN6Player15GetWalkingSpeedEv");
console.log("Player::GetWalkingSpeed() at address: " + walkSpeed);
// Check Speed
Interceptor.attach(walkSpeed,
{
// Get Player * this location
onEnter: function (args) {
console.log("Player at address: " + args[0]);
this.walkingSpeedAddr = ptr(args[0]).add(736) // Offset m_walkingSpeed
console.log("WalkingSpeed at address: " + this.walkingSpeedAddr);
},
留意我们是如何获取walkingSpeedAddr中m_walkingSpeed的内存地址的,以便我们在onLeave事件中访问此值:
// Get the return value and write the new value
onLeave: function (retval) {
console.log("Walking Speed: " + Memory.readFloat(this.walkingSpeedAddr));
Memory.writeFloat(this.walkingSpeedAddr, 9999);
}
});
// Get the return value and write the new value
onLeave: function (retval) {
console.log("Walking Speed: " + Memory.readFloat(this.walkingSpeedAddr));
Memory.writeFloat(this.walkingSpeedAddr, 9999);
}
});
正如我们之前对readCString所做的那样,现在我们使用Memory.readFloat来读取原始速度值(200)并将其记录在命令行终端中。最后,我们将移动速度设为浮点数(9999)。运行游戏并在地图上移动。疯狂的速度!
由于我们有获取聊天消息的例行程序,我们可以配合使用 !wspeed_on NUMBER和!wspeed_off来调节速度:
script = session.create_script("""
// Global Values
var Player = {
m_walkingSpeed : 200,
};
// Cheat status
var cheatStatus = {
walkingSpeed : 0,
};
// Chat Helper
function chatHelper(msg) {
var token = msg.split(" ");
if (token[0] === "!wspeed_on") {
Player.m_walkingSpeed = parseInt(token[1]);
cheatStatus.walkingSpeed = 1;
console.log("[CHEAT]: Walking Speed Enabled (" + token[1] + ")");
}
if (token[0] === "!wspeed_off") {
Player.m_walkingSpeed = 200;
cheatStatus.walkingSpeed = 0;
console.log("[CHEAT]: Walking Speed Disabled (200)");
}
}
//Find "Player::Chat"
var chat = Module.findExportByName("libGameLogic.so", "_ZN6Player4ChatEPKc");
console.log("Player::Chat() at address: " + chat);
// Add our logger
Interceptor.attach(chat, {
onEnter: function (args) { // 0 => this; 1 => cont char* (our text)
var chatMsg = Memory.readCString(args[1]);
console.log("[Chat]: " + chatMsg);
chatHelper(chatMsg);
}
});
// Find Player::GetWalkingSpeed()
var walkSpeed = Module.findExportByName("libGameLogic.so", "_ZN6Player15GetWalkingSpeedEv");
console.log("Player::GetWalkingSpeed() at address: " + walkSpeed);
// Check Speed
Interceptor.attach(walkSpeed,
{
// Get Player * this location
onEnter: function (args) {
//console.log("Player at address: " + args[0]);
this.walkingSpeedAddr = ptr(args[0]).add(736) // Offset m_walkingSpeed
//console.log("WalkingSpeed at address: " + this.walkingSpeedAddr);
},
// Get the return value and write the new speed
onLeave: function (retval) {
if (Memory.readFloat(this.walkingSpeedAddr) != Player.m_walkingSpeed && cheatStatus.walkingSpeed == 0) {
Memory.writeFloat(this.walkingSpeedAddr, 200);
}
if (cheatStatus.walkingSpeed == 1) {
Memory.writeFloat(this.walkingSpeedAddr, Player.m_walkingSpeed);
}
}
});
""")
script = session.create_script("""
// Global Values
var Player = {
m_walkingSpeed : 200,
};
// Cheat status
var cheatStatus = {
walkingSpeed : 0,
};
// Chat Helper
function chatHelper(msg) {
var token = msg.split(" ");
if (token[0] === "!wspeed_on") {
Player.m_walkingSpeed = parseInt(token[1]);
cheatStatus.walkingSpeed = 1;
console.log("[CHEAT]: Walking Speed Enabled (" + token[1] + ")");
}
if (token[0] === "!wspeed_off") {
Player.m_walkingSpeed = 200;
cheatStatus.walkingSpeed = 0;
console.log("[CHEAT]: Walking Speed Disabled (200)");
}
}
//Find "Player::Chat"
var chat = Module.findExportByName("libGameLogic.so", "_ZN6Player4ChatEPKc");
console.log("Player::Chat() at address: " + chat);
// Add our logger
Interceptor.attach(chat, {
onEnter: function (args) { // 0 => this; 1 => cont char* (our text)
var chatMsg = Memory.readCString(args[1]);
console.log("[Chat]: " + chatMsg);
chatHelper(chatMsg);
}
});
// Find Player::GetWalkingSpeed()
var walkSpeed = Module.findExportByName("libGameLogic.so", "_ZN6Player15GetWalkingSpeedEv");
console.log("Player::GetWalkingSpeed() at address: " + walkSpeed);
// Check Speed
Interceptor.attach(walkSpeed,
{
// Get Player * this location
onEnter: function (args) {
//console.log("Player at address: " + args[0]);
this.walkingSpeedAddr = ptr(args[0]).add(736) // Offset m_walkingSpeed
//console.log("WalkingSpeed at address: " + this.walkingSpeedAddr);
},
// Get the return value and write the new speed
onLeave: function (retval) {
if (Memory.readFloat(this.walkingSpeedAddr) != Player.m_walkingSpeed && cheatStatus.walkingSpeed == 0) {
Memory.writeFloat(this.walkingSpeedAddr, 200);
}
if (cheatStatus.walkingSpeed == 1) {
Memory.writeFloat(this.walkingSpeedAddr, Player.m_walkingSpeed);
}
}
});
""")
快速移动有助于探索更大的地图区域,但在地图的其他地点闪现的能力更酷。再次搜索我们的demangled函数:
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2018-8-13 19:49
被SpearMint编辑
,原因: