안녕하세요, 개발자 여러분! Pwntools는 CTF(Capture The Flag)와 익스플로잇 개발에 필수적인 파이썬 라이브러리입니다. 하지만 원격 서버에서 프로그램을 실행하려 할 때, 때때로 예상치 못한 에러와 마주치기도 합니다. 오늘은 sh.process() 메서드 사용 시 발생하는 ‘Process creation failed’ 에러와 None 반환 문제의 원인을 분석하고, 효과적인 해결 방법을 제시하겠습니다. 특히 pwnable.kr과 같은 CTF 플랫폼에서 이 문제에 직면한 분들께 도움이 될 것입니다.
1. 에러 발생 상황
이 에러는 주로 Pwntools의 ssh 모듈을 사용하여 원격 서버에 연결한 후, 특정 프로그램을 sh.process()로 실행하려 할 때 발생합니다. 사용자는 sh.run() 메서드를 통해 ls 같은 명령은 정상적으로 실행되지만, sh.process("./passcode")와 같이 대화형 프로세스를 시작하려 하면 실패하는 상황에 직면합니다. 아래는 문제가 발생한 코드와 그 출력 예시입니다.
문제 코드 예시:
from pwn import *
HOST = "pwnable.kr"
USER = "passcode"
PASSWORD = "guest"
sh = ssh(host=HOST, user=USER,port = 2222, password=PASSWORD)
print(f"Connection:{ sh.connected()} ")
result = sh.run("ls")
print(result.recvall().decode())
print(sh.run("ls -l ./passcode").recvall().decode())
# 여기서 문제 발생:
p = sh.process("./passcode") # 이 부분이 None을 반환합니다.
print(p)
print(p.recv().decode()) # p가 None이므로 에러 발생
에러 출력 예시:
Connecting to pwnable.kr on port 2222: Done
[*] passcode@pwnable.kr:
Distro Unknown Unknown
OS: Unknown
Arch: Unknown
Version: 0.0.0
ASLR: Disabled
SHSTK: Disabled
IBT: Disabled
Note: Susceptible to ASLR ulimit trick (CVE-2016-3672)
Connection:True
[+] Opening new channel: 'ls': Done
[+] Receiving all data: Done (34B)
[*] Closed SSH channel with pwnable.kr
flag passcode passcode.c readme
[+] Opening new channel: 'ls -l ./passcode': Done
[+] Receiving all data: Done (61B)
[*] Closed SSH channel with pwnable.kr
-r-xr-sr-x 1 root passcode_pwn 15232 May 23 16:00 ./passcode
[-] Starting remote process None on pwnable.kr: Process creation failed
None
Traceback (most recent call last):
File "/home/kali/Downloads/passcode.py", line 13, in
print(p.recv().decode())
출력에서 볼 수 있듯이, ls 명령은 정상적으로 작동하며 ./passcode 파일의 권한도 확인됩니다. 하지만 sh.process("./passcode")에서 Process creation failed 메시지가 뜨고 p 변수가 None이 되어 다음 줄에서 AttributeError가 발생하는 것을 확인할 수 있습니다.
2. 명확한 발생 원인
이 문제의 핵심 원인은 Pwntools의 sh.process() 메서드가 작동하는 방식과 원격 서버 환경의 보안 정책 때문입니다.
2.1. sh.process()의 역할
sh.process()는 로컬에서 process()를 사용하는 것과 유사하게, 원격 서버에 *새로운 대화형(interactive) 프로세스*를 독립적으로 생성하고 해당 프로세스의 표준 입출력(stdin/stdout)을 제어할 수 있는 튜브(tube) 객체를 반환하려고 시도합니다. 이는 일반적으로 원격 서버에 새로운 셸 세션이나 가상 터미널(pseudo-terminal, pty)을 할당하고 그 안에서 프로그램을 실행하는 복잡한 과정을 거칩니다.
2.2. 원격 서버의 보안 제약
대부분의 CTF 서버나 보안이 강화된 리눅스 서버 환경에서는 일반 사용자 계정(여기서는 passcode 사용자)이 임의로 새로운 대화형 프로세스를 생성하거나, fork() 및 exec() 시스템 호출을 통해 새로운 프로세스 트리를 시작하는 것이 제한될 수 있습니다. 특히 pwnable.kr과 같은 CTF 플랫폼은 악용을 방지하기 위해 이러한 기능에 엄격한 제한을 두는 경우가 많습니다.
- 권한 문제:
passcode사용자가 원격에서 새로운 프로세스를 생성하는 데 필요한 적절한 권한이 없을 수 있습니다. - 자원 제한: 서버 관리자가 특정 사용자의 프로세스 생성 수나 메모리 사용량에
ulimit등으로 제한을 걸었을 수 있습니다. - 가상 터미널(PTY) 제한: 새로운 대화형 프로세스는 종종 가상 터미널이 필요한데, 서버 설정에서 이를 제한할 수 있습니다.
반면, sh.run()은 단순히 원격 SSH 세션 내에서 명령을 실행하고 그 결과를 파이프를 통해 받아오는 방식이므로, 새로운 독립적인 프로세스를 생성하는 것보다 훨씬 낮은 권한과 복잡성을 요구합니다. 따라서 sh.run()은 잘 작동하지만, sh.process()는 실패하는 것입니다.
ls -l ./passcode 결과에서 보이는 -r-xr-sr-x의 s (setuid) 비트는 프로그램이 실행될 때 파일 소유자(root)의 권한으로 실행되게 하지만, 이는 프로세스 *생성* 자체의 권한과는 무관합니다. 즉, passcode 사용자는 ./passcode를 실행할 수는 있지만, sh.process()가 요구하는 방식으로 독립적인 프로세스를 생성할 권한은 없을 수 있습니다.
3. 해결 방법 및 코드 예시
sh.process()가 원격 환경에서 제한된다면, 그 대신 sh.run() 메서드를 활용하여 프로그램을 실행하고 상호작용하는 방식으로 접근해야 합니다. sh.run()은 명령 실행 후 그 명령의 표준 입출력에 연결된 튜브 객체를 반환하므로, 이를 통해 얼마든지 대화형 상호작용이 가능합니다.
해결 코드 예시: sh.run()을 이용한 상호작용
다음 코드는 sh.run()을 사용하여 ./passcode 프로그램을 실행하고, 예상되는 프롬프트에 응답하며 페이로드를 전송하는 일반적인 CTF 시나리오를 보여줍니다.
from pwn import *
# 연결 정보 설정
HOST = "pwnable.kr"
USER = "passcode"
PASSWORD = "guest"
PORT = 2222
# SSH 연결
print(f"[*] Connecting to {HOST} on port {PORT}...")
sh = ssh(host=HOST, user=USER, port=PORT, password=PASSWORD)
print(f"[+] Connection status: {sh.connected()}")
# 1. 'sh.run()'을 사용하여 './passcode' 프로그램 실행
# sh.run()은 원격에서 명령을 실행하고, 해당 명령의 stdin/stdout에 연결된 tube 객체를 반환합니다.
# 이를 통해 프로그램과 상호작용할 수 있습니다.
print("[*] Using 'sh.run()' to execute './passcode' and interact.")
io = sh.run("./passcode")
# 프로그램이 특정 프롬프트를 출력할 때까지 기다립니다.
# 'Input your passcode:'와 같은 문자열을 기다리는 것이 일반적입니다.
# 여기서는 ':' 문자를 포함하는 첫 번째 줄까지 기다린다고 가정합니다.
# 실제 CTF 문제에 따라 정확한 프롬프트를 파악해야 합니다.
try:
prompt = io.recvuntil(b":", timeout=2).decode() # 프롬프트를 기다립니다. (2초 타임아웃)
print(f"[+] Received prompt: {prompt.strip()}")
except EOFError:
print("[-] No prompt received, or program exited early.")
# 프로그램이 프롬프트 없이 바로 종료되거나, 다른 방식으로 동작할 수 있습니다.
# 이 경우 아래 페이로드 전송 로직을 조절해야 합니다.
io.close()
sh.close()
exit(1)
# 2. CTF 문제의 페이로드를 전송합니다.
# 'passcode' 문제는 스택에 특정 값을 덮어쓰는 것이 목표입니다.
# 이 예시에서는 4바이트 더미 값(AAAA)을 전송하는 것으로 대체합니다.
# 실제 해법에 맞는 페이로드를 여기에 넣어주세요.
# 예를 들어, 오버플로우를 유발하는 4바이트 값 (0xdeadbeef 등)을 리틀 엔디언으로 변환하여 사용:
# payload = p32(0xdeadbeef)
# 현재는 예시를 위해 단순 바이트열 사용
payload = b"AAAA"
print(f"[*] Sending payload: {payload.hex()}")
io.sendline(payload)
# 3. 프로그램의 응답을 받습니다.
# 프로그램이 최종 결과(예: 플래그)를 출력할 때까지 기다립니다.
print("[*] Receiving program response...")
response = io.recvall(timeout=2) # 모든 응답을 받을 때까지 기다립니다. (2초 타임아웃)
print(f"[+] Program output:\n{response.decode()}")
# 사용한 tube와 SSH 연결을 닫습니다.
io.close()
sh.close()
print("[*] Script execution completed. SSH connection closed.")
이 코드는 sh.run()을 사용하여 원격 프로그램을 실행하고 io.recvuntil(), io.sendline(), io.recvall()과 같은 Pwntools 튜브 메서드를 통해 상호작용하는 방법을 보여줍니다. 이는 sh.process()가 실패할 때 사용할 수 있는 가장 일반적이고 효과적인 대안입니다.
4. 향후 예방을 위한 팁
이러한 문제를 사전에 방지하고 효율적인 익스플로잇 개발을 위해 다음 팁들을 기억해두세요.
-
Pwntools 메서드 용도 이해하기:
process(): 로컬에서 새로운 프로세스를 생성하고 상호작용할 때 사용합니다.remote(): 원격 네트워크 서비스(예:nc host port)에 연결하고 상호작용할 때 사용합니다.ssh.run(): SSH 연결을 통해 원격에서 명령을 실행하고 그 입출력에 연결된 튜브를 얻을 때 사용합니다. 대부분의 CTF에서 원격 프로그램 실행 및 상호작용에 가장 적합합니다.ssh.process(): SSH 연결을 통해 원격에서 새로운 대화형 프로세스를 독립적으로 생성하려 할 때 사용합니다. 원격 서버의 보안 정책에 따라 실패할 확률이 높으므로, 작동하지 않는다면ssh.run()으로 전환하는 것을 고려하세요.
-
원격 환경에 대한 이해:
CTF 플랫폼이나 실제 서버 환경은 로컬 개발 환경과 다릅니다. 원격 서버의 파일 시스템 구조, 권한 설정, 방화벽,
ulimit등의 자원 제한 사항을 항상 염두에 두어야 합니다. -
에러 메시지 분석:
Process creation failed와 같은 명확한 에러 메시지는 문제의 근본 원인을 파악하는 데 중요한 단서가 됩니다. 에러 메시지를 검색하거나 Pwntools 공식 문서를 참고하여 추가 정보를 얻으세요. -
CTF 문제의 의도 파악:
대부분의 CTF 문제는 특정 취약점을 통해 셸을 얻거나, 특정 값을 입력하여 플래그를 읽어내는 방식으로 설계됩니다.
sh.run()을 통해 입력과 출력을 제어하는 것이 이러한 시나리오에 더 적합한 경우가 많습니다.
이 문서를 통해 Pwntools 사용 시 sh.process() 에러로 인한 어려움을 해결하고, 더욱 효율적인 익스플로잇 개발을 진행하시길 바랍니다!
![[에러 해결] Python Unix 환경: Stdin/Stdout 간섭 없는 대화형 터미널 입력 처리 방법 [에러 해결] Python Unix 환경: Stdin/Stdout 간섭 없는 대화형 터미널 입력 처리 방법](https://dev-error.com/wp-content/plugins/contextual-related-posts/default.png)