벌써 두 문제나 해치우고 level3에 와있다. 접속하자마자 ls -al과 find / -user level4 -perm -4000 2>/dev/null을 입력해보았다.
ls -al은 특별한 게 없었고, find 명령의 결과는 다음과 같았다.
[level3@ftz level3]$ find / -user level4 -perm -4000 2>/dev/null
/bin/autodig
해당 파일을 ls -al로 출력해보았다.
역시나... setUID가 설정되어 있고, level4의 파일이며 level3이 사용할 수 있다. 해당 파일을 이용해서 level4의 권한을 얻어 my-pass를 이용하면 될 것 같다. 무슨 파일인지 실행시켜보기로 한다. 이 때, autobig은 bin 디렉토리에 들어있기 때문에, 명령어처럼 사용할 수 있다.
그냥은 사용할 수 없고 autodig host를 인자로 넘겨서 사용해야 한다는 것 같다. 아무 값이나 넘겨보았다.
알 수 없는 이름 또는 서비스라는 문구가 뜬다. 로그인 하기 위한 계정같은 무언가가 필요한 듯 하다. 일단은 디버깅을 통해 어떠한 조건이 걸려있는지 확인해보기로 했다.
(gdb) disas main
Dump of assembler code for function main:
0x08048430 <main+0>: push ebp
0x08048431 <main+1>: mov ebp,esp
0x08048433 <main+3>: sub esp,0x78
0x08048436 <main+6>: and esp,0xfffffff0
0x08048439 <main+9>: mov eax,0x0
0x0804843e <main+14>: sub esp,eax
0x08048440 <main+16>: cmp DWORD PTR [ebp+8],0x2
0x08048444 <main+20>: je 0x8048475 <main+69>
0x08048446 <main+22>: sub esp,0xc
0x08048449 <main+25>: push 0x8048588
0x0804844e <main+30>: call 0x8048340 <printf>
0x08048453 <main+35>: add esp,0x10
0x08048456 <main+38>: sub esp,0x8
0x08048459 <main+41>: mov eax,DWORD PTR [ebp+12]
0x0804845c <main+44>: push DWORD PTR [eax]
0x0804845e <main+46>: push 0x80485a1
0x08048463 <main+51>: call 0x8048340 <printf>
0x08048468 <main+56>: add esp,0x10
0x0804846b <main+59>: sub esp,0xc
0x0804846e <main+62>: push 0x0
0x08048470 <main+64>: call 0x8048360 <exit>
0x08048475 <main+69>: sub esp,0x8
0x08048478 <main+72>: push 0x80485b2
0x0804847d <main+77>: lea eax,[ebp-120]
0x08048480 <main+80>: push eax
0x08048481 <main+81>: call 0x8048370 <strcpy>
0x08048486 <main+86>: add esp,0x10
0x08048489 <main+89>: sub esp,0x8
0x0804848c <main+92>: mov eax,DWORD PTR [ebp+12]
0x0804848f <main+95>: add eax,0x4
0x08048492 <main+98>: push DWORD PTR [eax]
0x08048494 <main+100>: lea eax,[ebp-120]
0x08048497 <main+103>: push eax
0x08048498 <main+104>: call 0x8048330 <strcat>
0x0804849d <main+109>: add esp,0x10
0x080484a0 <main+112>: sub esp,0x8
0x080484a3 <main+115>: push 0x80485b8
0x080484a8 <main+120>: lea eax,[ebp-120]
0x080484ab <main+123>: push eax
0x080484ac <main+124>: call 0x8048330 <strcat>
0x080484b1 <main+129>: add esp,0x10
0x080484b4 <main+132>: sub esp,0x8
0x080484b7 <main+135>: push 0xbbc
0x080484bc <main+140>: push 0xbbc
0x080484c1 <main+145>: call 0x8048350 <setreuid>
0x080484c6 <main+150>: add esp,0x10
0x080484c9 <main+153>: sub esp,0xc
0x080484cc <main+156>: lea eax,[ebp-120]
0x080484cf <main+159>: push eax
0x080484d0 <main+160>: call 0x8048310 <system>
0x080484d5 <main+165>: add esp,0x10
0x080484d8 <main+168>: leave
0x080484d9 <main+169>: ret
0x080484da <main+170>: nop
0x080484db <main+171>: nop
End of assembler dump.
(gdb)
autodig 실행 파일의 메인 함수 부분이다. 먼저 main에 break를 걸어주고 run AAAAAAAA를 입력해보았다. 그 후에, esp, ebp, eax의 상태를 출력하였다.
eax는 보통 변수를 저장할 때 사용하는 레지스터인데, main이 시작함과 동시에 2가 저장되어있다. 아래의 main+16의 je 명령어 전까지 해석해보면 다음과 같다.
0x08048430 <main+0>: push ebp //ebp의 주소를 현재위치(esp)에 저장.
0x08048431 <main+1>: mov ebp,esp //ebp에 esp의 값(현재위치) 저장. 이로써 ebp는 main 스택프레임의 바닥의 주소가 됨.
0x08048433 <main+3>: sub esp,0x78 //esp에서 0x00000078만큼 뺀 후에 esp에 저장.
0x08048436 <main+6>: and esp,0xfffffff0 //esp와 0xfffffff0을 비트단위로 AND연산.
0x08048439 <main+9>: mov eax,0x0 //eax에 0을 저장.
0x0804843e <main+14>: sub esp,eax //esp에서 eax를 뺀 값을 esp에 저장. 변동 없음.
0x08048440 <main+16>: cmp DWORD PTR [ebp+8],0x2 //ebp+8의 값과 0x2를 비교.
cmp 명령어는 앞의 값에서 뒤의 값을 뺐을 때, 0이 나오면 ZF라는 레지스터에 1을 저장한다. 그 후에 je 명령을 실행할 때, ZF가 1이면 지정한 곳으로 점프한다. 점프하는 곳은 main+69인데, 그 바로 위의 main+64를 보면 프로그램을 종료하는 exit 함수를 호출하는데,
한 마디로 정리하면 "eax가 2라면 종료하지 않고 계속 실행한다."라고 볼 수 있겠다. 그렇다면 프로그램을 시작하자 마자 eax에 2라는 값이 저장되는데, 변수를 선언하는 부분이 따로 보이지 않는다... 했더니, main 함수의 인자 argc를 생각해보면 이에 대한 답이 나온다. 그러니까 아무런 입력없이 프로그램만 실행하면 argc는 기본적으로 1이지만, 인자를 하나 추가할 때마다 1씩 늘어나게 되니까, 프로그램을 실행할 때 넘기는 인자가 1개가 있어야 프로그램이 정상 작동한다는 의미가 된다. 일단, 이후의 코드를 쭉 보았다.
코드가 너무 길어 함수가 선언되는 부분만 봤다. strcpy가 실행되고, strcat이 2번 실행된 후에 setreuid가 실행되고서 system 함수가 실행되는 식이다. 각각의 함수 호출부분에 중단점을 지정해서, 함수 호출 직전에 매개변수로 넘기기 위해 push하는 값들을 확인해 보았는데,
strcpy는 dig @ , strcat은 각각 AAAAAAAA, version.bind chaos txt 이였고 이를 eax에 저장해서 dig @ AAAAAAAA version.bind chaos txt 라는 값을 system 함수의 인자로 넘겨준다. 명령어에 dig라는 것도 있나? 한번 입력해보았다.
[level3@ftz level3]$ dig
; <<>> DiG 9.2.1 <<>>
;; global options: printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2667
;; flags: qr rd ra; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 15
;; QUESTION SECTION:
;. IN NS
;; ANSWER SECTION:
. 5 IN NS d.root-servers.net.
. 5 IN NS j.root-servers.net.
. 5 IN NS e.root-servers.net.
. 5 IN NS b.root-servers.net.
. 5 IN NS f.root-servers.net.
. 5 IN NS g.root-servers.net.
. 5 IN NS h.root-servers.net.
. 5 IN NS a.root-servers.net.
. 5 IN NS i.root-servers.net.
. 5 IN NS m.root-servers.net.
. 5 IN NS k.root-servers.net.
...
dig라는 프로그램이 실행되었다. 뭐하는 프로그램이지... dig --help를 입력해보았다.
[level3@ftz tmp]$ dig --help
Invalid option: --help
Usage: dig [@global-server] [domain] [q-type] [q-class] {q-opt}
{global-d-opt} host [@local-server] {local-d-opt}
[ host [@local-server] {local-d-opt} [...]]
Use "dig -h" (or "dig -h | more") for complete list of options
dig -h을 이용하란다. 입력했더니 엄청나게 많은 옵션들이 나온다.
dig가 무엇인지 알아보기 위해 구글링을 좀 해보았다.
dig : Domain Information Groper의 약자로, DNS(도메인 네임 시스템) 서버로부터 정보를 가져올 수 있는 명령 툴이다. 사용법은 다음과 같다.
dig @[server] [domain] [query-type] [query-class]
server : 질의를 요청하고자 하는 DNS 서버.
domain : 정보를 요청하고자 하는 도메인의 이름.
query-type : 요청한 정보에 대한 타입. (any, a, mx, sig ... 등이 있다.)
- a : 도메인의 아이피 정보(network address)
- any : 지정된 도메인의 모든 정보
- mx : mail exchanger의 정보
- ns : name server 정보
- soa : Zone 파일 상단의 authority 레코드
- hinfo : host 정보
- axft : zone transfer (authority를 갖는 특정 네임서버에 질의)
- txt : 임의의 수의 캐릭터 라인 (arbitrary number of strings)
query-class : query의 network class 부분. (확인하고자 하는 도메인)
설명은 이렇다. 우선 autodig로 넘기는 인자와 비교해서 보려 한다.
dig @AAAAAAAA version.bind chaos txt
도메인 이름은 version.bind, 요청한 정보의 타입은 chaos이고 쿼리의 network class가 txt이다...? 무슨 말이지...? 뭘 하는 프로그램이라는거지..?
도저히 갈피가 잡히지 않아, 어쩔 수 없이 힌트를 보기로 했다.
디버거를 이용해 소스코드를 열심히 분석했고만, 힌트에 소스코드가 떡하니 있었다. 다만, autodig 프로그램 자체를 이용하는 건 아니였나 보다. 그러니까 level4 유저의 권한으로 autodig을 실행하는데, 이 때 허점을 이용해서 쉘을 불러야 하는 것 같다.
일단, system 함수로 넘어가는 cmd의 구성은 "dig @" + argv[1] + " version.bind chaos txt"이다. 그리고 cmd는 문자형 배열이다. 이 때 쉘을 부르는 방법이 무엇이 있을까 생각하다, "동시에 여러 명령어를 사용하려면?" 에서 무언가 번뜩였다.
한 줄에 명령어를 여러 개 쓰는 방법은 명령어들 사이를 세미콜론(;)으로 연결하는 것이다.
//pwd;ls
[level3@ftz tmp]$ pwd;ls
/home/level3/tmp
autodig shell.txt test.txt
[level3@ftz tmp]$
그리고 문자열 형태로 명령어를 전달하려면 "큰 따옴표"를 이용해서 하나로 묶어주면 된다.
그렇다면... 모르겠다. 명령어를 여러개 사용한다고 하니, 그냥 인자를 ls;pwd;whoami; 로 보내보았다. 문자열을 보내는 방법이란 힌트도 있었으니, 큰 따옴표도 이용해보았다.
??????? 그냥 명령어만 나열해서 입력했을 때는 아무일 없던 것이, 문자열 형식으로 "큰 따옴표"를 이용해서 보낼때에는 whoami가 level4가 되어있었다. 그렇다면 큰 따옴표로 묶어서 my-pass를 입력한다면...
level4의 패스워드가! 드디어! 출력되었다.
왜 큰 따옴표가 있을 때와 없을 때의 결과가 달라지는지에 대해 적어보려 한다. 예시로 들 문자열은 " ;whoami; "이다.
우선, autodig 프로그램은 "dig @argv[1] version.bind chaos txt" 문자열을 system 함수의 인자로 넘겨주어 실행시킨다. 이 때, system 함수를 호출하기 전에 setreuid 함수를 이용해 uid를 재설정해준다. setreuid(ruid, euid)는 RUID, EUID를 변경해주는 함수로 각각 ruid의 값과, euid의 값으로 변경해준다.
UID의 종류
1) RUID
-> Real User ID의 약자로, 실제 사용자의 ID를 의미함. 프로세스를 실행할 때의 사용자 정보로 사용됨.
2) EUID
-> Effective User ID의 약자로, 현재의 프로그램, 프로세스에 대한 권한을 나타낸다.
기본적으로 RUID 값과 동일.
3) SUID
-> Saved User ID의 약자로, 바꾸기 전의 EUID의 값을 저장한다. 이전 EUID의 값을 복원할 때 SUID의 값을
이용함.
위의 어셈블리 코드에서 setreuid 함수를 호출하기 직전에 0xbbc 값을 push하는 코드가 두 줄있는데, 이들이 각각 RUID, EUID의 값을 설정해주기 위한 인자이다. 0xbbc를 10진수로 바꾸어보면 3004인데, ruid, euid에 각각 3004의 값을 넘겨주는 것이다. 3004의 uid를 가진 유저가 누가 있을까? 바로 level4이다. 실제로 level3과 level4의 id를 출력해보면 3003, 3004가 나온다. 그러니까 system 함수를 실행할 때, level4의 권한으로서 실행하게 되는 것이다.
다시 본론으로 돌아와서, 먼저 autodig ;whoami; 의 실행 과정이다. 세미콜론(;)은 명령어 사이에 써줌으로써 한 줄에 실행하고자 하는 여러개의 명령어를 입력할 때 사용 된다고 하였다. 만약, 큰 따옴표가 없이 위처럼 사용한다면, ;whoami; 가 프로그램의 인자로 넘어가는게 아니라,현재 로그인한 유저(level3)가 autodig 를 사용하고, whoami를 사용한다는 것과 같은 의미이다.
[level3@ftz level3]$ autodig
[level3@ftz level3]$ whoami
이렇게 두 줄에 거쳐서 명령어들을 실행한 것과 같다는 이야기이다. 이 때, autodig에 인자가 없기 때문에, autodig 프로그램이 바로 종료되고, 다시 돌아와서 whoami를 사용하는 것이기 때문에 whoami의 결과는 level3이 된다.
하지만 autodig ";whoami;"를 사용한다면 달라진다. 큰 따옴표를 이용해서 출력한다면, 큰 따옴표 안에 있는 문자열을 하나로 묶어서, 하나의 인자로 넘겨서 autodig를 실행하게 된다. 이 때에는 세미콜론(;)이 명령어들을 이어주는 기호(?)로 인식되는게 아닌, 하나의 문자로 인식되기 때문에 autodig가 바로 종료되지 않는 것이다.
프로그램이 실행되는 동안, argv[1] 은 ";whoami;"가 되고, setreuid를 통해 level4의 uid로 바꾸어주고, system 함수를 호출한다.
이 때 system함수의 인자는 "dig @;whoami;version.bind chaos txt"가 된다. 이 때, 프로그램이 종료되지 않았기 때문에, dig @; 명령어가 에러 메시지를 출력하고 dig 프로그램이 종료되어도, autodig는 아직 실행중이기 때문에 uid는 아직 level4로 남아있게 된다. 그렇기 때문에 whoami를 입력하면 level4가 출력되는 것이다. whoami 뿐만 아니라, level4의 권한으로만 사용할 수 있는 그 무엇도 할 수 있게 된다.
고작 큰 따옴표 하나라고 생각했지만, 그 때문에 이렇게 결과가 달라질 수가 있고, 악용될 수도 있다는 것을 깨달으면서, level3을 마무리하고 level4로 넘어가도록 한다.
'Pwnable > FTZ' 카테고리의 다른 글
[ProjectH4C] [Write-up] 해커스쿨 FTZ Level 5 (0) | 2020.08.04 |
---|---|
[ProjectH4C] [Write-up] 해커스쿨 FTZ Level 4 (0) | 2020.08.03 |
[ProjectH4C] [Write-up] 해커스쿨 FTZ Level 2 (0) | 2020.07.31 |
[ProjectH4C] [Write-up] 해커스쿨 FTZ Level 1 (0) | 2020.07.31 |
[ProjectH4C] [Write-up] 해커스쿨 FTZ Training 6~10 (0) | 2020.07.30 |