pwn刷题记录
NSS-[CISCN 2023 初赛]funcanary【canary】
NSSCTF | 在线CTF平台
下载附件,直接去checksec文件,查看文件二进制保护机制再通过file命令查看文件的信息
ida64打开,首先查看main函数
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
__pid_t v3; // [rsp+Ch] [rbp-4h]
sub_1243(a1, a2, a3);
while ( 1 )
{
v3 = fork();
if ( v3 < 0 )
break;
if ( v3 )
{
wait(0LL);
}
else
{
puts("welcome");
sub_128A();
puts("have fun");
}
}
puts("fork error");
exit(0);
}
fork函数:
fork()用于创建一个子进程,我们在shell下执行一个命令其实也是通过fork()实现的,fork()是Linux下最基本的一个系统调用。fork()最大的特点就是一次调用,两次返回,两次返回主要是区分父子进程,因为fork()之后将出现两个进程,所以有两个返回值,父进程返回子进程ID,子进程返回0。
通过复制的方式创建一个进程,被创建的进程称为子进程,调用进程称为父进程,复制的子进程是从父进程fork()调用后面的语句开始执行的。
这也就是说fork()函数,将调用自己前的复制了下来,再创建了一个子进程。
子进程和父进程在开始执行时是一模一样的,但是它们的执行状态可以独立进行改变。
进程ID:
进程ID(PID)是操作系统分配给每个进程的唯一标识符。在同一时刻,每个运行中的进程都有一个唯一的PID。
当一个进程被创建时,操作系统会为其分配一个唯一的PID。PID 通常是一个正整数,系统会在进程终止后回收其PID,并可能在未来为新的进程重新分配该PID。
通过shift+f12,我们可以看到一个特殊的字符串,这或许是一个后门在main函数中,看到了
一个fork函数,和
一个sub_128A()函数
因为有fork函数,这个值得我们好好利用。f前文提到fork函数将复制出一个子进程,包括但不限于代码段、数据段、堆和栈。也就是说子进程的最初状态与父进程完全一致,如此便可以知道:子父进程的canary完全相同。当程序存在fork函数并触发canary时,__ stack_chk_fail函数只能关闭fork函数所建立的进程,不会让主进程退出,所以当存在大量调用fork函数时,我们可以利用它来一字节一字节的泄露。
查看一下sub_128A,调用了read函数,又存在地址0x28,这是经典的金丝雀
v2
的值通过 __readfsqword(0x28u)
读取,这是从线程信息块(TIB)中读取堆叠金丝雀值的标准方法。TIB 中的 0x28
偏移处存储了当前线程的堆叠金丝雀值。
在sub_128A函数有溢出(read),又存在canary的阻挡,那么我们将canary覆盖,然后将溢出的发送给后门
但是我们还有pie保护,我们还得绕过pie保护。这里利用的是部分写入的方法,这是一个利用了PIE技术缺陷的绕过方法,因为内存载入虚拟内存中是通过页的方式载入的,一般来说页的大小是4kb,就是0x1000,这就代表着程序加载中因为pie导致程序的的地址就算不断地变化他的后三位都不会改变,所以我们只需要改变一个返回地址的后三位就代表着可以将这个返回地址修改成我们的后门函数的地址。