기타

Attack Lab(어택랩) 풀이 (ctarget 1~3, rtarget touch 2~3) (시스템프로그래밍)

restudy 2022. 7. 9. 22:21
반응형

이 포스트에서는 시스템 프로그래밍(system programming)의 실습 과제 중 하나인 Attack Lab(어택 랩)의 풀이를 다룬다.

 

Attack Lab은 버퍼 오버플로우를 활용하여 문제의 의도대로 프로그램을 조작하여 원하는 데이터를 얻는 등의 처리를 하는 과제이다.

프로그램은 ctarget과 rtarget의 2개가 존재하며, ctarget은 touch1, touch2, touch3의 3개의 함수가 존재하고 rtarget은 touch2, touch3의 2개의 함수가 존재한다. (편의상 이들을 Phase 1 ~ 5로 칭하기도 한다.)

ctarget은 code injection attack을 이용하여 attack하고 rtarget은 return-oriented programming을 이용하여 attack하는 것이 목적이다.

 

서론을 줄이고 바로 풀이를 소개한다.


ctarget의 level1 (Phase 1)

 

 

문제 파일을 참고하면 함수는 위와 같이 생겼음을 알 수 있다.

 

 

 

그리고 touch1 함수를 호출했을 때 위와 같은 문자열이 출력됨을 미리 인지할 수 있다.

 

우선 test 함수가 getbuf 함수를 호출하는 것을 확인했으므로 getbuf 함수의 어셈블리코드 를 확인해보자.

 

 

 

버퍼의 크기가 0x18 bytes임을 확인할 수 있다.

따라서 0x18 bytes보다 긴 입력을 주면 스택 오버플로우가 일어나서 리턴 주소를 덮어씌우게 될 것을 예상할 수 있다.

그런데 우리는 touch1 함수를 호출하는 것이 목적이므로 원래 리턴 주소가 있던 위치에 touch1 함수의 주 소를 적어두면 touch1 함수를 호출할 수 있을 것이다.

그래서 touch1 함수의 주소를 일단 알아야 할 것이다.

 

 

 

확인해보면 touch1 함수의 시작 주소는 0x40193d이다.

필요한 정보를 모두 알았으니 이제 vi sol1.hex를 통해 hex 파일을 만들고 다음과 같이 작성해준다.

 

 

 

위에서 언급했듯 0x18 bytes보다 긴 입력을 주면 스택 오버플로우가 일어나서 리턴 주소를 덮어씌우게 될 것을 예상할 수 있다고 하였으므로 우선 0x18 bytes 동안은 아무 dummy 값 이나 채워놓아도 되지만, 편의상 00으로 채워두었다.

그 다음 0x18 byte 뒤에는 touch1 함 수의 주소를 리턴 주소 대신 채워둔다.

이 때 little endian 양식에 맞게 하위 단위부터 바이 트를 채워서 써야한다.

그리고 저장하고나서 문제에 작성된 명령어대로 채점을 해보면 성공적으로 touch1이 호출된 것을 확인할 수 있다.

 

 

 

ctarget의 level2 (Phase 2)

문제 파일을 참고하면 touch2 함수의 형태는 다음과 같다.

 

 

 

따라서 우리는 touch2 함수를 단순히 호출만 하는 것이 아니라 적절한 cookie의 값을 찾아서 같이 넘겨주어야 함을 확인할 수 있다.

그래서 어셈블리 코드를 확인해보면 다음과 같다.

 

 

 

edi 레지스터에 들어있는 것은 val일 것이고, +16행의 cmp 문에서 비교가 이루어짐을 확인 할 수 있다.

결론은 우리는 touch2 함수가 호출되기 전에 rdi(edi) 레지스터에 cookie에 해당 하는 값을 저장하고, 그 다음에 touch2 함수를 호출시키면 edi 레지스터에는 cookie 값이 들어있을 것이고 그러면 검사를 통과하여 올바른 문자열을 출력시키도록 만들 수 있을 것이다.

나에게 주어진 cookie 값은 다음과 같다.

 

 

 

그러한 조작을 위해서는 우선 어셈블리코드를 작성한 뒤 해당 내용을 기계어로 변경시켜주는 과정이 필요할 것이다.

그래서 appendix B의 Generating Byte Codes에 대한 내용을 먼저 숙지해야했다.

 

어쨌든 필요한 부분을 읽고나서 example.s라는 파일을 만들어서 다음과 같이 필요한 기능을 가진 어셈블리코드를 작성했다.

 

 

 

