OverTheWire Narnia 

[ level9 ]



OverTheWire Narnia 클리어


OverTheWire Narnia 

[ level8 -> level9 ]



오랜만에 풀이하는 OverTheWire 문제입니다. 이전에 하던 Narnia 난이도의 마지막 문제를 풀이했는데요, 간만헤 하는 시스템해킹인지라 감이 떨어졌는지 한 문제 푸는데만 하루가 꼬박 걸렸습니다. 문제 보도록 하죠.




소스입니다. 메인 함수를 확인 해 보니 argv[1]을 func 함수로 보내주네요. func 함수는 blah 포인터 변수에 받아온 argv[i]의 값을 넣어 준 후 20의 크기를 가진 bok이라는 배열을 선언합니다. 그리고 memset 함수를 이용하여 bok의 사이즈(20)만큼 NULL로 초기화를 시켜줍니다. 그 후 가장 중요한 부분입니다. 빨간 네모 박스 쳐져 있는 부분인데, blah[i]가 NULL이 나올 때 까지 bok 배열에 argv[1]의 값을 하나씩 집어 넣어줍니다. 여기서 ret 값을 덮어 씌우는 BOF 취약점이 발생 할 수 있습니다. 

다음은 문제 풀이 할 때 표기했던 내용인데, func 함수를 disassemble 한 어셈블리 코드입니다. 보기 쉽게 주석으로 의미를 적어두었습니다.





참고 삼아서 보면 될 것 같습니다.

다음으로 gdb로 직접 돌려보면서 값을 확인 해 보도록 하겠습니다.



argv[1]의 값으로 A를 총 20개 넣었을 때의 결과입니다. func함수의 어셈블리 코드를 확인 했을 때 ebp-0x20의 위치가 bok 변수의 위치라고 명시 되어 있었으니, continue를 하면서 값을 확인 해 보았습니다.





그리고 총 20번을 돌았을 때의 결과입니다. 20바이트 직후에 나타나는 4바이트는 바로 blah 변수입니다. 어셈블리 코드를 확인 해 보면 ebp-0xc의 위치에 blah가 위치해 있다는 것을 확인 할 수 있는데, 0x20-0xc = 32-12 = 20 입니다. 그래서 20바이트 직후에 blah가 위치해있는데, 이로 인해 생기는 문제는 다음 그림을 통해 확인해보도록 하겠습니다.



위에서 설명했던 문제가 바로 빨간 박스 쳐진 부분입니다. 첫 번째와 두 번째 박스를 확인 해 보면 0xffffd8a2였던 blah 주소 값의 마지막 부분이 0x41(A)로 덮어씌워지는 것을 알 수 있습니다. 그리고 그 다음 루프에서 blah의 값을 참조하기 위해 0xffffd68c의 주소를 확인하는데, 방금 전 루프에서 blah 변수의 주소를 변경하였으므로, 엉뚱한 주소를 참조하게됩니다. 그 결과가 두 번째 네모 박스입니다.

그렇다면 어떤식으로 이러한 문제를 해결해야할까요?

저는 overflow시킬 때 blah 변수의 위치에 해당하는 부분에 blah 변수의 주소 값을 넣어주었습니다. 무슨 말이냐면, blah 변수의 값을 그대로 bok 변수에 저장하기 때문에 blah[21]~blah[24]의 위치에 blah의 주소 값을 그대로 때려박아주는 방법을 선택했습니다.

그럼 방법은 이후에 다시 설명하도록 하고, 빨간 네모 박스 쳐 놓은 부분을 확인 해보겠습니다. 




