题目
攻防世界 Reverse 高手区题目链接 simple-check-100,如下:
解压缩得到三个文件:
三个文件依次是一个 32 位 elf,一个 64 位 elf 和一个 32 位 exe。
ELF 文件 (Executable Linkable Format) 是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。Linux 下的目标文件和可执行文件都按照该格式进行存储,它是 Linux 的主要可执行文件格式。
IDA静态分析
1、查壳发现未加壳:
2、这 3 个文件拖进 IDA 后的反汇编结果是大体一致的,以 64 位 elf 文件为例进行分析,定位到 main 函数:
3、按 F5 查看反汇编结果的的 C 语言伪代码:
完整代码如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{void *v3; const char **v5; int v6; char v7[28]; __int64 v8; const char ***v9; unsigned __int64 v10; v6 = argc;v5 = argv;v10 = __readfsqword(0x28u);v7[0] = 84;v7[1] = -56;v7[2] = 126;v7[3] = -29;v7[4] = 100;v7[5] = -57;v7[6] = 22;v7[7] = -102;v7[8] = -51;v7[9] = 17;v7[10] = 101;v7[11] = 50;v7[12] = 45;v7[13] = -29;v7[14] = -45;v7[15] = 67;v7[16] = -110;v7[17] = -87;v7[18] = -99;v7[19] = -46;v7[20] = -26;v7[21] = 109;v7[22] = 44;v7[23] = -45;v7[24] = -74;v7[25] = -67;v7[26] = -2;v7[27] = 106;v8 = 19LL;v3 = alloca(32LL);v9 = &v5;printf("Key: ");__isoc99_scanf("%s", v9);if ( (unsigned int)check_key(v9) )interesting_function(v7);elseputs("Wrong");return 0;
}
可以看到,程序的核心是让输入一个字符串,然后使用 check_key 函数进行判断,如果返回非 0(为真),则执行 interesting_fuction 函数。
4、双击跟进查看 interesting_fuction 函数伪代码:
该函数对输入的 v7(未知)进行加密处理,最后 putchar 输出。
5、check_key 函数是一个计算校验和与正确的校验和对比的一个函数:
【解题思路】
只要让程序绕过 check_key 函数的检查,强行执行 interesting_function 函数,就能获得目标 Flag。故可以对程序进行动态调试,将 check_key 的返回结果改为 1,就能调用 interesting_function 函数并得到正确的 key。
gdb动态调试
GDB (GNU Debugger)是一个由 GNU 开源组织发布的、UNIX/LINUX 操作系统下的、基于命令行的、功能强大的程序调试工具。像所有的调试器一样,GDB 可以让你调试一个程序,包括让程序在你希望的地方停下,此时你可以查看变量、寄存器、内存及堆栈,更进一步你可以修改变量及内存值。对于一名 Linux 下工作的 C/C++ 程序员或逆向工作者,gdb 是必不可少的工具。
基础使用教程:linux下gdb调试方法与技巧整理、CTF 竞赛入门指南(CTF All In One)。
gdb 基本使用
gdb 的常用命令如下:
本人在自己的 VPS 服务器(Centos8)上安装 gdb,步骤如下:
yum install gcc gcc-c++
wget http://ftp.gnu.org/gnu/gdb/gdb-10.2.tar.gz
tar -zxvf gdb-10.2.tar.gz
cd gdb-10.2
./configure --with-python=/usr/bin/python --enable-targets=all
make
sudo make install
gdb -v
【注意防坑】编译 gdb 时必须加上--with-python
参数指定本地的 Python 路径,否则后续使用 gdb 插件时将各种报错(为此我浪费了整整一天……最终参照 博文 找到了该解决的办法)。
如下已成功安装 gdb 工具:
GDB基础用法演示
下面给出一个具有 test.c 的程序:
#include
int getSum(int n) {int sum&#61;0,i;for (i&#61;1;i<&#61;n;i&#43;&#43;)sum&#43;&#61;i;return sum;
}
int main(){int res&#61;getSum(100);printf("1&#43;2&#43;...&#43;100&#61;%d\n",res);
}
1、使用 gcc 编译 C 语言程序&#xff1a;
【注意】如果要调试程序&#xff0c;则在进行 gcc 编译的时候要加上 -g 参数&#xff08;表示 debug 模式&#xff09;&#xff0c;即gcc -g test.c -o test
(如果是 C&#43;&#43; 程序的话则是g&#43;&#43; -g test.cpp -o test
&#xff09;。如果没有 -g 参数&#xff0c;你将看不见程序的函数名、变量名&#xff0c;所代替的全是运行时的内存地址。
2、执行命令 gdb test
&#xff0c;开始使用 GDB 对生成的 test 可执行程序进行调试&#xff08;GDB 会显示自己的提示符 gbd&#xff0c;提示并等待你输入调试命令&#xff09;&#xff1a;
3、输入l
命令&#xff08;相当于 list&#xff09;&#xff0c;gdb 将从第一行开始列出源码&#xff08;默认前 10 行&#xff09;&#xff1a;
直接回车表示&#xff0c;重复上一次命令&#xff08;继续输出第 10-20 行的源码&#xff09;&#xff1a;
4、执行命令break 9
、break getSum
&#xff0c;表示在第 9 行处、getSum 函数处设置断点&#xff0c;同时执行命令info break
可查看设置的断点信息&#xff1a;
5、执行命令run
(简写 r) &#xff0c;开始从头运行程序&#xff0c;直到程序结束或者遇到断点并等待下一个命令&#xff1a;
6、执行命令 continue
&#xff08;简写 c&#xff09;&#xff0c;表示从暂停处继续运行程序&#xff1a;
7、执行 step
命令&#xff0c;表示向前执行一步&#xff08;可进入被调用函数中&#xff09;&#xff0c;进一步可利用 print i
&#xff08;变量名&#xff09;来查看变量的值&#xff1a;
8、执行命令 backtrace
&#xff08;简写 bt&#xff09;&#xff0c;可查询当前函数调用栈&#xff0c;执行命令 finish
&#xff0c;可退出当前函数并返回到上层函数中&#xff08;本例为 main 主函数&#xff09;&#xff1a;
9、执行命令set res &#61; 6666
&#xff0c;可改变程序中指定变量的值&#xff1a;
10、info reg
命令可查看寄存器使用情况&#xff0c;info stack
命令可查看堆栈使用情况&#xff1a;
最后执行命令 quit
&#xff08;简写 q&#xff09;&#xff0c;即可退出 gdb&#xff1a;
而未被篡改变量值的程序正常的运行输出应当如下&#xff1a;
gdb peda插件
从上面的演示实例中也可以看出&#xff0c;gdb 非常强大&#xff0c;但是在调试过程中对于需要查看的辅助信息&#xff08;如寄存器信息&#xff09;都要手动输入命令&#xff0c;未免有点麻烦&#xff0c;所以就出现了插件&#xff0c;把某一些经常要查看的信息每一步都自动帮你显示出来&#xff0c;方便调试。一般来说有常用的 3 个 GDB 插件&#xff1a;peda、gef、gdbinit。
&#xff0c;完整介绍可参见&#xff1a;GDB的三个插件&#xff08;gef gdbinit peda&#xff09;。Github 上已经有人把这 3 个插件的项目合成了一个&#xff0c;使得插件的安装使用很方便&#xff1a;
git clone https://github.com/gatieme/GdbPlugins.git ~/GdbPlugin
echo "source ~/GdbPlugins/peda/peda.py" > ~/.gdbinit
echo "source ~/GdbPlugins/gef/gef.py" > ~/.gdbinit
echo "source ~/GdbPlugins/gdbinit/gdbinit" > ~/.gdbinit
成功克隆到本地后可以看到包含 3 个插件的文件夹&#xff1a;
下文将介绍其中的 peda 插件——peda 是 gdb 调试工具的插件&#xff0c;用于增强 gdb 的调试能力&#xff0c;同时增强 gdb 的显示&#xff1a;在调试过程中着色并显示反汇编代码&#xff0c;寄存器和内存信息&#xff08;单纯的 gdb 在内存信息、汇编信息的显示和查看上的不方便&#xff09;。
1、设置 gdb 以 peda 插件的执行形式启动&#xff0c;并调试上面编译好的 test 程序&#xff1a;
2、输入 start
命令开始调试程序&#xff0c;从下图中可以看到寄存器 (registers)&#xff0c;汇编代码 (code)&#xff0c;栈空间数据 (stack) 等信息&#xff1a;
在前面的文章 浅析缓冲区溢出漏洞的利用与Shellcode编写 中曾经介绍了函数调用过程中的内存堆栈变化&#xff0c;建议读者同步阅读博文 基于GDB-peda汇编调试理解函数调用栈 &#xff0c;作者利用 peda 插件来汇编调试一段程序&#xff0c;帮助深入理解函数调用栈。
函数校验绕过
返回到 CTF 题目中&#xff0c;在 Linux 中正常运行程序如下&#xff1a;
下面开始借助 gdb 对程序进行动态调试&#xff0c;将 check_key 函数的返回结果改为真即可正确的 key。
1、启动 gdb 调试程序&#xff0c;执行命令 break main
在 main 函数设置断点&#xff08;Breakpoint 1 at 0x4007c0&#xff09;&#xff0c;然后执行命令 run
开始运行程序&#xff1a;
程序暂停在断点处&#xff1a;
2、上述程序暂停在 main 函数断点处&#xff0c;执行命令 next
开始一步步单步执行程序&#xff08;一直按回车键重复执行 next 命令即可&#xff09;&#xff0c;直到运行至 check_key 函数所在位置&#xff0c;随意输入 key 之后会有判断函数&#xff0c;所以注意看 check_key 所在位置&#xff1a;
3、当判断函数执行完之后&#xff0c;再次跳到test eax, eax
时候&#xff0c;可以用printi $eax
查看寄存器的值&#xff0c;发现是 0&#xff1a;
4、这样的话不会跳转藏有 flag 函数的位置&#xff0c;所以执行命令 set $eax&#61;1
&#xff0c;手动篡改寄存器 eax 的值&#xff0c;最后执行命令 continue
&#xff0c;运行程序直至程序终止&#xff0c;可获得 flag 值如下&#xff1a;
提交 flag&#xff0c;over&#xff01;
总结
本文通过一道 CTF 题目&#xff0c;学习记录了 GDB 调试工具及其 peda 插件的使用&#xff0c;实现了对二进制程序的内存数值进行篡改并成功绕过程序的逻辑校验的目的&#xff0c;这有点类似于 Android 中使用 Frida 对 APP 的函数返回值进行 hook 拦截和篡改。