IPC(Inter Process Communication)란?

2026. 5. 28. 18:26·CS

 최근 임베디드 시스템을 분석하던 중 IPC를 이용해 다양한 서비스를 처리하는 바이너리를 분석하게 되었다. IPC라는 개념은 운영체제 원론서에서 추상적으로 공부한 적은 있었지만 실제 임베디드 바이너리에서 socket, connect, send, recv, pthread_create 같은 API와 함께 등장하니 전체 구조를 파악하는 데 어려움이 있었다.

 

따라서 이번 글에서는 임베디드 바이너리 리버싱 관점에서 IPC의 기본 개념을 정리하고 실제 구현에서 자주 등장하는 Socket API의 동작 방식을 함께 살펴보려고 한다. 

1. IPC(Inter Process Communication)란?

 IPC(Inter Process Communication)란 이름 그대로 프로세스간의 통신 방법을 말한다. 리눅스 시스템에서는 프로세스는 기본적으로 서로 독립된 가상 주소 공간을 가지기 때문에 한 프로세스가 다른 프로세스의 변수나 버퍼에 직접 접근해서 데이터를 읽거나 쓸 수 없다.

IPC 그림

 

 이러한 구조는 프로세스 간의 격리를 통해 시스템의 안정성과 보안을 높여준다. 하지만 실제 시스템에서는 여러 프로세스가 서로 협력해야 하는 경우가 많다. 이때 각 프로세스가 독립된 메모리 공간을 유지하면서도 필요한 데이터를 주고받을 수 있도록 커널이 제공하는 통신 수단이 IPC이다.

 

2. Pipe와 FIFO

 Pipe는 한 프로세스가 데이터를 쓰면 다른 프로세스가 이를 읽을 수 있도록 커널이 제공하는 단방향 통신 통로이다. pipe를 통해 전달되는 데이터가 일반 파일처럼 디스크에 저장되는 것이 아니라 커널 내부 버퍼를 통해 전달된다.

 

대표적인 예시는 쉘에서 사용하는 | 연산자이다.

cat log.txt | grep error

위 명령은 cat의 출력이 pipe를 통해 grep의 입력으로 전달되는 구조이다.

Pipe의 구조

Pipe는 pipe() API를 통해 생성된다. pipe()를 호출하면 두 개의 파일 디스크립터가 만들어진다. 한쪽은 읽기용 다른 한쪽은 쓰기용이다. 

fd[0] = read end 
fd[1] = write end

 

 

 다만 여기서 중요한 점은 파일 디스크립터 번호 자체는 해당 프로세스 안에서만 의미가 있다는 것이다. 어떤 프로세스에서 fd[0]이 3번이라고 해서 다른 독립적인 프로세스의 3번 fd가 같은 pipe를 가리키는 것은 아니다. 그럼에도 Pipe가 프로세스 간 통신에 사용될 수 있는 이유는 fork() 때문이다. fork()를 통해 자식 프로세스가 생성되면 부모 프로세스의 파일 디스크립터가 자식에게 상속된다. 따라서 부모와 자식은 서로 다른 fd 테이블을 가지지만 그 fd들이 같은 커널 pipe 객체를 참조하게 된다.

 

Named Pipe/ FIFO

 

 반면 FIFO는 Named Pipe라고도 하며 이름을 가진 pipe이다. Pipe와 달리 파일 시스템에 경로가 존재하기 때문에 서로 부모-자식 관계가 없는 프로세스도 같은 FIFO 경로를 열어 통신할 수 있다. FIFO는 mkfifo() API를 통해 생성하고 이후에는 일반 파일처럼 open(), read(), write()를 사용한다.

 

3. Message Queue

Message Queue는 이름 그대로 메시지를 큐에 넣고 다른 프로세스가 이를 꺼내가는 방식의 IPC이다. Kerenel 익스플로잇을 할 때 가장 많이 쓰는 API 중 하나기도 하다. 

메시지 큐

message queue는 다음과 같이 msgget을 통해 생성이 가능하다.

#include <sys/msg.h>

int msgget(key_t key, int msgflg);

 

그리고 msgsnd로 송신, msgrcv로 수신, msgctl로 메시지 큐를 제어 또는 삭제한다. 

 

System V Message Queue에서 메시지는 보통 다음과 같은 형태를 가진다.

struct msgbuf {
    long mtype;
    char mtext[...];
};

 

여기서 mtype은 메시지의 종류를 나타내고 mtext에는 실제 데이터가 들어간다. mtype은 커널이 의미를 해석하는 값이 아니라 프로그램이 메시지를 구분하기 위해 정의하는 타입 값이다. 커널은 mtype을 이용해 특정 타입의 메시지를 선택적으로 수신할 수 있게 해줄 뿐이고 mtype이 실제로 어떤 요청을 의미하는지는 해당 프로그램의 구현에 따라 결정된다.

 

4. Shared Memory

 Shared Memory는 여러 프로세스가 같은 메모리 영역을 공유하도록 만드는 IPC 방식이다. Shared Memory는 특정 메모리 영역을 여러 프로세스의 가상 주소 공간에 매핑하여 같은 데이터를 직접 읽고 쓸 수 있게 한다.

Shared Memory

System V Shared Memory는  shmget 등의 api를 통해 Shared memory를 구현한다. 현대적 방식으로는 shm_open, mmap 등의 syscall을 이용해서 구현 하는 방법 등이 있다. (매우 다양하다)

 