우리가 overflow를 시키고 있는 이유가 바로 ret 값을 덮어 씌우기 위함이었는데요, 그 ret 값의 위치를 알아야 페이로드를 작성 할 수 있겠죠? 첫 번째 쳐 놓은 박스 부분이 ret로 의심되는 부분입니다. 현재 스택의 구조가 (bok[20], &blah, argc, argv, sfp, ret)라고 생각 할 수 있겠습니다. 그럼 해당하는 값이 ret가 맞는지 확인 하기 위해 메인함수를 disassemble 해 보았는데, func 함수를 호출 한 직후의 위치가 기록 되어 있는 것을 확인 할 수 있습니다. 그러므로 해당 주소는 ret가 맞다고 생각하면 될 것 같습니다. 그리고 두 번째 친 네모 박스는 중간에 argv가 맞는 지 확인하기 위해 가져다 놓았습니다. argv[1]의 자리를 확인 해 보니 잘 안착 해 있네요.




그럼 본격적으로 페이로드를 작성해 보도록 하겠습니다. 페이로드 양식은 아래와 같습니다.


- payload : ./narnia8 "(Dummy 20byte) + (&blah) + (Dummy 12byte) + (system got) + (exit got) + (&argv[2])" "/bin/sh"


위 그림에선 AAAA가 Dummy Code, BBBB가 &blah, CCCC가 system got, 마지막 AAAA가 exit의 got(Dummy가 와도 무방함), DDDD가 &argv[2]입니다.

해당 페이로드를 그대로 때려박아주고 argv[1]과 argv[2]의 주소를 확인 해 보니 각각 0xffffd885, 0xffffd8b6이었습니다. 그리고 덤으로 system 함수와 exit 함수의 주소도 확인하였습니다.


- &argv[1] : 0xffffd885

- &argv[2] : 0xffffd8b6

- system got : 0xf7e63cd0

- exit got : 0xf7e56ec0




우리가 구한 주소들을 페이로드 양식에 때려박아주면 위 그림의 빨간 네모박스와 같은 형태가 나옵니다. 그대로 실행 해 주면 gdb에서 쉘이 떨어지는 것을 확인 할 수 있습니다.

하지만 gdb 환경과 bash의 환경에서는 argv[0]의 값이 다르기 때문에 argv[1]과 argv[2]의 주소가 꼬여버려 페이로드를 그대로 넣어주면 쉘이 떨어지지 않습니다. 따라서 argv[0]의 값을 맞춰주기 위해 실행 할 때 '/'를 추가하여 이름을 늘려주면서 실행시켰습니다.




/를 총 8번 사용하여 실행 시켰을 때 쉘이 떨어졌으며, narnia9의 패스워드를 알아내면서 OverTheWire Narnia 난이도는 끝을 맺도록 하겠습니다.


OverTheWire_Wargame Narnia

[ level7 -> level8 ]





level7의 소스입니다. 이전 레벨에서 볼 수 있었던 snprintf 함수의 취약성을 이용한 FSB와 비슷한 듯 합니다.

차이점으로는 main 함수에서 직접 처리하는 것이 아닌 vuln 함수에서 처리한다는 점 빼곤 없어 보입니다. 아, 또 다른 점으로는 buffer 변수의 결과 값을 출력 해 주지 않는다는 점이 있겠습니다. 자, 그럼 실행하면서 어떤식으로 풀어야 할 지 찾아보도록 하죠.



prtf() = 0x80486e0 (0xffffd64c) 부분을 확인 해 보면 괄호 안에 있는 부분은 ptrf 포인터 변수의 주소이고, 왼쪽의 값은 goodfunction 함수의 주소입니다. 이 값을 hackedfunction의 주소인 0x8048706으로 변경하여 shell을 띄우는 게 이번 레벨의 목표인 것 같습니다.

하지만 이전 레벨과 달리 buffer의 변수를 printf로 출력 해 주지 않기 때문에 얼마나 %x 서식문자를 입력 해 주어야 buffer 변수에 접근 할 수 있는지 알 수가 없습니다. 따라서 gdb를 이용해 스택 값을 확인하여 0xffffd64c의 값의 변화를 직접 확인하는 쪽으로 풀이하였습니다. 노가다죠.



vuln 함수에서 snprintf를 한 직후에 breakpoint를 걸어주고 실행하였습니다. 여기서 FSB를 위해 측정한 거리는 다음과 같습니다.


