Uaf
虚函数,一旦一个类有虚函数,编译器会为这个类建立一张vtable。子类继承父类vtable中所有项,当子类有同名函数时,修改vtable同名函数地址,改为指向子类的函数地址,子类有新的虚函数时,在vtable中添加。记住,私有函数无法继承,但如果私有函数是虚函数,vtable中会有相应的函数地址,所有子类可以通过手段得到父类的虚私有函数。
vptr每个对象都会有一个,而vptable是每个类有一个vptr指向vtable,一个类中就算有多个虚函数,也只有一个vptr,做多重继承的时候,继承了多个父类,就会有多个vptr
虚函数表的结构:它是一个函数指针表,每一个表项都指向一个函数。任何一个包含至少一个虚函数的类都会有这样一张表。需要注意的是vtable只包含虚函数的指针,没有函数体。实现上是一个函数指针的数组。虚函数表既有继承性又有多态性。每个派生类的vtable继承了它各个基类的vtable,如果基类vtable中包含某一项,则其派生类的vtable中也将包含同样的一项,但是两项的值可能不同。如果派生类覆写(override)了该项对应的虚函数,则派生类vtable的该项指向覆写后的虚函数,没有覆写的话,则沿用基类的值。
每一个类只有唯一的一个vtable,不是每个对象都有一个vtable,恰恰是每个同一个类的对象都有一个指针,这个指针指向该类的vtable(当然,前提是这个类包含虚函数).那么,每个对象只额外增加了一个指针的大小,一般说来是4字节。
在类对象的内存布局中,首先是该类的vtable指针,然后才是对象数据。 在通过对象指针调用一个虚函数时,编译器生成的代码将先获取对象类的vtable指针,然后调用vtable中对应的项。对于通过对象指针调用的情况,在编译期间无法确定指针指向的是基类对象还是派生类对象,或者是哪个派生类的对象。但是在运行期间执行到调用语句时,这一点已经确定,编译后的调用代码能够根据具体对象获取正确的vtable,调用正确的虚函数,从而实现多态性。
全文地址请点击:https://blog.csdn.net/qq_20307987/article/details/51511230?utm_source=copy
根据源代码,可知:
1 |
|
思路: 执行步骤,3-2-2-1,第二次调用2的时候,将introduce
的地址覆盖为give_shell
的地址。
操作:
1 | python -c "print '\x68\x15\x40\x00\x00\x00\x00\x00'" > /tmp/uaf |
memcpy
1 | // compiled with : gcc -o memcpy memcpy.c -m32 -lm |
要点:
fast_memcpy
函数中用于内存复制的两个指令movdqa
和movntps
他们的操作数如果是内存地址的话,那么这个地址必须是16字节对齐的,否则会产生一般保护性异常导致程序退出。malloc
在分配内存时它实际上还会多分配4字节用于存储堆块信息,所以如果分配a字节实际上分配的是a+4
字节。另外32位系统上该函数分配的内存是以8字节对齐的。
解题思路:
每次分配的内存地址能够被16整除就可以了(实际上由于malloc
函数分配的内存8字节对齐,只要内存大小除以16的余数大于9就可以了)。
1 | #-*- coding:utf8 |
asm
1 |
|
首先查看stub的内容:
1 | qianfa@qianfa:~/Desktop/pwnable/asm$ disasm 4831c04831db4831c94831d24831f64831ff4831ed4d31c04d31c94d31d24d31db4d31e44d31ed4d31f64d31ff |
大体上就是将部分寄存器的值清0。
直接操作:
1 | #-*- coding:utf8 |
unlink
1 | #include <stdio.h> |
gets(A->buf) 很明显存在堆溢出,所以可以任意地址写,覆盖返回main函数地址为shell地址。
1 | mov ecx, [ebp+var_4] |
从汇编我们可以看到,只需要如下几个步骤即可:
1 | esp = heap_A_address + 8 |
同时,我们知道:
1 | A: stack_A_address = &A = ebp - 0x14 |
任意地址写代码如下:
1 | FD->bk = BK; |
相应的有两种方法:
方法一:
FD->bk = BK,则payload 如下:
1 | fd: stack_A_address + 12 |
方法二:
BK->fd = FD,则payload如下:
1 | fd: heap_A_address + 12 |
所以payload如下:
1 | from pwn import * |
brainfuck
1 | int __cdecl do_brainfuck(char a1) |
该函数存在任意地址写,范围不超过1024左右个字节,而p指针不远处就是got表,所以可以通过修改putchar的地址为”main”函数地址,修改”memset”地址为”gets”函数地址,修改”fgets”地址为”system”地址。
需要注意的是,首先需要leak出libc的地址。
1 | from pwn import * |
simple login
1 | _BOOL4 __cdecl auth(int decode_length) |
auth函数存在栈溢出,最多溢出4个字节,只能覆盖ebp,input在bss中。
leave => mov esb ebp; pop ebp;
ret => pop rip; jump rip;
通过修改ebp指向bss段,这样esp将指向bss段,从而可以控制rip。
1 | from pwn import * |
otp
通过ulimit限制了进程可以创建文件的最大值,只要限制为0,那么最后的密码一定为空,于是空密码通过,在写脚本时还需要注意的点就是把错误输出重定向到标准输出中。
1 | ulimit -f 0 |
1 | import subprocess |
flag: Darn… I always forget to check the return value of fclose() :(
dragon
1 | XXXAttack: |
由于free(dragon)之后,如果赢得话,将立马malloc(0x10),输入名字,这时候dragon_info将可控,然后执行*dragon_info(dragon_info)。
由以上伪代码,我们可以看出,只要能够击败龙,就可以在输入name时,输入call system的地址,即可getshell。
记录怪物血量的变量是BYTE
类型的,也就是unsigned char
类型。与它比较的0默认情况是signed int
。
1 | while ( *((_BYTE *)dragon_info + 8) > 0 ); // 这里的0 是signed int类型 |
在c语言中不同类型的变量做比较会进行隐式类型转换,短长度类型会向较长长度类型转换,长度一致符号不同则向无符号转换,整数会向float转换,float会向double转换。
那么这里的比较,BYTE
就会向signed int
转换。如果此时怪物血量为128
,转换为二进制就是10000000
,那么进行符号扩展时就变成了0xffffff80
,而这个数其符号位为1
所以是个负数,那么函数就会跳出循环并返回1
,也就是取得胜利。
综上,payload如下:
1 | from pwn import * |