pwntools recv recvline pitfall
최근에 문제를 풀다가 내가 pwntools에서 recv recvline에 대해 오해한 정보가 있어서 여기에 서술하겠다.
p = remote('host8.dreamhack.games', 20769)
p.sendline(str(3))
p.sendline(str(20))
p.recvline()
p.sendline(str(1))
payload = b"A" * 80
p.sendline(payload)
p.recvline()
p.sendline(str(3))
p.sendline(str(2))
data = p.recvline()
print("data -> " , data[:-1])
data = int(data[:-1] , 10)
print(hex(data))
p.interactive()예를 들어 다음과 같은 익스 코드가 있다고 하자 중간 중간에 p.recvline() 이 보일 것이다.
이런식으로 recvline() 하고 sendline 하고를 하는 대화형 프로그램인데… 과연 상단의 코드가
p = process(fname , env = {'LD_PRELOAD' : './libc.so.6'} ) # libc 지정
p.sendline(str(3))
p.sendline(str(20))
p.sendline(str(1))
payload = b"A" * 80
p.sendline(payload)
p.sendline(str(3))
p.sendline(str(2))
p.recvline()
p.recvline()
data = p.recvline()
print("data -> " , data[:-1])
data = int(data[:-1] , 10)
print(hex(data))
p.interactive()p.recvline() 을 한번에 연달아 2번을 하는 이 코드가 똑같이 작동할까?
나는 처음에는 당연히 안될줄 알았다. p.recvline() 이 실시간으로 버퍼에서 받을 때까지 대기를 하는 함수인지 알았기 때문이다. 하지만, 약간의 오해가 있다.
p.recvline() 은
- 내부 버퍼에 이미 개행까지 한 줄이 있으면 → 그걸 즉시 반환
- 아니면 소켓에서 새 데이터를 받으며 개행이 나올 때까지 대기(또는 타임아웃 시 b'')
다음과 같이 작동한다. 즉, 1번이 있다는 것이다.
이 내부 버퍼에 대해 서술하자면,
- pwntools의 모든 I/O 객체(process, remote, ssh 등)는 공통으로 튜브(tube) API를 씁니다. 내부에 Buffer가 있고, 먼저 도착한 데이터가 먼저 소비됩니다. recvline()은 이 버퍼에서 개행까지 한 줄을 꺼내옵니다(없으면 새 데이터가 올 때까지 대기/타임아웃).
다음과 같다. 즉, 내부 버퍼에 프로그램의 출력들이 차곡차곡 저장이 되고 p.recv~~() 계열의 함수들은 그걸 버퍼의 시작부터 가져오기만 할 뿐이다. 만약 버퍼가 없으면 그때 대기를 한다.
따라서,
p = remote('host8.dreamhack.games', 20769)
p.sendline(str(3))
p.sendline(str(20))
p.recvline()
p.sendline(str(1))
payload = b"A" * 80
p.sendline(payload)
p.recvline()
p.sendline(str(3))
p.sendline(str(2))
data = p.recvline()
print("data -> " , data[:-1])
data = int(data[:-1] , 10)
print(hex(data))
p.interactive()다음과 같이 받을때마다 p.recvline() 을 통해 내부버퍼를 정렬해놓아야한다. 근데 이는 귀찮다.
따라서,
p = remote('host8.dreamhack.games', 20769)
p.sendline(str(3))
p.sendline(str(20))
//p.recvline()
p.sendline(str(1))
payload = b"A" * 80
p.sendline(payload)
//p.recvline()
p.sendline(str(3))
p.recv() //버퍼 미리 다 사용하기
p.sendline(str(2))
data = p.recvline()
print("data -> " , data[:-1])
data = int(data[:-1] , 10)
print(hex(data))
p.interactive()다음과 같이 새로운 입력을 받기 전에 p.recv() 등을 사용해서 버퍼에 있는 내용을 다 사용하거나,
p.clean() 으로 명시적으로 버퍼를 정리할 수 있다.
근데 이때 네트워크 환경 ( remote )에서는 타이밍과 관련된 문제가 생길 수 있다.
상당의 코드를 동작시켰을때,
payload = b"A" * 80
p.sendline(payload)
//p.recvline()이 이후에 데이터를 받아야 하는데 네트워크 환경이라서 데이터가 늦게와
p.sendline(str(3))
p.recv() //버퍼 미리 다 사용하기
p.sendline(str(2))이 부분이 먼저 실행이 되어버리는 것이다. 그럼 이 다음 코드인
data = p.recvline()
print("data -> " , data[:-1])이 부분에서 해당 데이터가 처리가 되어버리는 의도하지 않은 전개가 될 수 있다. 따라서 네트워크 환경에서는 입력을 받을때 p.recv~ 계열 함수로 정렬을 시켜놓던가 아니면,
p.sendline(str(3))
sleep(0.5)
p.recv() //버퍼 미리 다 사용하기
p.sendline(str(2))이런식으로 기다리는 것도 하나의 방법이긴 하다.