34542 = 0x8706(34566) - 16 - 8

33046 = 0x10804(67588) - 34542


위와 같이 거리를 구한 후 대입 해 주었으나, gdb 상에서 실행하였기에 파일명이 절대경로로 들어가 argv[0]이 변해서 ptrf의 주소 값이 바뀌었습니다. 그래서 그대로 대입을 해 주었으나, ptrf의 값이 바뀌지 않은 것을 보니 %8x를 한 번 사용했을 때는 buffer 변수에 접근을 못하는 듯 합니다. %8x와 거리 값들을 바꿔주변서 계속 시도하다 보면 아래와 같이 ptrf 포인터의 값이 변하는 부분을 찾을 수 있습니다.



%8x를 총 5번 사용했을 때 buffer 변수에 접근 할 수 있다는 것을 확인 할 수 있는데, 이상한 게 한 가지 있습니다.

예상대로라면 buffer 변수에 접근했을 때 ptrf 포인터의 값은 0x08048706이 되어야 하는데, 0x8706은 정상적으로 overwrite 된 반면 0x0804는 0x083c로 들어가 있는 것을 확인 할 수 있었습니다. 원인은 공부가 부족하여 찾지는 못했습니다. 계산법이 틀린건지, 놓친 부분이 있는 것인지는 잘 모르겠습니다. 혹시 아시는 분이 있다면 댓글로 지적 부탁드립니다. :D

일단 답을 찾아내기 위해... 3c에서 04까지의 거리인 0x38(56)을 빼주고 실행해봅시다.



어떻게든 답은 찾아냈습니다. :D

OverTheWire_Wargame Narnia

[ level6 -> level7 ]


Level7입니다. Narnia도 끝이 다가오네요.

코드를 보면 처음에 fp라는 이름의 int형 포인터에 puts함수의 주소를 넣어줍니다.

이는 곧 fp를 함수포인터로 사용한다는 의미가 되겠네요. 인자는 puts와 동일한 (char *)형입니다.

그리고 환경변수와 argv[3] 이후의 값들을 모두 제거하네요.

다음으로 취약점이 발생하는 strcpy문이 2번 호출됩니다. b1와 b2에 argv[1]와 argv[2]를 덮어씌워주는데, b2가 b1보다 나중에 선언되기 때문에 b1이 high address가 되겠네요. 따라서 b2에서 buffer를 overflow 시키게 된다면 b1의 값에도 영향을 미칠 것입니다.

그리고 esp의 값을 0xff000000과 and 시켜 결국 fp의 값이 스택 영역일 경우 프로그램을 종료시킵니다. 이는 스택에 쉘코드를 직접 넣어 실행 시킬 경우를 막아놓은 장치인듯 합니다.

마지막으로 b1을 인자로 fp(puts) 함수를 호출시킵니다.

대충 감이 오지 않나요? 일단 한번 실행을 해 보겠습니다.



b1과 b2의 값을 변경시켜가면서 출력을 해 봤더니 b1의 값이 8바이트 이상 입력 될 시 segmentation fault가 나타나는 것을 확인 할 수 있습니다.

b2의 값은 overflow 시켰을 때 16바이트를 입력하면 segmentation fault가 일어나는 것으로 보아, b1과 b2 사이에 dummy byte는 따로 없는 것 같습니다.

그렇다면 여기서 생각 할 수 있는 것은 b1[8]의 상위 스택에 바로 sfp, ret가 있거나 다른 경우의 수가 있다는 것입니다.

하지만 이 코드의 경우 gdb로 분석 해봤을 때 exit(1)로 끝나는 코드이기 때문에 leave;ret이 없는 것으로 보아 첫 번째 경우는 제외하였습니다.

그럼 두 번째 경우를 알아봐야 하는데, gdb를 통해 알아보도록 하겠습니다.



main 함수를 disassemble 한 상태입니다. 코드의 처음 부분에서 지역변수를 위해 0x30만큼 esp를 확보 해 준 이후 곧바로 esp+0x28의 값에 0x80483f0를 넣어줍니다.

