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 난이도는 끝을 맺도록 하겠습니다.