여기에서 pushq 옆의 주소는 return 했을 때 돌아갈 위치이므로 여기에 touch2의 시작 주소를 작성해주었고, movq 명령어를 통해 rdi 레지스터에 cookie의 값을 옮겨주는 명령어를 추가해주었다.

 

 

 

그 다음 위와 같은 과정으로 .s 파일을 .o 파일로 만들어주고 .txt 파일로 변행해준뒤 기계어로 작성된 값들을 확인해준다. (pdf에서는 .d로 바꾸라 했는데 편의상 .txt로 변형했다.)

 

 

 

이제 필요한 것은 getbuf 함수 종료 후 이동할 이 명령문이 존재할 주소이다.

 

 

 

getbuf 함수의 어셈블리코드를 다시 확인해보면 rdi 레지스터에 rsp-0x18 값이 저장되어 있음을 확인할 수 있다.

직접 처음 rsp 값에서 0x18을 빼도 되고 rdi 레지스터 값을 직접 확인 해보아도 좋다. 어쨌든 확인해보면 다음과 같다.

 

 

 

그러면 0x55631518이 리턴 주소에 들어갈 값인 것을 확인할 수 있다.

이를 바탕으로 sol2의 hex 파일을 작성해준다.

 

 

 

그 다음 level 1에서 했던 것과 마찬가지로 채점을 해보면 다음과 같이 통과했음을 확인할 수 있다.

 

 

 

 

ctarget의 level3 (Phase 3)

 

 

level 3에서도 마찬가지로 함수 touch3의 구조를 알려준다.

확인해보면 올바른 문자열을 같이 input으로 전달해주어야 통과할 수 있다.

대충 구조를 파악했으면 이제 어셈블리 코드를 확인해본다.

 

 

 

구조를 보면 rdi 레지스터에 문자열의 주소를 저장한 뒤 hexmatch 함수를 호출해서 cookie와 비교함을 알 수 있다.

이번 문제도 마찬가지로 rdi에 문자열 주소를 저장하는 명령어를 수행시키고 명령어가 끝난 다음 touch3의 주소를 두어 실행되게 하면 될 것이다.

 

cookie는 역시 cookie.txt에 들어있는 값일 것이므로 확인해보면...

 

 

 

0x71e9f190임을 확인할 수 있다. 그런데 문자열 “71e9f190"을 hex로 바꾸어주어야 하는데, 이것은 pdf 파일에 잘 정리되어 있다.

아무튼 변경하면 hex 값은 ”37 31 65 39 66 31 39 30“이다.

여기에 null 문자에 해당하는 ”00“이 추가되어야 한다.

 

이제 hex 파일을 작성해야 하는데 level 2에서 한 것 처럼 어셈블리 코드를 작성해주자.

여기에서는 리턴 주소가 (문자열을 생각하기 전에 우선 2개의 리턴 주소가 저장되고 그 다음에 저장되므로 rsp 값에 첫 번째 리턴 주소가 저장되고 rsp + 0x8에 두 번째 리턴 주소가 저장되므로 rsp + 0x10 자리에 문자열이 들어가면 될 것이라는 것을 생각할 수 있다.

그리고 rsp 값은 위에서 구한 값에 0x18을 더해서 구해도 되고 다음과 같이 *getbuf 위치에 breakpoint를 걸고 구해도 된다.)

 

 

 

위처럼 breakpoint를 걸고 rsp - 0x18이 수행되기 전에 rsp 주소를 확인해주면 0x55631530 임을 확인할 수 있다.

여기에 위에서 설명한대로 0x10을 더해주면 0x55631540이 된다.

그래서 어셈블리 코드에서 rdi에 이 값을 저장해주도록 어셈블리 코드를 다음과 같이 작성한다.

 

 

 

위와 마찬가지로 변환의 과정을 거치면 다음과 같은 hex 값들을 확인할 수 있다.

 

 

 

이제 모든 내용을 얻었으니 sol3.hex를 작성한다.

 

 

 

이제 작성한 sol3.hex를 바탕으로 채점을 해보면 다음과 같은 결과를 얻을 수 있다.

 

 

 

 

rtarget의 level2 (Phase 4)

level1이 없길래 잘못 본 줄 알았는데 ROP에는 touch2 함수와 touch3 함수 두 개의 함수만 존재한다.

어쨌든 두 개의 level을 해결하면 되는데 먼저 level2에 정리된 내용들을 요약하면 다음과 같다.

 

 

 

rtarget 프로그램에는 위와 같은 함수들을 포함하고 있고, 또 이들을 어셈블리 코드로 나타내어보면...

 

 

 