????? 왜죠? 분명 선언은 b1, b2, fp, i 순으로 했는데...

아직 시스템에 대한 공부가 부족해서 정확하게 이해는 하지 못했습니다. 정황상으로는 선언과는 별개로 값을 직접 입력 해 줄 때에는 순서와 상관 없이 직접 입력 해 준 변수가 가장 상위의 스택에 쌓이는 듯 합니다.

아, 그리고 0x80483f0의 값은 puts@plt의 주소입니다. 결국 i, b2, b1, fp 의 순서로 스택이 나열되어 있다는 뜻이 되겠습니다.

그림에는 따로 나와있지 않지만 b1의 주소는 esp+0x20, b2의 주소는 esp+0x18입니다.

드디어 두 번째 경우의 수를 찾아냈습니다.

제가 생각하지 못했던 스택 시스템에 따라 b1 이후에 fp가 저장되어 있기 때문에 b1에 8byte를 입력했을 경우 fp에 NULL이 입력되어 마지막에 fp를 호출 할 때 segmentation fault가 나타났던 것입니다.

그렇다면 puts가 저장 되어 있는 fp에 system 함수의 주소를 꽂아주고, b1에는 b2를 이용해 /bin/sh를 넣어준다면 system("/bin/sh")라는 완벽한 결과가 나타나겠습니다.

자, 갑시다!



address of system : 0xf7e63cd0



system 함수가 정상적으로 실행되는 것을 확인 할 수 있습니다.

b2에 값을 더 추가하여 b1에 /bin/sh를 넣어주도록 합시다.


payload :

./narnia6 'A'*8 + 0xf7e63cd0 + 'B'*8 + '/bin/sh'




짜잔! 깔끔하게 쉘을 얻어냈습니다.


OverTheWire_Wargame Narnia

[ level5 -> level6 ]




자, level5입니다. 문제를 까 보니 i와 buffer 변수가 선언 되어 있으며, i 변수를 500으로 바꾸면 쉘을 띄워주겠다고 합니다.

buffer 변수는 [buffer의 크기-1]의 위치에 NULL을 집어넣기 때문에 overflow는 시키기 어려울 것 같습니다.

그래서 구글링을 잘 해보니 snprintf에 FSB가 존재한다고 합니다. snprintf는 출력 할 때 printf처럼 표준 출력을 사용하지 않고 문자열 자체로 출력하기 때문에 %x와 같은 서식문자도 buffer에 집어 넣을 수 있는 것입니다.

FTZ 이후로 기억 속에서 잊혀져 가던 FSB를 문서를 다시 보면서 어렴풋이 되찾았습니다.

차근차근 실행 해 가면서 보도록 하죠.



먼저 buffer 변수에 문자열을 집어 넣어봤습니다.

버퍼의 총 길이와 입력한 argv[1]의 값을 출력 해 주고, i의 값과 i의 주소를 뱉어냅니다.



여기서 argv[1]에 %x를 입력 해 줍니다.

그러면 snprintf에 의해 %x 문자열 자체가 buffer에 넘어가게 되고, 이를 printf에서 그대로 인자로 전달 해 줘서 %x에 해당하는 인자값을 출력해줍니다.



그럼, AAAA를 인자로 전달하고(printf의 첫 번째 인자) %x를 연달아 쓰면서 해당 인자 값이 몇 번째에 출력 되는 지 확인 해 보았습니다.

%x를 총 5번 사용했을 때 AAAA에 해당하는 값이 출력이 되었습니다.

그럼 이 위치를 이용해 FSB를 시도 해 보도록 하죠.

payload는 아래와 같습니다.


payload :

./narnia5 AAAA+[i의 주소]+%8x%8x%8x%8x+%460c%n


여기서 %460c를 입력 해 준 이유는 앞에 나온 'AAAA'와 i의 주소, %8x의 4번 출력으로 인해 8byte * 5 = 40byte이기 때문에 500에서 40을 빼서 460이 된 것입니다.

