热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【CVE20177184】Linuxxfrm模块越界读写提权漏洞分析

一、漏洞分析漏洞位于内核模块,该模块是协议的实现模块。1.简介(1)IPSEC协议简介IPSEC是一个协议组合,它包含AH、ESP、IKE协议,提供对数据包的认证和加密功能,能帮助

一、漏洞分析

漏洞位于内核xfrm模块,该模块是IPSEC协议的实现模块。

1.简介


(1)IPSEC协议简介

IPSEC是一个协议组合,它包含AH、ESP、IKE协议,提供对数据包的认证和加密功能,能帮助IP层建立安全可信的数据包传输通道。

SA(Security Associstion):安全关联。SA由spi、ip、安全协议标识(AH或ESP)这三个参数唯一确定。SA定义了ipsec双方的ip地址、ipsec协议、加密算法、密钥、模式、抗重放窗口等。其实SA就是用xfrm_state结构体表示。

AH(Authentication Header):认证。AH为ip包提供数据完整性校验和身份认证功能,提供抗重放能力,验证算法由SA指定。

ESP(Encapsulating security payload):加密。ESP为ip数据包提供完整性检查、认证和加密。

(2)Linux内核的IPSEC实现

在linux内核中的IPSEC实现即是xfrm这个框架(读作transform转换,表示内核协议栈收到的IPsec报文需要经过转换才能还原为原始报文),很多解决方案如StrongSwan、OpenSwan都使用XFRM框架进行报文接收发送,关于xfrm的代码主要在net/xfrm以及net/ipv4下。

// 以下是/net/xfrm下的代码的大概功能
xfrm_state.c 状态管理
xfrm_policy.c xfrm策略管理
xfrm_algo.c 算法管理
xfrm_hash.c 哈希计算函数
xfrm_input.c 安全路径(sec_path)处理, 用于处理进入的ipsec包
xfrm_user.c netlink接口的SA和SP(安全策略)管理

其中xfrm_user.c中的代码允许我们向内核发送netlink消息来调用相关handler实现对SA和SP的配置,其中涉及处理函数如下。

