취약점분석/Pwnable

[Pwnable] pwntools 실습-1 (addition-quiz)

poiri3r 2025. 6. 12. 17:19

이번 포스팅은 어제 작성했던 pwntools의 실습을 연습하는 과정을 작성해보겠습니다.

해당 문제는 드림핵 홈페이지 https://dreamhack.io/wargame/challenges/1114 에서 다운로드 받으실 수 있습니다.

상세 해설도 드림핵 - 학습에서 pwntools1에서 확인하실 수 있습니다!

 

해당 문제를 다운 받은 뒤 chall.c 를 소스코드의 메인함수를 분석하면 다음과 같습니다.

 int num1 = 0;
    int num2 = 0;
    int inpt = 0; 

    for (int i = 0; i < 50; i++){
        alarm(1);
        num1 = rand() % 10000;
        num2 = rand() % 10000;
        printf("%d+%d=?\n", num1, num2);
        scanf("%d", &inpt);

        if(inpt != num1 + num2){
            printf("Wrong...\n");
            return 0;
        }
    }
  • 50회 반복
  • 매 문제마다 1초 타임아웃 설정(alarm(1))
  • 0~9999사이의 랜덤 난수 생성(rand())
  • 덧셈 문제를 출력한 뒤 사용자의 입력을 받음
  • 정답과 입력하여 틀리면 return 0으로 프로세스 종료
    puts("Nice!");
    puts(flag);

    return 0;
}

50문제를 다 풀고 나면 플래그를 출력함

 

문제 특징과 제한점

문제당 1초의 시간 제한이 있음

srand(time(NULL))을 통해 랜덤 난수를 생성함 -> 매 번 문제가 달라짐

=> 자동으로 문제를 계산해서 보내주는 자동화 스크립트가 필요하다!

 

문제 풀이 

먼저 문제는 드림핵 서버에서 VM을 부팅해주고 그 서버와 통신해서 문제를 받아와야 하기 때문에 remote를 통한 원격 통신이 필요하다.

드림핵에서 문제를 다운받으면 chall, chall.c, flag를 제공한다

드림핵에서 제공하는 파일 외에 자동화스크립트를 작성할 flagprinter.py를 만들었다.

자동화스크립트를 짜기 전에 pwntools에서 r로 서버에 연결을 해보자

r = remote("host주소", 포트번호)로 연결 해주면 잘 연결 됨을 확인할 수 있다. (주소와 포트번호는 드림핵 워게임에서 접속할 수 있다)

 

recv함수를 통해 데이터를 받으면 문제 한 줄이 출력된다.

이제 반응을 확인하기 위해서 r.send()를 통해서 아무 메세지나 보내고 출력해보자

1초가 지났기 때문에 정답 유무와 관계 없이 TIME OUT이 뜨고 연결이 종료되었다.

 

파이썬 스크립트 작성

먼저 서버에서 연결하는 초반 스크립트를 작성한다.

from pwn import* 

r = remote('host주소', port번호)

 

그 다음 서버에서 보내주는 숫자를 받기 위해 recvuntil()을 사용하는 스크립트를 작성한다.

 

for _ in range(50):
	str1 = int(r.recvuntil(b'+', drop=True)
	str2 = int(r.recvuntil(b'=?\n', drop=True)

 

여기서 스크립트 문자의 기준은 아까 출력 확인했을 때 출력인 +와 ?\n으로 recvuntil을 사용하였다

(파이썬 스크립트가 있으면 스크립트를 확인하고 코드를 짜도 상관없다)

 

drop=True를 사용하면 recvuntil을 사용해서 문자를 받을때 첫번째 인자를 제외한다. 즉 다음 recvuntil에서 +가 함께 출력되지않고 숫자만 깔끔하게 출력된다.

 

이제 서버에서 받은 숫자를 더한 뒤 송신하는 코드를 작성한다.

for _ in range(50):
	str1 = int(r.recvuntil(b'+', drop=True)
	str2 = int(r.recvuntil(b'=?\n', drop=True)
	r.sendline(str(str1+str2))

 

50번의 반복이 끝난 뒤 플래그값을 출력해줄 문자열을 송신할 print를 작성하고 프로세스를 닫아준다.

print(r.recvall())
r.close()

*r.recvall은 연결을 내부적으로 닫아주기 때문에 r.close()를 생략할 수 있다. 하지만 명확한 종료 처리를 위해 습관적으로 넣는것이 좋다..!

 

완성된 스크립트는 다음과 같다.

from pwn import *

r = remote('host3.dreamhack.games', 19692)

for _ in range(50):
    str1 = int(r.recvuntil(b'+', drop=True))
    str2 = int(r.recvuntil(b'=?\n', drop=True))
    
    r.sendline(str(str1+str2))

print(r.recvall())
r.close()

 

굉장히 간결한 코드가 완성됐다 ㅎ

이 코드를 flagprinter.py에 저장한 뒤 가상환경에서 실행하면 플래그가 추출될 것이다.

 

가상환경을 킬 때 쪼금 헷갈렸던 부분이 있는데 어제 만들어둔 가상환경이 mnt/c/Users/../폴더에 저장이 되어있어서 해당 지점에서 실행해야 가상환경을 실행할 수 있다. 항상 문제들을 풀 때 바탕화면에 폴더를 만들어두고 거기서 리눅스를 실행하였는데 가상환경이 안돼서 가상환경을 설치한 폴더에서 flagprinter.py경로를 읽어서 실행했다.

좀 간단하게 하고 싶은데 어떻게 해야할지 모르겠네. .ㅎ

어쨋든 제대로 실행을 했다.

경로도 복사한뒤 리눅스 경로로 바꿔서 실행해주면 DH{}로 플래그가 잘 나오는 걸 확인할 수 있다.

직접 해보길 바라는 마음에서 플래그는 숨겨뒀습니다.

 

좀 더 문제 송수신과정을 확인해보고 싶어서 스크립트를 조금 수정했다. (중간에 서버가 닫혀서 새로 요청해서 포트번호가 바꼈다.)

from pwn import *

r = remote('host3.dreamhack.games', 10745)

for _ in range(50):
    str1 = int(r.recvuntil(b'+', drop=True))
    print(str1, "+ " ,end="")
    str2 = int(r.recvuntil(b'=?\n', drop=True))
    print(str2, "=", str1+str2)
    r.sendline(str(str1+str2))

print(r.recvall())
r.close()

덧셈결과와 FLAG값이 아주 잘 출력된다!

 

이상으로 pwntools 첫번째 실습 포스팅을 마치겠습니다. 

제대로 할 줄 아는 언어가 없어서 간단한 스크립트 작성하는것도 생각보다 어려운데, 보안공부한다는 핑계대지말고 C언어랑 파이썬도 꾸준히 공부를 해야 할 것 같습니다. 

오늘 내일내로 pwntools 두번째 문제 실습 포스팅을 작성하겠습니다.

여기까지 봐주신분들 대단히 감사합니다~ㅎ