大家好,今天小编为大家分享关于gg修改器 游戏代码_gg修改器游戏代码大全的内容,赶快来一起来看看吧。
今天的主题要从一道”花式调用函数”的题目谈起,题目是这样的:
void Echo() {
puts("HelloWorld");
}
int main() {
// 在下面的括号中填写代码,使用任意方法调用Echo(), 打印“HelloWorld”即可
{
// Your Code Here, Example: Echo();
}
return 0;
}
如果学了一段时间C语言,我们还可以想到通过函数指针来实现,比如这样:
void Echo() {
puts("HelloWorld");
}
int main() {
// 在下面的括号中填写代码,使用任意方法调用Echo(), 打印“HelloWorld”即可
{
void(*f)();
f = Echo;
f();
}
return 0;
}
如果你使用g++编译代码,使用C++11编译选项(-std=c++11)还可以通过将函数构造为Functor的方式去调用,比如这样
void Echo() {
puts("HelloWorld");
}
int main() {
// 在下面的括号中填写代码,使用任意方法调用Echo(), 打印“HelloWorld”即可
{
using Function = std::function<void()>;
Function t = Echo;
t();
}
return 0;
}
如果你不太熟悉std::function的使用,这里简单的介绍下。代码中通过调用std::function的赋值构造函数,构造了一个std::function对象;对象内保存了Echo的函数指针。由于std::function的类中重载了`括号`操作符,所以当调用t()的时候,实际上执行了std::function的operator()(arg1..argN) 方法,通过它来调用之前保存的Echo函数。c++11之后,通过std::function可以调用签名相似的函数,实现函数的类型擦除;配合lambda函数,逐步摒弃了之前c++98的众多”函数适配器”,比如bind1st、bind2nd等;增加了灵活性。
言归正传,那么有没有其他的方法么?就这道题目而言(特别提醒:工程项目中请不要使用这么tricky的技巧- -!),我们还可以这样做:
void Echo() {
puts("HelloWorld!");
}
int main(int argc, char* argv[], char** env) {
size_t arr[1] ;
// arr[0] = arr[2]; ①
arr[2] = (size_t)Echo; ②
// arr[3] = arr[0]; ③
return 0;
}
没错,这里用到的知识点就”栈缓冲区溢出”,②处的代码通过数组越界的方式覆盖了ebp寄存器(x86为ebp,x86_64为rbp),即当main函数返回执行return之后,就转去执行Echo函数中d的代码。想要明白具体的原理,我们要搞清楚函数的调用逻辑是怎么的,请看下图
x86下Stack Frame
这里蓝色的部分为”调用者函数”,黄色部分为”被调用者函数”。比如蓝色部分为main,黄色部分为sum(int a, int b)函数。和把大象装进冰箱的步骤一样,这里也分为3步:
(1)参数入栈:当我们准备调用sum(100,200)的时候,根据x86的调用规范,参数通过栈传递。大多数调用规范来讲,参数都是由右向左传递,所以main函数来说,按照图中所示,arg2为200、arg1为100;把它们压入栈中
(2)保存返回:由于是从main函数中调用其他函数(main函数的代码还未完全执行完),所以需要把main函数的sum(int a, int b)之后的语句先保存下来,作为调用sum之后继续执行的语句,也就是这里的”Return Address”
(3)开辟新栈:执行sum函数中的代码,开辟一块新的栈空间给sum作为该作用域内变量的存储空间
那么当我们在sum(int a, int b)中想访问a,b参数的时候,就可以借助ebp (x86为ebp,x86_64为rbp)偏移来访问,由于栈整体是由高到低分配的,所以比如上图来说ebp+0x4就是Return address、ebp+0x8就是arg1、ebp+0x10就是arg2。ebp+0x4作为函数sum执行之后,main函数继续执行的地址,是本次构造栈溢出的重点。我们的最后一种方法,实际上就是通过在main函数中local variables部分保存的数组越界,达到了修改main函数返回地址(epb+0x4)的目的;让其走到了Echo函数中。
修改main函数返回地址?也许你会疑惑main函数也是一个被调用的函数么?没错,main函数会被glibc中的代码调用,在glibc提供的run time下,其实main函数的调用链大致是这个过程: _start -> __libc_start_main -> main。
由于笔者的环境是x86_64机器,所以堆栈是这样的,需要覆盖的返回值地址为rbp+0x8:
x86_64下Stack Frame
“源码之下,了无秘密”。现在让我们看下我们的代码对应的二进制是否覆盖了rbp+0x8的位置
➜ objdump -M intel -d -S a.out
0000000000400597 <main>:
int main(int argc, char* argv[], char** env) {
400597: 55 push rbp
400598: 48 89 e5 mov rbp,rsp
40059b: 89 7d ec mov DWORD PTR [rbp-0x14],edi
40059e: 48 89 75 e0 mov QWORD PTR [rbp-0x20],rsi
4005a2: 48 89 55 d8 mov QWORD PTR [rbp-0x28],rdx
size_t arr[1] ;
arr[0] = arr[2];
4005a6: 48 8b 45 08 mov rax,QWORD PTR [rbp+0x8]
4005aa: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
arr[2] = (size_t)Echo;
4005ae: b8 86 05 40 00 mov eax,0x400586 // 将Echo函数的地址复制到eax中
4005b3: 48 89 45 08 mov QWORD PTR [rbp+0x8],rax // 覆盖了rbp+0x8的位置
arr[3] = arr[0];
4005b7: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
4005bb: 48 89 45 10 mov QWORD PTR [rbp+0x10],rax
return 0;
4005bf: b8 00 00 00 00 mov eax,0x0
}
4005c4: 5d pop rbp
4005c5: c3 ret
4005c6: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
4005cd: 00 00 00
看到现在,反汇编的之后的代码确实符合预期。那么对于0x400586这个地址为何就是Echo的地址,能否实锤一下呢?我们可以通过检查程序符号表看一下
➜ readelf –sym a.out |grep Echo
104: 0000000000400586 17 FUNC GLOBAL DEFAULT 13 Echo
由于Echo函数并非通过动态库链接,所以在编译的时候就会确定好当前的位置,通过readelf我们便看到了Echo的位置和汇编中访问的地址一样!
现在,我们运行一下我们通过栈溢出这种”奇技淫巧”实现访问Echo函数的方式
➜ ~ ./a.out
HelloWorld![1]
17109 segmentation fault (core dumped) ./a.out
是不是看到这个结果有点意外?这确实算不上是一次完美的调用。对于拥有极客精神的我们,需要继续探索一下。不知道大家是否注意到在代码中注释掉的两行,现在我们把注释取消。
void Echo() {
puts("HelloWorld!");
}
int main(int argc, char* argv[], char** env) {
size_t arr[1] ;
arr[0] = arr[2]; ①
arr[2] = (size_t)Echo; ②
arr[3] = arr[0]; ③
return 0;
}
运行结果是:
➜ ~ ./a.out
HelloWorld!
通过之前分析,main函数也是拥有返回地址的。正是由于main中执行return语句后,会返回到上层调用函数__libc_start_main中去。所以站在main的角度,如果我们强行覆盖了main函数的Return Address(rbp+0x8)为Echo函数,当Echo执行完毕之后返回main的下条语句(rbp+0x10)必然是一条不符合语境的语句,而它原来想表达是返回__libc_start_main中去继续执行。所以到导致了本次segment fault的发生。
因此,我们通过②和③处的代码,先备份原来rbp+0x8的代码,再填充到Echo调用之后。这样会让main函数的调用链完整走完,因而不会发生崩溃了。那么rbp+0x10是什么内容呢?覆盖它会出错么?如果通过gdb分析,可以看到,rbp+0x10实际上是指main函数的参数。不要忘了,完整的main函数签名也是有参数的:main(int argc,char* argv[], char** env),这些参数正是由main的调用者__libc_start_main传递过来的,当前覆盖rbp+0x10位置的参数我们并未使用,所以并无大碍。
实际上对于这类危险的操作,编译器也在试图避免缓冲区溢出带来的安全隐患;只是需要通过特定的方式开启,编译的时候可以看到gcc有以下编译选项:
-fno-stack-protector -- Disable statck protection 禁用栈保护
-fstack-protector -- Use propolice as a stack protection method 对alloca系列函数和内部缓冲区大于8字节的函数启用保护
-fstack-protector-all -- Use a stack protection method for every function 对所有函数启用保护
-fstack-protector-explicit -- Use stack protection method only for functions with the stack_protect attribute 对定义了stack_protect属性的函数提供保护
-fstack-protector-strong -- Use a smart stack protection method for certain functions 增加对包含局部数组定义和地址引用的函数的保护
我们可以通过checksec命令来对比下添加了保护后的两个程序差异
➜ ~ checksec --file=a.out
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 108) Symbols No 0 0 a.out
➜ ~ checksec --file=a.out.protect
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH 109) Symbols No 0 0 a.out.protect
很明显,通过 -fstack-protector-strong 编译的程序,打开了STACK CANARY。结果可想而知,由于运行时会对栈是否被覆盖做校验会触发崩溃。
拓展阅读:
https://zhuanlan./p/25816426
https://blog./iamokay/2155957
以上就是关于gg修改器 游戏代码_gg修改器游戏代码大全的全部内容,希望对大家有帮助。