Linux socket进程间通信及应用

0x00

linux socket网络编程大家肯定不会陌生,然而linux也可以使用socket这套接口来实现进程间通信,大概的步骤和网络通信差不多,也是可以基于C/S的,服务端创建套接字,然后绑定、侦听,客户端创建套接字,链接,然后就可以通信了。这篇文章主要是关于基本使用以及一点应用,仅仅是个人看法。

0x01 基础部分

1.socket创建

首先来看socket的创建,依然是使用socket()函数,不过第一个参数,即类型变了,具体函数如下

1
2
3
4
5
6
7
#include <sys/socket.h>
#include <sys/un.h>
....
....
unix_socket = socket(AF_UNIX, type, 0);
....
....

AF_UNIX也可以替换成AF_LOCAL

2.socket命名

关于socket进程通信,命名方式有两种,一是普通命名,二是抽象命名空间。差别在于,普通命名会根据你给的名字产生一个socket文件,然后你的通信过程socket会读取这个文件,这种方式也就决定了你编写的server必须对这个命名文件的路径有读写权限,而且客户端必须知道这个文件的路径;然而抽象命名空间的方式没有这个限制,但是需要一个全局名称,客户端根据这名称来连接。

1.普通命名

1
2
3
4
5
6
7

#define SERVER_NAME "/tmp/server_socket"

server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path,SERVER_NAME);
server_len = sizeof(struct sockaddr_un);
client_len = server_len;

服务端必须对/tmp/server_socket有读写权限,且客户端知道这个路径。

2.抽象命名空间

1
2
3
4
5
6
#define SERVER_NAME "@server_socket"

server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, SERVER_NAME);
server_addr.sun_path[0]=0;
server_len = strlen(SERVER_NAME) + offsetof(struct sockaddr_un, sun_path);

这种方式对地址结构成员sun_path数组赋值的时候,必须把第一个字节置0。

2.socket绑定

这个是对于服务端来说的,也是使用bind函数

1
2
3
4
5
    ....
....
bind(server_sockfd, (struct sockaddr *)&server_addr, server_len);
....
....
2.socket侦听

使用listen函数

1
2
3
4
5
    ....
....
listen(server_sockfd, 5);
....
....
2.socket连接

下面这个部分,作为服务端,要等待服务端的连接,使用accept函数接收连接然后做处理,作为服务端使用connect函数连接,然后通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
while(1){
//accept client connect
client_len = sizeof(client_addr);
client_sockfd = accept(server_sockfd,(struct sockaddr*)&client_addr, &client_len);

//每次读一个字节并显示
read(client_sockfd, &ch, 1);
printf("read from client %d: %c",client_sockfd,ch);
ch ++;
write(client_sockfd, &ch, 1);
//关闭连接
close(client_sockfd);
}
1
2
3
4
5
6
7
8
9
10
11
//communicate with server socket  
while(1){

printf("set send content:");
//从键盘读取一个自己发送给服务端
scanf("%c",&ch);
write(sockfd, &ch, 1);
printf("send to server:%c \n",ch);
read(sockfd, &ch, 1);
printf("read from server: %c\n", ch);
}

0x02 示例代码

这里直接使用了参考blog中的代码

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
//server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>

#define SERVER_NAME "@server_socket"
//#define SERVER_NAME "/tmp/server_socket"

int main(){
int server_sockfd, client_sockfd;
socklen_t server_len, client_len;
struct sockaddr_un server_addr;
struct sockaddr_un client_addr;
char ch;
int nread;

//delete the old server socket
unlink("server_socket");
server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, SERVER_NAME);
server_addr.sun_path[0]=0;//普通命名不应该有这句
server_len = strlen(SERVER_NAME) + offsetof(struct sockaddr_un, sun_path);
bind(server_sockfd, (struct sockaddr *)&server_addr, server_len);