5. Semaphore와 Mutex

 Shared Memory는 여러 프로세스가 같은 메모리 영역을 직접 공유하기 때문에 빠르지만 동시에 같은 데이터에 접근할 경우 문제가 발생할 수 있다. 이런 문제를 막기 위해 사용하는 것이 Semaphore나 Mutex 같은 동기화 기법을 사용한다.

Semaphore는 프로세스 간 공유 자원 접근을 제어하기 위해 사용될 수 있다. System V Semaphore는 semget, semop, semctl 등의 api를 통해 사용 가능하다.

 

Mutex는 보통 스레드 간 동기화에서 자주 사용된다. 하나의 프로세스 안에서 pthread_create()로 여러 스레드를 생성하면 여러 스레드가 같은 전역 변수나 구조체에 동시에 접근할 수 있다. 이때 pthread_mutex_lock()과 pthread_mutex_unlock()을 사용해 공유 데이터 접근을 보호한다.

 

6. Signal

 Signal은 프로세스에게 특정 이벤트가 발생했음을 알려주는 방식이다. Pipe나 Message Queue처럼 데이터를 많이 주고받기 위한 IPC라기보다는 프로세스에 간단한 제어 신호를 보내는 데 사용된다.  예를 들어 프로세스를 종료하거나 설정 파일을 다시 읽게 하거나 자식 프로세스가 종료되었음을 알리는 용도로 사용될 수 있다.

Linux signal 표

7. socket 기반 IPC

 마지막으로 살펴볼 IPC 방식은 Socket이다. Socket은 보통 네트워크 통신에서 많이 사용되지만 같은 시스템 내부의 프로세스끼리 통신하는 IPC 용도로도 자주 사용된다.

socket 통신을 나타낸 그림, 네트워크 기반임

 

Socket API에서 먼저 봐야 하는 것은 socket()의 첫 번째 인자인 domain 값이다. 각 Domain은 다음과 같은 의미로 사용된다.

 

●  AF_UNIX, AF_LOCAL: 같은 시스템 내부 프로세스 간 통신

 

●  AF_INET : IPv4 네트워크 통신

 

●  AF_INET6 : IPv6 네트워크 통신

 

같은 socket(), connect(), send(), recv() API를 사용하더라도 AF_UNIX를 사용하면 네트워크 통신이 아니라 로컬 IPC에 가깝고 AF_INET을 사용하면 실제 IP 기반 네트워크 통신이 된다.

 

임베디드 시스템에서는 Unix Domain Socket이 자주 사용된다. Unix Domain Socket은 네트워크 주소 대신 파일 시스템 경로를 이용해 소켓을 식별한다.

 

예를 들어 Unix Domain Socket을 이용한 간단한 통신 흐름은 다음과 같다.

 

// server
int sfd = socket(AF_UNIX, SOCK_STREAM, 0);

struct sockaddr_un addr = {0};
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/test.sock");

unlink("/tmp/test.sock");
bind(sfd, (struct sockaddr *)&addr, sizeof(addr));
listen(sfd, 5);

int cfd = accept(sfd, NULL, NULL);

char buf[128] = {0};
recv(cfd, buf, sizeof(buf), 0);
send(cfd, "OK", 2, 0);

 

// client
int fd = socket(AF_UNIX, SOCK_STREAM, 0);

struct sockaddr_un addr = {0};
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/test.sock");

connect(fd, (struct sockaddr *)&addr, sizeof(addr));

send(fd, "hello", 5, 0);

char buf[128] = {0};
recv(fd, buf, sizeof(buf), 0);

 

위 예시에서 서버는 /tmp/test.sock 경로에 소켓을 만들고 accept()로 클라이언트 연결을 기다린다. 클라이언트는 같은 경로에 connect()한 뒤 send()로 데이터를 보내고 서버는 recv()로 이를 읽는다.

 

 

8. 마무리

이번 글에서는 간단하게 시스템 분석을 할 때 마주칠 수 있는 IPC 등을 정리해 보았다. 오늘 공부한 것을 토대로 임베디드 시스템을 더욱 이해해보아야겠따. 

 

'CS' 카테고리의 다른 글

보안기법 정리  (1) 2025.06.07
ELF 바이너리 포맷  (1) 2025.05.22
컴파일 과정  (0) 2025.05.21
'CS' 카테고리의 다른 글
  • 보안기법 정리
  • ELF 바이너리 포맷
  • 컴파일 과정
haehet
haehet
noob hacker
  • haehet
    haehet
    haehet
  • 전체
    오늘
    어제
    • 분류 전체보기 (28)
      • CS (4)
      • Pwnable (6)
      • Cryptography (0)
      • Linux kernel (10)
      • Network (5)
      • Embedded (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    System Management Mode
    tl mr 100
    kernel exploit
    memory
    ROP
    mips
    physmap
    IPC
    공유기
    익스플로잇
    커널
    mr 100
    openwrt
    UART
    linux ring 권한 구조
    hardware hakcing
    fini array
    Cross-cache attack
    라우터
    tp link
    shared memory
    공유 메모리
    Router
    exit handler overwrite
    message queue
    SMM
    protection ring
    Pwnable
    Slab free list poisoning
    exploit
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
haehet
IPC(Inter Process Communication)란?
상단으로

티스토리툴바