또한, i의 주소 앞에 'AAAA'를 붙여주는 이유는 %n이라는 서식문자 자체가 esp+4의 위치(다음 스택)에 있는 주소의 값을 수정해 주기 때문입니다.

그럼 계속 실행 해 보도록 하죠.



앞서 나온 buffer의 크기가 늘어났기 때문에 i의 위치가 변동되었습니다.

0xffffd6dc -> 0xffffd6cc로 바꿔주도록 하죠.



짜잔! key값을 얻어냈습니다.






References

- Format String Bug(HackerLogin 서정현 선배님)

- Format String Attacks(Tim Newsham)

OverTheWire_Wargame Narnia

[ level4 -> level5 ]



이번에는 환경변수를 사용하지 못하도록 NULL Byte를 채워 넣는 Egg hunter가 추가된 BOF 문제입니다.

스택의 주소는 이전과 동일하게 고정적이므로 nop sled와 shellcode를 이용하여 풀이하겠습니다.



먼저 값을 충분히 집어넣어 segmentation fault가 터지는 지점을 찾아냈습니다.

272바이트를 입력했을 경우 segmentation fault가 나타나는데, 이는 SFP가 0x91919191로 덮어져 잘못된 주소로 리턴했기 때문에 나타나는 증상이죠.

그러므로 buffer+272의 위치가 return address입니다.



shellcode는 24byte짜리 shellcode를 사용 할 것이며, 272 - 24 = 248byte 만큼을 0x90으로 채워주고 리턴 할 위치를 찾아보도록 하겠습니다.



ebp+0x0c에서 +4만큼의 위치에 있는 argv[1]의 값을 찾아냈습니다.

그 중 0xffffd7df를 return address로 잡도록 하겠습니다.


return address : 0xffffd7df



payload :

./narnia4 0x90*248 + shellcode(24byte) + 0xffffd7df



짜잔!

OverTheWire_Wargame Narnia

[ level3 -> level4 ]




Level3의 문제입니다.

R/W 권한과 READ ONLY의 권한으로 파일을 각각 하나씩 open 해줍니다.

output 하는 경로의 경우 "/dev/null"로 고정이 되어있네요. input의 경우에는 사용자에게 직접 입력을 받습니다. 이 때, 입력은 argv[1]로 받으며 strcpy 함수를 이용하기 때문에 길이 제한이 없습니다. 따라서 이 때 BOF가 발생하게 되는데, ofile은 값을 지정해줬음에도 불구하고 ifile보다 먼저 선언되었기 때문에 ifile에서 32바이트를 넘어가는 길이의 입력을 받을 경우 ofile의 값에도 영향을 미치게 됩니다.

이러한 스택의 특성을 이용하여 level4로 갈 수 있는 패스워드를 따 보도록 합시다.

일단 ifile의 경우 overflow를 시키면 ofile의 값을 덮어씌우게 되는데, 이럴 경우 ofile의 포인터는 overflow 된 ifile의 중간에 위치하게 됩니다. 따라서 아래와 같은 payload를 사용하면 두 개의 파일에 따로따로 접근 할 수 있게 됩니다.




ifle과 ofile을 이어 붙인 그림입니다.

입력을 받을 수 있는 argv[1]에는 ///////////////////tmp/fstr3am_/tmp/fstr3am_/a 를 넣어줍니다.

그럼 첫 번째 fstr3am_ 이후의 위치가 원래 ofile의 시작 포인터입니다. 따라서 ofile을 끄집어 낼 경우 /tmp/fstr3am_/a가 딸려나오게 됩니다.

그리고 ifile을 꺼낼 경우 원래 ifile의 길이인 24byte를 초과했기 때문에 ofile의 마지막인 a 이후에 나오는 NULL Byte까지 인식을 하여 /tmp/fstr3am_/tmp/fstr3am_/a이 ifile의 전체 경로가 됩니다.