xfrm_dispatch[XFRM_NR_MSGTYPES] = {
[XFRM_MSG_NEWSA - XFRM_MSG_BASE] = { .doit = xfrm_add_sa },
[XFRM_MSG_DELSA - XFRM_MSG_BASE] = { .doit = xfrm_del_sa },
[XFRM_MSG_GETSA - XFRM_MSG_BASE] = { .doit = xfrm_get_sa,
.dump = xfrm_dump_sa,
.dOne= xfrm_dump_sa_done },
[XFRM_MSG_NEWPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_add_policy },
[XFRM_MSG_DELPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_get_policy },
[XFRM_MSG_GETPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_get_policy,
.dump = xfrm_dump_policy,
.dOne= xfrm_dump_policy_done },
[XFRM_MSG_ALLOCSPI - XFRM_MSG_BASE] = { .doit = xfrm_alloc_userspi },
[XFRM_MSG_ACQUIRE - XFRM_MSG_BASE] = { .doit = xfrm_add_acquire },
[XFRM_MSG_EXPIRE - XFRM_MSG_BASE] = { .doit = xfrm_add_sa_expire },
[XFRM_MSG_UPDPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_add_policy },
[XFRM_MSG_UPDSA - XFRM_MSG_BASE] = { .doit = xfrm_add_sa },
[XFRM_MSG_POLEXPIRE - XFRM_MSG_BASE] = { .doit = xfrm_add_pol_expire},
[XFRM_MSG_FLUSHSA - XFRM_MSG_BASE] = { .doit = xfrm_flush_sa },
[XFRM_MSG_FLUSHPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_flush_policy },
[XFRM_MSG_NEWAE - XFRM_MSG_BASE] = { .doit = xfrm_new_ae },
[XFRM_MSG_GETAE - XFRM_MSG_BASE] = { .doit = xfrm_get_ae },
[XFRM_MSG_MIGRATE - XFRM_MSG_BASE] = { .doit = xfrm_do_migrate },
[XFRM_MSG_GETSADINFO - XFRM_MSG_BASE] = { .doit = xfrm_get_sadinfo },
[XFRM_MSG_NEWSPDINFO - XFRM_MSG_BASE] = { .doit = xfrm_set_spdinfo,
.nla_pol = xfrma_spd_policy,
.nla_max = XFRMA_SPD_MAX },
[XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = { .doit = xfrm_get_spdinfo },
};

下面简单介绍一下其中几个函数的功能:


  • xfrm_add_sa:创建一个新的SA,并可以指定相关attr,在内核中,是用一个xfrm_state结构来表示一个SA的。


  • xfrm_del_sa:删除一个SA,也即删除一个指定的xfrm_state。


  • xfrm_new_ae:根据传入参数,更新指定xfrm_state结构中的内容。


  • xfrm_get_ae:根据传入参数,查询指定xfrm_state结构中的内容(包括attr)。



(3)Netlink介绍

简介:Netlink是linux提供的用于内核和用户态进程之间的通信方式,也能用于用户空间的两个进程通信(很少)。一般来说用户空间和内核空间的通信方式有三种:/proc、ioctl、Netlink。而前两种都是单向的,但是Netlink可以实现双工通信。

Netlink 是一种特殊的 socket,它是 Linux 所特有的,类似于 BSD 中的AF_ROUTE 但又远比它的功能强大。目前在Linux 内核中使用netlink 进行应用与内核通信的应用很多; 包括:路由 daemon(NETLINK_ROUTE),用户态 socket 协议(NETLINK_USERSOCK),防火墙(NETLINK_FIREWALL),netfilter 子系统(NETLINK_NETFILTER),内核事件向用户态通知(NETLINK_KOBJECT_UEVENT), 通用 netlink(NETLINK_GENERIC)等。

通信方式:基于netlink的内核通信与socket的通信方式一致,都是通过sendto(),recvfrom(); sendmsg(), recvmsg()的用户态API。内核便可以调用 xfrm_netlink_rcv() 来接收和处理。

2.漏洞分析


(1)xfrm_state结构体生成

xfrm_state结构中比较重要的变量:


  • struct xfrm_id id用于标识一个SA身份,包含daddr、spi、proto三个参数;

    struct xfrm_id {
    xfrm_address_t daddr;
    __be32 spi;
    __u8 proto;
    };


  • xfrm_replay_state_esn结构体(replay_esnpreplay_esn),bmp是一个边长的内存区域,是一块bitmap,用于标识数据包的seq是否被重放过,其中bmp_len表示变长结构体的大小,replay_window用于seq索引的模数,即索引的范围,此结构体在创建xfrm_state结构体时根据用户输入参数动态被创建,而程序漏洞存在于这个结构体的读写过程中。bmp_len决定整个结构体的具体大小. 而replay_window则决定了bmp数组的索引范围

    struct xfrm_replay_state_esn {
    unsigned int bmp_len;
    __u32 oseq;
    __u32 seq;
    __u32 oseq_hi;
    __u32 seq_hi;
    __u32 replay_window;
    __u32 bmp[0];
    };


xfrm_add_sa函数

static int xfrm_add_sa(struct sk_buff *skb, struct nlmsghdr *nlh, struct nlattr **attrs)
(1)verify_newsa_info(p, attrs); // 协议及参数检查
verify_replay(p, attrs) // xfrm_replay_state_esn结构参数检查
/*检查:[1]bmp_len是否超过最大值,最大值定义为4096/4/8。[2]检查参数长度定义是否正确。[3]是否为IPPROTO_ESP或者IPPROTO_AH协议。*/
(2)xfrm_state_construct(net, p, attrs, &err) // 根据用户输入对结构体进行构造
- struct xfrm_state *x=xfrm_state_alloc(net); // 调用kzalloc函数新建xfrm_state结构
- copy_from_user_state(x, p); // 拷贝用户数据
- xfrm_alloc_replay_state_esn(); // 申请xfrm_replay_state_esn结构体
/*通过kzalloc函数分别申请了两块同样大小的内存(replay_esn和preplay_esn),大小为sizeof(*replay_esn) + replay_esn->bmp_len * sizeof(__u32),并将用户数据中attr[XFRMA_REPLAY_ESN_VAL]内容复制过去。*/
- xfrm_init_replay(); // 检查滑动窗口replay_window大小及flag,确定检测使用的函数
/*对上述申请的结构体数据进行检查,replay_window不大于定义的bmp_len大小,并对x->repl进行初始化,该成员是一个函数虚表,作用是在收到AH或ESP协议数据包时进行数据重放检查。*/
x->repl = &xfrm_replay_esn;

(2)xfrm_replay_state_esn结构体更新

目的:修改replay_esn成员,也即xfrm_alloc_replay_state_esn申请的第1个内存块。

xfrm_new_ae函数

static int xfrm_new_ae(struct sk_buff *skb, struct nlmsghdr *nlh, struct nlattr **attrs)
(1)xfrm_state_lookup() //循环查找hash表,得到xfrm_state结构体
(2)xfrm_replay_verify_len() //对用户输入的attr[XFRMA_REPLAY_ESN_VAL]参数进行检查,也就是修改后的replay_esn 成员内容。
/*主要检查了修改部分的bmp_len长度,该检查是因为replay_esn成员内存是直接进行复制的,不再二次分配。但缺少了对replay_window变量的检测,导致引用replay_window变量进行bitmap读写时造成的数组越界问题。*/
(3)xfrm_update_ae_params() //利用memcpy进行成员内容修改。

(3)数组越界写定位

// 对xfrm模块代码中,replay_window关键字的查找,可以发现主要对这个关键字的操作位于xfrm_replay_advance_esn和xfrm_replay_check_esn函数中
static const struct xfrm_replay xfrm_replay_esn = {
.advance = xfrm_replay_advance_esn, // 越界读写
.check = xfrm_replay_check_esn, // 越界读写
.recheck = xfrm_replay_recheck_esn,
.notify = xfrm_replay_notify_esn,
.overflow = xfrm_replay_overflow_esn,
};

xfrm_replay_esn结构体在xfrm_init_replay函数中被使用,并赋值给x->replx->replxfrm_input函数调用。xfrm_input函数之前被xfrm4_rcv_spi <= xfrm4_rcv <= xfrm4_ah_rcv ,最终追溯到AH协议的内核协议栈中。

static const struct net_protocol ah4_protocol = {
.handler = xfrm4_ah_rcv,
.err_handler = xfrm4_ah_err,
.no_policy = 1,
.netns_ok = 1,
};

可见,通过发送AH数据包可以触发越界读写。

xfrm_input函数,xfrm_replay_advance_esn函数。

int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
(1)x = xfrm_state_lookup(net, mark, daddr, spi, nexthdr, family);//利用AH数据包数据找到对应的SA-xfrm_state
(2)x->repl->check(x, skb, seq) //x->repl 在 xfrm_init_replay赋值,可调用xfrm_replay_check_esn。调用xfrm_replay_check_esn进行检查,再调用xfrm_replay_recheck_esn再次检查
*replay_esn = x->replay_esn; // 找到的还是x->replay_esn成员
if (replay_esn->bmp[nr] & (1U < (3)x->repl->advance(x, seq); //调用xfrm_replay_advance_esn
replay_esn->bmp[nr] &= ~(1U < nr = (replay_esn->replay_window - 1) >> 5;
for (i = 0; i <= nr; i++)
replay_esn->bmp[i] = 0; // else 对从bmp[0]到bmp[(replay_esn->replay_window - 1) >> 5]块内存均置零
replay_esn->bmp[nr] |= (1U <

因此,通过用户态空间发送一个AH数据包将导致,一个bit的内存写,或者一段空间的置零



二、漏洞触发与利用

1.Netlink内核协议解析

xfrm数据包的协议格式

/* ========================================================================
* Netlink Messages and Attributes Interface (As Seen On TV)
* ------------------------------------------------------------------------
* Messages Interface
* ------------------------------------------------------------------------
*
* Message Format:
* <--- nlmsg_total_size(payload) --->
* <-- nlmsg_msg_size(payload) ->
* +----------+- - -+-------------+- - -+-------- - -
* | nlmsghdr | Pad | Payload | Pad | nlmsghdr
* +----------+- - -+-------------+- - -+-------- - -
* nlmsg_data(nlh)---^ ^
* nlmsg_next(nlh)-----------------------+
*
* Payload Format:
* <---------------------- nlmsg_len(nlh) --------------------->
* <------ hdrlen ------> <- nlmsg_attrlen(nlh, hdrlen) ->
* +----------------------+- - -+--------------------------------+
* | Family Header | Pad | Attributes |
* +----------------------+- - -+--------------------------------+
* nlmsg_attrdata(nlh, hdrlen)---^
*
* Data Structures:
* struct nlmsghdr netlink message header
* ------------------------------------------------------------------------
* Attributes Interface
* ------------------------------------------------------------------------
*
* Attribute Format:
* <------- nla_total_size(payload) ------->
* <---- nla_attr_size(payload) ----->
* +----------+- - -+- - - - - - - - - +- - -+-------- - -
* | Header | Pad | Payload | Pad | Header
* +----------+- - -+- - - - - - - - - +- - -+-------- - -
* <- nla_len(nla) -> ^
* nla_data(nla)----^ |
* nla_next(nla)-----------------------------&#39;
*
* Data Structures:
* struct nlattr netlink attribute header

从上图可以看出,发送到内核的数据需要如下形式nlmsghdr + Family Header + n * (nla + data)。

首先从xfrm_netlink_rcv函数中调用netlink_rcv_skb函数,会检查nlmsg_typenlmsg_len范围,并交由cb函数处理,其赋值为xfrm_user_rcv_msg

int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *,
struct nlmsghdr *))
{
struct nlmsghdr *nlh;
int err;
while (skb->len >= nlmsg_total_size(0)) {
int msglen;
nlh = nlmsg_hdr(skb);
err = 0;
if (nlh->nlmsg_len len nlmsg_len)
return 0;
/* Only requests are handled by the kernel */
if (!(nlh->nlmsg_flags & NLM_F_REQUEST))
goto ack;
/* Skip control messages */
if (nlh->nlmsg_type goto ack;
err = cb(skb, nlh);
if (err == -EINTR)
goto skip;
ack:
if (nlh->nlmsg_flags & NLM_F_ACK || err)
netlink_ack(skb, nlh, err);
skip:
msglen = NLMSG_ALIGN(nlh->nlmsg_len);
if (msglen > skb->len)
msglen = skb->len;
skb_pull(skb, msglen);
}
return 0;
}

在xfrm_user_rcv_msg函数中,会根据nlmsg_typexfrm_dispatch中查找对应要调用的函数,并在[2]处检查对应需要的权限,而在[3]处会根据nla中参数类型,来初始化一个** attr,作为用户输入参数的索引。最终调用link->doit去执行。

static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
struct net *net = sock_net(skb->sk);
struct nlattr *attrs[XFRMA_MAX+1];
const struct xfrm_link *link;
int type, err;
#ifdef CONFIG_COMPAT
if (in_compat_syscall())
return -EOPNOTSUPP;
#endif
type = nlh->nlmsg_type;
if (type > XFRM_MSG_MAX)
return -EINVAL;
type -= XFRM_MSG_BASE;
[1] link = &xfrm_dispatch[type];
/* All operations require privileges, even GET */
[2] if (!netlink_net_capable(skb, CAP_NET_ADMIN)) //检查进程权限
return -EPERM;
if ((type == (XFRM_MSG_GETSA - XFRM_MSG_BASE) ||
type == (XFRM_MSG_GETPOLICY - XFRM_MSG_BASE)) &&
(nlh->nlmsg_flags & NLM_F_DUMP)) {
if (link->dump == NULL)
return -EINVAL;
{
struct netlink_dump_control c = {
.dump = link->dump,
.dOne= link->done,
};
return netlink_dump_start(net->xfrm.nlsk, skb, nlh, &c);
}
}
[3] err = nlmsg_parse(nlh, xfrm_msg_min[type], attrs,
link->nla_max ? : XFRMA_MAX,
link->nla_pol ? : xfrma_policy);
if (err <0)
return err;
if (link->doit == NULL)
return -EINVAL;
return link->doit(skb, nlh, attrs);
}

xfrm_dispatch可见,我们所需的XFRM_MSG_NEWSAXFRM_MSG_NEWAE,仅需将nlmsg_type设置为相应值即可。 xfrm_dispatch见上述Linux内核的IPSEC实现。

Family Header需要到对应的处理函数中找,以xfrm_add_sa为例,其调用nlmsg_data函数的赋值变量类型为xfrm_usresa_info,即为Family Header

struct xfrm_usersa_info *p = nlmsg_data(nlh);

2.利用思路


(1)权限限制

权限限制:即是在上文中提到的netlink_net_capable(skb, CAP_NET_ADMIN)检查,所需为CAP_NET_ADMIN权限。但在Linux操作系统中存在命名空间这样的权限隔离机制,在每一个NET沙箱中,非ROOT进程可以具有CAP_NET_ADMIN权限。查看命名空间开启的方式为cat /boot/config* | grep CONFIG_USER_NS,若为「y」,则启用了命名空间。

命名空间namespace 是 Linux 内核用来隔离内核资源的方式(为虚拟化而生)。通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。 Linux namespaces 是对全局系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源,改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程,对其他 namespace 中的进程没有影响。

绕过限制的两种方法:一是使用setcap命令为EXP赋予权限,即执行sudo setcap cap_net_raw,cap_net_admin=eip ./exp。二是仿照CVE-2017-7308中设置namespace sandbox,但注意此时无法利用getuid来判断是否为root用户。

(2)利用方法



  • 绕过权限限制:在ubuntu,Fedora等发行版,User namespace是默认开启的。非特权用户可以创建用户命名空间、网络命名空间。在命名空间内部,我们就可以触发漏洞了。


  • 越界写:xfrm_replay_advance_esn()函数对bitmap操作是清除[last seq, current seq)的bit;设置bmp[current seq] = 1。可以指定好spi、seq等参数(内核是根据spi的哈希值以及ip地址来确定SA的),并让内核来处理我们发出的ESP数据包,多次进行这个操作即可达到对越界任意长度进行写入任意值。


  • 越界读:本例不需要。构造两个相邻的replay_state结构(首先分配足够多的同样大小的replay_state结构把堆上原来的坑填满,之后便可大概率保证连续分配的replay_state结构是相邻的),使用越界写,改大下一个replay_state_esn的结构中的bmp_len,之后利用下一个bitmap结构进行越界读。如下所示,为被改掉bmp_len的bitmap结构:

    pwndbg> p *(struct xfrm_repaly_state_esn*)(0xffff9a6a28262b00+0x200)
    $1 = {
    bmp_len = 1024,
    oseq = 0,
    seq = 0,
    oseq_hi = 0,
    replay_window = 64,
    bmp = 0xffff9a6a8262d18
    }


  • 绕过kASLR:利用xfrm_del_sa函数删除没用的xfrm_state,再向内核喷射很多struct file结构体填在这些坑里,最后利用越界读能力,可以泄露一些内核里的指针来算出内核的加载地址和bitmap的位置。


  • 代码执行:构造内核rop,先在bitmap中伪造一个file_operations结构;再利用越界写改写刚在内核中喷射的struct file结构体的file_operations指针,使其指向合适的ROPgadget;调用llseek函数(实际上已经是rop gadget);多次改写file_operations结构中的llseek函数指针来实现多次执行ROPgadget实现提权。 ???方法存疑,还能多次改seek指针???



(3)注意

xfrm_replay_advance_esn()中,先清0,后续的还有一个置位操作,所以构造好 replay_window seq seq_no三个值,将cred结构体清0之后,再将cred->usage值还原(调试这个值为2)。

利用伪代码

/* 用于向内核发送信息, 然后生成/更新xfrm_state结构体的套接字 */
xfrm_state_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_XFRM);
/* 用于接收IPPROTO_AH信息, 触发xfrm_input的接收套接字 */
recvfd = socket(AF_INET, SOCK_RAW, IPPROTO_AH);
/* 用于发送自定义数据包的发送套接字 */
sendfd = socket(AF_INET, SOCK_RAW, IPPROTO_AH);
alloc_xfrm_state(...);
update_esn(...);
trigger_oob(...);

内存布局

xfrm_alloc_replay_state_esn函数中, 连续申请了两个esn 也就是说, 在alloc_xfrm_state函数之后, 内存布局是这样的

内存低地址 | xfrm_replay_state_esn 结构0 | xfrm_replay_state_esn 结构1 | cred 结构 | 高地址

在测试系统中, cred对象的大小为0xa8,对齐到kmalloc-192的slab块中,也即每个对象大小为0xc0。那么cred结构的相对于esn->bmp偏移为0xc0-0x18+0xc0,对应32位数据的数组索引,cred->usage的nr值为 (0xc0-0x18+0xc0)/4。 查看POC点这里

(4)数据包构造

本漏洞属于一个利用条件比较宽松的漏洞。首先,xfrm_replay_state_esn是一个变长的数据结构,而其长度可以由用户输入的bmp_len来控制,并由kzalloc申请bmp_len *4 + 0x18大小的内存块。其次,越界读写可以每次写1bit大小的数据,同时也可以将(replay_windows -1)>>5比特大小的内存块清空。

并且cred结构体的申请是通过prepare_creds中的new = kmem_cache_alloc(cred_jar, GFP_KERNEL);得到的,但在调试中发现,本内核的cred_jarkmalloc-192

技术分享图片

根据内核分配使用的slub+伙伴算法可以知道,对于同一个kmem_cache分配出来的内存块有一定概率是相邻。因此一种很取巧的思路,就是将xfrm_replay_state_esn结构体设置为192(0xc0)以内,以利用kmalloc-192进行分配,并利用fork新建大量进程,使申请大量cred,这样喷射之后有很大概率越界读写漏洞存在的位置之后就是一个cred结构体,这样利用之前提到过的置零一段内存的操作就可以将cred结构体中的部分成员(uid gid等)置零,从而对该进程提权,并通过反弹shell就可以得到一个root权限的shell

因此对于数据包构造主要根据上述思路。

xfrm_add_sa

在触发xfrm_add_sa函数的数据包中,需要满足128 。并且需要参考之前源码分析中的各项flag及参数检查。

xfrm_new_ae

在触发xfrm_new_ae函数的数据包中,需要对seq_hiseqreplay_window进行设定,replay_window即将要置零的长度大小,由于连续申请了两块大小相同的结构体,而置零的时候是从第一次申请的位置操作的,有可能出现二者相邻,因此需要将replay_window设置稍大一些。而seq_hiseq两个数据需要结合之后发送的ah数据包中的seq参数,引导xfrm_replay_advance_esn走向置零bmp[0]~bmp[n]这个分支。

AH数据包

AH数据包的要求即spi需要和之前申请SAspi相同用于寻找xfrm_state,并且需要满足

diff >= replay_esn->replay_window,其中diff的数据由xfrm_replay_state_esn中的seqseq_hiAHseq共同决定。还行需在后续单字节写的位置,将cred结构体中usage置回原值。

xfrm_replay_advance_esn函数执行前后发现,相邻cred中的成员被置零。

技术分享图片

技术分享图片

参考:

p4nda—Linux xfrm模块越界读写提权漏洞分析(CVE-2017-7184)

长亭—Pwn2Own 2017 Linux 内核提权漏洞分析

cve-2017-7184 (长亭在Pwn2Own上用于提权Ubuntu的漏洞) 的分析利用

Linux内核学习:netlink的内核实现原理

linux用户空间与内核空间通信——Netlink通信机制

Netlink 基于事件的信号机制

XFRM -- IPsec协议的内核实现框架

Linux Namespace : 简介


推荐阅读
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 本文讲述了作者通过点火测试男友的性格和承受能力,以考验婚姻问题。作者故意不安慰男友并再次点火,观察他的反应。这个行为是善意的玩人,旨在了解男友的性格和避免婚姻问题。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
author-avatar
hz--Ives
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有