위와 같은 16진수 sequence들을 포함하고 있는데, 이들을 적절히 이용하여 attack을 수행해 주면 되는 것이다.

그래서 이 문제를 해결하는데에는 많은 함수들의 어셈블리코드들이 필요한데, 이에 필요한 정보가 appendix에 정리되어 있었다.

그래서 이를 참고하여 앞에서도 했던 것처럼, objdump 명령어를 이용하여 rtarget 프로그램을 어셈블리 코드로 변형해준다.

 

 

 

변형하고 나서 vi로 열어보니 어셈블리코드가 2500줄 정도 있었는데, 다시 pdf 파일을 제대 로 읽어보니까 “The gadget farm is demarcated by functions start_farm and end_farm in your copy of rtarget. Do not attempt to construct gadgets from other portions of the program code.”라는 정보가 있어서 이를 참고하여 부터 까지의 코드만 살펴보았다.

 

touch2 함수는 아까보았듯 cookie의 값을 비교하여 같아야만 통과가 되는데, ROP에서 우리 는 위에 보이는 함수들을 비롯한 farm 범위에 있는 어셈블리코드의 일부를 이용하여 통과 할 수 있다.

그래서 가능할 수 있는 함수의 형태 중 하나로 (여기에서 명령어들을 떠올리는 과정은 pdf 파일에 있는 표에 나타나있는 명령어들을 참고하는 방법밖에 없다) 다음과 같은 순서를 생각해볼 수 있다.

 

우선 popq %rax / (cookie value)를 통해 %rax에 쿠키를 넣어준다.

그 다음 movq %rax, %rdi로 %rdi 레지스터에 쿠키 값을 옮겨준다.

그 다음 touch2 함수 주소를 넣어서 그 쪽으로 리턴이 됨과 동시에 쿠키 값을 보낸다.

 

이러한 함수 구조를 만들기 위해서는 우선 farm 범위에 해당 명령어가 존재하는 부분들을 모두 찾아야 하는데, 우선 popq %rax를 위해서는 다음 표를 참고할 수 있다.

 

 

 

따라서 hex sequence들 중 58이 있는 부분을 찾으면 되는데, 해당 부분만 필요한 것이 아니라 다음을 참고해주어야 한다.

 

 

 

위와 같이 58에 c3이 붙어있는 부분을 찾아야 popq %rax를 하고 명령이 끝날 수 있을 것이다.

그리고 nop에 해당하는 0x90은 아무런 수행을 하지 않으므로 중간에 껴 있어도 상관이 없다.

그래서 해당 조건을 만족하는 부분들을 찾아보면 다음과 같이 찾을 수 있다.

 

 

 

여기서 0x401b3a가 아닌 0x401b3f를 호출하면 58에 해당하는 부분부터 읽게 될 것이다.

따라서 popq %rax에 해당하는 코드를 만들 수 있다. 그 다음 movq %rax, %rdi라는 명령어가 필요한데 이를 위해서는 다음의 표를 참고해야 된 다.

 

 

 

여기서 movq %rax, %rdi에 해당하는 hex 코드는 48 89 c7이다.

그래서 해당 부분을 어셈블리 코드에서 찾아주면 다음과 같다.

 

 

 

여기서도 마찬가지로 0x401b28을 호출하면 48 부분부터 수행이 될 것이다.

마지막으로 touch2 함수의 주소를 확인해보자.

 

 

 

위 캡쳐 사진에서도 확인할 수 있듯 touch2의 주소는 0x401969이다.

이제 hex 파일을 작성해보자.

 

 

 

우선 윗 부분의 0x18 bytes의 “00”들은 버퍼 크기만큼 덮기 위해 dummy 값을 채워준 것이고 (padding), 그 다음 popq %rax에 해당하는 hex sequence를 포함하는 코드의 주소, 그 다음줄은 쿠키 값 0x71e9f190, 그 다음 줄은 movq %rax, %rdi에 해당하는 hex seqence를 포함하는 코드의 주소, 그리고 마지막 줄은 touch2 함수의 주소이다.

그리고 이 sol4.hex를 저장한 뒤 채점을 해보면 다음과 같은 결과를 얻을 수 있다.

 

 

 

 

rtarget의 level3 (Phase 5)

ctarget의 level3과 같이 문자열의 주소를 rdi 레지스터에 담아서 전달하면 된다.

그런데 우리는 popq, movq, movl만 가지고는 문자열의 주소를 전달할 수 없다.

최소한 현재 위치한 rsp 주소에 특정 값을 더해야 스택에 저장되어있는 문자열의 주소를 구할 수 있을 것인데, 이를 위해서는 최소한 덧셈 연산 정도는 필요하다.