//listen the server
listen(server_sockfd, 5);
client_sockfd = -1;
client_len = sizeof(client_addr);
while(1){
printf("server waiting...\n");
if(client_sockfd == -1){
client_sockfd = accept(server_sockfd,(struct sockaddr*)&client_addr, &client_len);
}

nread = read(client_sockfd, &ch, 1);
if(nread == 0){
printf("client %d disconnected\n",client_sockfd);
client_sockfd = -1;
}
else{
printf("read from client %d: %c\n",client_sockfd,ch);
ch ++;
write(client_sockfd, &ch, 1);
}
usleep(100);
}
return 0;
}

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
//client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>

#define SERVER_NAME "@server_socket"
//#define SERVER_NAME "/tmp/server_socket"


int main()
{
int sockfd;
socklen_t len;
struct sockaddr_un address;
int result;
char ch = 'A';

//create socket
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

address.sun_family = AF_UNIX;
strcpy(address.sun_path, SERVER_NAME);
address.sun_path[0]=0;

len = strlen(SERVER_NAME) + offsetof(struct sockaddr_un, sun_path);

result = connect(sockfd, (struct sockaddr*)&address, len);
if(result == -1)
{
perror("opps:client1");
exit(1);
}
//communicate with server socket
while(1)
{
printf("set send content:");
scanf("%c",&ch);
write(sockfd, &ch, 1);
printf("send to server:%c \n",ch);
read(sockfd, &ch, 1);
printf("read from server: %c\n", ch);

}
exit(0);

}

效果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ./server 
server waiting...
read from client 4: a
server waiting...
read from client 4:

server waiting...
read from client 4: Z
server waiting...
read from client 4:

server waiting...


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ ./client 
set send content:a
send to server:a
read from server: b
set send content:send to server:

read from server:

set send content:Z
send to server:Z
read from server: [
set send content:send to server:

read from server:
set send content:

服务端把客户端发来的字符加一后送回了。

0x03 关于应用

既然是进程间的通信,那么父子进程之间通信也是可以使用这个东西的。

  1. 那这个东西就可以用来做一个wapper,赛棍们都懂的 2333333
  2. 或者可以用来做简单的fuzz,子进程里启动目标程序,然后交互数据都是通过socket传递,那么就可以把一些输入变异之后再送过去吧,而且异常捕获也挺好做,waitpid就可以。不过用来做fuzzer的话,效率不知道怎么样…

0x04 代码

首先创建套接字,然后fork产生子进程

1
2
3
4
5
6
7
8
9
int pid;
unlink(SOCK_NAME);
int s = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
memcpy(addr.sun_path, SOCK_PATH, strlen(SOCK_PATH));
addr.sun_family = AF_UNIX;
bind(s, (struct sockaddr *)&addr, strlen(addr.sun_path) + sizeof(addr.sun_family));
listen(s, 5);
pid = fork();

父进程与子进程启动的目标程序交互,可以劫持输入输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct sockaddr_un child;
int clen = sizeof(child);
int cs = accept(s, (struct sockaddr *) &child, &clen);

if (cs == -1) {
fprintf(stderr, "server socket fail\n");
exit(0);
}

n = read(cs, buf, BUFSIZE);
write(1, buf, n);

while (1) {
// 在这里可以劫持输入
n = read(0, buf, BUFSIZE);

write(cs, buf, n);
bzero(buf, BUFSIZE);

// 劫持输出
n = read(cs, buf, BUFSIZE);
write(1, buf, n);
}

子进程里启动目标程序

1
2
3
4
5
6
7
8
9
10
11
12
13
int s = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
bzero(addr.sun_path, sizeof(addr.sun_path));
memcpy(addr.sun_path, SOCK_PATH, strlen(SOCK_PATH));
addr.sun_family = AF_UNIX;
connect(s, (struct sockaddr *)&addr, strlen(addr.sun_path) + sizeof(addr.sun_family));
if (s == -1) {
fprintf(stderr, "cli socket fail\n");
exit(0);
}
dup2(s, 0);
dup2(s, 1);
execve("./target", NULL, NULL);

0x05 参考

manpage
linux socket进程间通信