그렇다면 /tmp/fstr3am_/tmp/fstr3am_/ 의 경로에 /etc/narnia_pass/narnia4의 symbolic link를 걸어준 a라는 이름의 파일을 생성해주고, /tmp/fstr3am_/ 의 경로에 비어있는 a라는 파일을 하나 만들어주면 /tmp/fstr3am_/a에 level4로 통하는 패스워드가 복사 될 것입니다.



위와 같이 a라는 파일을 두 개 만들어줍니다.



그리고 위에서 설명했던 payload대로 입력을 해 주면 패스워드가 출력됩니다.

OverTheWire_Wargame Narnia

[ level2 -> level3 ]



이번 문제는 기본적인 BOF 문제입니다. strcpy 함수로 buf 변수에 argv[1]의 값을 넣어주는데, 인자 길이를 제한하지 않기 때문에 BOF 취약점이 존재합니다.

이를 이용하여 스택에서 쉘코드를 실행시키는 형태로 풀이하도록 하겠습니다.



먼저 스택의 크기를 가늠하기 위해 4바이트씩 늘려 가며 A를 출력해줍니다.

140바이트를 출력했을 때 segmentation fault가 터지는 것으로 보아 buffer 128byte + dummy 8byte + sfp 4byte + ret 4byte 의 구조를 띄고 있는 것 같습니다.

ret 값에 들어갈 리턴 값의 위치를 구하기 위해 gdb로 스택을 살펴보도록 하겠습니다.



main 함수의 strcpy 부분에 breapoint를 걸고, argv[1]의 위치를 찾아봅니다.

140개의 A를 지나 return address(BBBB) 뒤의 0x90이 있는 지점을 찾아 그 중 한 곳의 주소를 집어줍니다.

이렇게 특정 스택 주소를 집어주는 이유는 여러 번 실행한 결과 랜덤 스택이 적용되어있지 않기 때문에 0x90중 한 곳에만 return 시켜준다면 뒤에 있는 쉘코드가 실행 될 것입니다.


payload : ./narnia2 A*140 + 0xffffd872 + 0x90*100 + 25byte shellcode



짜잔!

OverTheWire_Wargame Narnia

[ level1 -> level2 ]




이번 문제는 'EGG'라는 환경변수에 들어있는 값을 ret 포인터 변수에 값을 집어 넣은 후 해당 주소를 호출하는 형태를 띄고 있습니다.

export로 EGG 환경변수에 쉘코드를 박아주면 될 것 같습니다.



짜잔!

OverTheWire_Wargame Narnia

[ level0 -> level1 ]


Bandit와 Leviathan을 지나 드디어 Narnia로 넘어왔습니다.

level0부터 바로 시작하도록 하겠습니다.


OverTheWire 웹페이지 narnia 카테고리를 눌러보면 하단에 위와 같은 문구가 있습니다.

지금까지는 보통 홈 디렉토리에 다음 레벨로 넘어갈 수 있는 바이너리나 정보가 있었는데, 이번에는 /narnia/ 디렉토리에 전부 몰려있는 듯 합니다.

/narnia/ 디렉토리에서 level1로 넘어갈 수 있는 정보를 찾아보도록 하죠.



홈 디렉토리에는 아무 정보도 있지 않고, /narnia 디렉토리로 가 보니 level8까지의 정보가 모두 들어있습니다.

바이너리와 소스코드 모두 주어져 있으니 narnia0.c를 살펴보도록 합시다.



첫번째로 long형 데이터인 val이 'AAAA'로 선언 되어 있으며, buf변수는 20바이트로 선언이 되어있습니다. 그리고 쉘을 띄워주는 조건으로 val 변수가 0xdeadbeef가 되길 원하고 있습니다. 바로 위의 scanf 부분에서 24바이트만큼 입력을 받는 것을 보니 char 배열과 long형 변수 사이에 dummy byte는 따로 없는듯 합니다. 바로 20바이트 dummy + 0xdeadbeef를 입력해주면 쉘이 띄워질 것 같습니다.



짜잔!

+ Recent posts