그런데 다행히도 farm의 여러 함수들을 살펴보면 단순한 상수 lea, movl이 아닌 다른 기능을 가진 함수가 딱 한 가지 존재한다.

 

 

 

이 함수는 %rsi * 1 + %rdi 즉 rsi 레지스터에 저장된 값과 rdi 레지스터에 저장된 값을 더해서 rax 레지스터에 저장하는 연산을 수행한다.

따라서 이를 이용하면 rsi 레지스터에 저장된 값에 특정 값을 더해 문자열 주소를 rax 레지스터에 저장할 수 있을 것이다.

그리고 또 한 가지의 문제점은 우리가 원하는 source 레지스터에서 원하는 dest 레지스터로 이동하는 명령어가 없다는 것이다.

그래서 여러가지 가능한 movq S, D 명령어들을 찾아서 정리해보고 그들 중 일부를 조합하여 원하는 명령어를 간접적으로 만들 수 있을 것이다.

그래서 모든 함수들에서 찾을 수 있는 명령어들을 정리해보면 다음과 같다.

 

 

 

0x401b28 : 48 89 c7 c3 = movq %rax, %rdi / retq

0x401b29 : 89 c7 c3 = movl %eax, %edi / retq

 

 

 

위랑 같으므로 필요하지는 않다.

 

 

 

0x401ba7 : 48 89 e0 c3 = movq %rsp, %rax / retq

0x401ba8 : 89 e0 c3 = movl %esp, %eax / retq

 

 

 

0x401bb4 : 89 d6 20 c0 c3 = movl %edx, %esi / andb %al, %al / retq

 

 

 

0x401bcf : 89 ca 90 c3 = movl %ecx, %edx / nop / retq

 

 

0x401bf1 : 89 e0 c3 = movl %esp, %eax / retq - 위에도 이미 언급됨

 

 

0x401bf7 : 48 89 e0 90 c3 = movq %rsp, %rax / nop / retq - 이것도 이미 언급됨

89 e0 90 c3은 위에도 있음

 

 

 

0x401c13 : 89 c1 90 90 c3 = movl %eax, %ecx / nop / nop / retq

 

 

0x401c20 : 89 e0 c3 - 위에도 이미 언급됨

 

그래서 정리해보면 우리가 쓸 수 있는 명령어들은...

 

- popq %rax - lea (%rdi, %rsi, 1), %rax

- movq %rax, %rdi (movl %eax, %edi)

- movq %rsp, %rax (movl %esp, %eax)

- movl %edx, %esi (이후 andb %al, %al 수행하지만 무시 가능)

- movl %ecx, %edx - movl %eax, %ecx

 

이 정도이다.

우리는 이것들을 가지고...

 

- rsp를 rdi에 옮기고

- rdi와 (rsp와 문자열 주소 사이의 gap에 해당하는) offset 값을 rsi에 넣어 add 연산을 해 주고

- rax에 저정된 계산된 값을 다시 rdi로 옮긴 뒤 touch3 함수를 호출해주면 된다.

 

그러려면 다음과 같은 순서로 연산을 수행하면 될 것이다.

 

- movq %rsp, %rax : a7 1b 40 00 00 00 00 00

- movq %rax, %rdi : 28 1b 40 00 00 00 00 00

- popq %rax : 3f 1b 40 00 00 00 00 00

- offset에 해당하는 값 : 48 00 00 00 00 00 00 00 (rsp 값을 넣은 지점과 string 위치가 48 bytes 차이가 나므로)

- movl %eax, %ecx (어차피 offset 값은 몇 자리 안되니까) : 13 1c 40 00 00 00 00 00

- movl %ecx, %edx : cf 1b 40 00 00 00 00 00

- movl %edx, %esi : b4 1b 40 00 00 00 00 00

- add_xy 함수 주소 : 53 1b 40 00 00 00 00 00

- movq %rax, %rdi : 28 1b 40 00 00 00 00 00

- touch3 함수 주소 : 7a 1a 40 00 00 00 00 00

 

 

 

- cookie의 hex 값 : 37 31 65 39 66 31 39 30 (ctarget 할 때도 구함)

- “00” (null 문자)

 

그래서 위의 내용을 그대로 hex 파일을 작성해준다. (물론 0x18 bytes 크기의 padding은 그대로 해준다.)

 

 

 

그 다음 채점을 해보면 다음과 같은 결과를 얻을 수 있다.

 

 

 

이로써 Attack Lab의 모든 과제를 해결하였다.

 

 

 

반응형