FORTIFY_SOURCE
FORTIFY_SOURCE(源码增强),这个其实有点类似与Windows中用新版Visual Studio进行开发的时候,当你用一些危险函数比如strcpy、sprintf、strcat,编译器会提示你用xx_s加强版函数。
FORTIFY_SOURCE本质上一种检查和替换机制,对GCC和glibc的一个安全补丁。
目前支持memcpy, memmove, memset, strcpy, strncpy, strcat, strncat,sprintf, vsprintf, snprintf, vsnprintf, gets等。
默认Ubuntu16.04下是关闭的,测试发现Ubuntu18.04是开启的
gcc -D_FORTIFY_SOURCE=1 仅仅只在编译时进行检查(尤其是#include <string.h>这种文件头)
gcc -D_FORTIFY_SOURCE=2 程序执行时也会进行检查(如果检查到缓冲区溢出,就会终止程序)FORTIFY_SOURCE(代码增强)
-D 1(开启缓冲区溢出攻击检查)
-D 2(开启缓冲区溢出以及格式化字符串攻击检查) ,通过数组大小来判断替换strcpy、memcpy、memset等函数名,来防止缓冲区溢出。
pwn32
我们首先还是先将pwn文件下载下来,然后拖进虚拟机加上可执行权限使用checksec命令查看文件的信息。
chmod +x pwn
checksec pwn
文件是64位的我们先拖进ida64反编译一下。
// main
int __cdecl main(int argc, const char **argv, const char **envp)
{__gid_t v3; // eaxconst char *v4; // raxint v5; // eaxint num; // [rsp+4h] [rbp-44h] BYREFchar buf2[11]; // [rsp+Ah] [rbp-3Eh] BYREFchar buf1[11]; // [rsp+15h] [rbp-33h] BYREFv3 = getegid();setresgid(v3, v3, v3);logo();v4 = argv[1];*(_QWORD *)buf1 = *(_QWORD *)v4;*(_WORD *)&buf1[8] = *((_WORD *)v4 + 4);buf1[10] = v4[10];strcpy(buf2, "CTFshowPWN");printf("%s %s\n", buf1, buf2);v5 = strtol(argv[3], 0LL, 10);memcpy(buf1, argv[2], v5);strcpy(buf2, argv[1]);printf("%s %s\n", buf1, buf2);fgets(buf1, 11, _bss_start);printf(buf1, &num);if ( argc > 4 )Undefined();return 0;
}
// Undefined
void __cdecl Undefined()
{FILE *v0; // raxchar flag[64]; // [rsp+0h] [rbp-48h] BYREFputs("The source code of these three programs is the same, and the results of turning on different levels of protection are understood\n");puts("You should understand the role of these protections!But don't just get a flag\nHere is your flag:\n");v0 = fopen("/ctfshow_flag", "r");if ( !v0 ){puts("/ctfshow_flag: No such file or directory.");exit(0);}fgets(flag, 64, v0);puts(flag);
}
我们先来分析一下代码的逻辑:
程序打印出logo的信息,然后然后将第一个参数argv[1]也就是我们启动程序输入的第一个参数(其实还有argv[0]的,这个参数是每个程序的都一定会有的,并且值为程序名称)赋给v4,并且没有长度限制。之后程序将v4[10]的数组内容赋给buf1[10]数组,再将CTFshowPWN通过strcpy函数赋给buf2,接着打印输出buf1和buf2。再然后通过strtol(argv[3], 0LL, 10)将我们启动程序传入的第三个参数转为10进制赋给v5,紧接着使用memcpy函数将argv2的v5个字符赋给buf1,v5是int型,数值就是上条语句的得来的;然后再通过strcpy函数argv[1]我们输入第一个参数赋给buf2,之后再打印输出buf1和buf2,最后读取_bss_start 11个字符给buf1,打印输出buf1和num的信息。最最后通过if判断argc是否大于4(默认情况下argc是等于1的,因为argv[0]必定存在,我们输入一个参数,argc就会等于2,依次递推),如果大于4,就会进入Undefined函数,Undefined函数的作用就是输出flag了。
因为本题目FORTIFY_SOURCE没有开启,代表我们启动函数直接输入4个参数(这时argc=5 > 4)就行了,而且这4个参数没有长度限制,如果开启FORTIFY_SOURCE就不好说了,因为开启了之后,由于程序存在strcpy和memcpy函数会检测长度,如果长度超过了限制,可能会使程序抛出异常而退出执行。
那么这道题目我们就直接输入4个参数就行了。
先ssh连接上主机。
ssh ctfshow@pwn.challenge.ctf.show -p28160
这里我们使用ls看了下目录存在pwnme文件这个文件就给我们之前下载的pwn文件是一样的,我们运行随便加上4个参数就行了。
./pwnme 1 2 3 4
这时程序会停在这里,我们直接一个回车就行了。
OK,成功拿到了flag。
pwn33
题目已经告诉我们开启了FORTIFY_SOURCE=1,并说明在编译时进行一些安全检查,如缓冲区边界检查、格式化字符串检查等。 在运行时进行某些检查,如检测函数返回值和大小的一致性。 如果检测到潜在的安全问题,会触发运行时错误,并终止程序执行。
本题目的文件跟上道一样,pwn都是64位的,并且反编译出的代码也大致相似,这里就不详细分析反编译的代码了,但是它开启了FORTIFY_SOURCE=1,所以我们要注意源代码里的memcpy和strcpy函数。
memcpy和strcpy这两个函数被替换成了__mencpy_chk和__strcpy__chk安全函数,可以看到这两个函数相比前两个函数只是加上了11LL这个参数加以限制,因为buf1和buf2在声明的时候的长度就是11,所以程序为了防止溢出,使用后两个函数加上这两个数组的长度加以限制以防溢出。
那这个东西对于我们想要和上道题目一样输出flag完全是不没有任何阻碍的呀,我们就保证我们输入的第一个参数和第二个参数的长度不超过11就行了啊,只是上到题目没有开启FORTIFY_SOURCE,我们输入参数的长度是任意的,这道题目对我们的参数加以限制了而以。
我们直接ssh连接,运行pwnme加上不超过11长度的4个参数就行了(主要是参数1和参数2不超过11),那我们就和上道题目一样每个参数长度都为1呗。
ssh ctfshow@pwn.challenge.ctf.show -p28161
./pwnme 1 2 3 4
成功拿到了flag。
pwn34
本道题目指明了FORTIFY_SOURCE=2,在虚拟机用checksec命令检查如下:
题目描述也说了该程序包括基本级别的安全检查,并添加了更多的检查。 在编译时进行更严格的检查,如更精确的缓冲区边界检查。 提供更丰富的编译器警告和错误信息。
使用ida64反编译的加过略微与前两道题目有所不同,大部分还是一样的,还是把危险函数替换成了安全函数。如下:
__mencpy_chk和__strcpy__chk安全函数上道题目已经说过与memcpy和strcpy的区别,这里不再赘述。
__printf__chk该函数与printf的区别在于:
不能使用 %x$n 不连续地打印,也就是说如果要使用 %3$n,则必须同时使用 %1$n 和 %2$n。在使用 %n 的时候会做一些检查。
这个涉及到格式化字符串漏洞,本题涉及不到,所以对本道题也几乎阻碍不大,所以我们还是ssh之后运行文件时输入的4个长度为1的参数,不出意外还能到拿到flag!
ssh ctfshow@pwn.challenge.ctf.show -p28177
./pwnme 1 2 3 4
果然没有出意外hhh,拿到了flag!
本文链接:https://my.lmcjl.com/post/2850.html
4 评论