본문 바로가기

보안/DreamHack

Dreamhack Return Oriented Programming

64bit 아키텍처이고 canary, NX가 활성화 되어있으므로, 카나리를 우회하고 return to library에서 사용했던 방법을 통해 system('/bin/bash')로 쉘을 얻을 것이다.

 

main함수에서 0x40만큼의 스택 프레임이 확장되고 그 중 rbp-0x8부터 0x8바이트만큼은 fs에서 0x28만큼 떨어진 곳에서 가져온 값인 카나리가 들어간다. 카나리의 첫 바이트가 Null이므로 첫 입력에서 0x40-0x08+0x01 = 0x39만큼의 값을 입력해 카나리의 첫 NULL을 값으로 채워주면 이후 7바이트만큼의 canary 값이 같이 나올 것이다.

 

따라서 'A'를 0x39번 입력한 후에 카나리 값을 추출할 수 있다.

 

이후 system('/bin/bash')를 실행해야 하는데 system함수가 바이너리에 의해 호출되지 않았기 때문에 got안에는 존재하지 않는다. 다만 read, printf, write 등의 함수들은 got에 등록이 되기 때문에 이를 이용해 system함수의 주소를 찾을 것이다.

 

우선 libc 안에서의 read함수, system함수의 거리를 D라고 하자

그럼 라이브러리 파일이 메모리에 매핑 되었을 때 read함수와 system함수의 거리 또한 D이다.

 

따라서 read함수의 got값 즉, 라이브러리 파일이 메모리에 매핑되었을 때 read함수의 주소를 알면 비록 got에는 등록이 안되어있지만 system함수의 주소 또한 알 수 있는 것이다.

 

따라서 리턴 가젯을 통해 

 

1. read함수(꼭 read함수일 필요는 없음 바이너리에서 호출한 함수 중 아무거나 해도 괜찮음)의 got값을 추출한다.

2. libc 파일 내에서 해당 함수와 system함수의 거리 차를 구한다.

3. 해당 차이를 통해 메모리에 매핑 된 라이브러리 파일의 system함수의 주소를 구한다.

또는 

1. read함수(꼭 read함수일 필요는 없음 바이너리에서 호출한 함수 중 아무거나 해도 괜찮음)의 got값을 추출한다.

2. got값과 해당 함수의libc 주소값을 구한다.

3. got 값 - libc주소값 을 통해 메모리에 매핑된 라이프러리의 시작 주소를 구한다.

4. 라이브러리의 시작 주소 + libc에서의 system함수 주소값 을 통해 system함수의 주소를 구한다.

 

우선 system함수의 주소를 구한 후, read함수를 실행시켜 system함수의 주소와 '/bin/bash' 인자를 read함수의 got로 입력을 받는다. 이후 read함수를 실행하면 read함수의 got값을 읽어 실행하는데 이는 system함수의 주소로 덮였기 때문에 system함수가 실행이 된다. 그리고 함께 입력한 '/bin/bash'를 인자로 갖게 된다.

 

페이로드는 다음과 같다.

 

payload가 들어간 스택을 그려보면

다음과 같다. 현재는 main함수의 leave까지 마친 상태이다.

main함수의 ret을 실행하면

리턴 가젯의 pop rid 실행하면

ret 을 실행하면

pop rip

jmp rip이므로 

pop rsi, pop r15를 실행하면

다음과 같다. 함수의 첫번째 인자인 rdi = 1, rsi = read_got로 들어가게 된다.

이때 r15의 값을 1로 설정 했는데 해당 값은 함수의 인자와는 상관이 없어서 1을 넣었다. 0을 넣어도 write함수는 잘 호출된다.

64bit 운영체제에서 함수의 인자는 rdi, rsi, rdx, rcx...순서로 들어가는데 rdx 가젯은 찾기 힘들다고 한다. 대신 rdx에는 default로 충분히 큰 값이 들어간다고 하니 write함수의 앞 인자 2개만 넣어도 size가 모자랄 일은 없다.

 

이후 ret을 실행하여 

pop rip

jmp rip

를 수행하면

write(1, read_got, default_size)함수가 실행이 되며 read의 got값이 나오게 된다.

이후 write함수가 호출이 되었으니 write함수의 에필로그인 ret이 실행이 될 것이다. (leave ret이 아닌 이유는 일단 디버깅을 했을 때 write함수는 스택프레임을 사용하지 않기 때문에 leave가 없는 것이라고 추측한다.)

write함수 이후 ret을 실행하면

rip와 rsp가 이렇게 바뀌게 된다. 이렇게 리턴가젯이 실행되다 보면 이후 read(0, read_got, default_size)가 실행이 되고

이 read함수가 실행이 되었을 때

send를 통해 system함수의 주소와 '/bin/bash'문자열을 read로 읽을 것이다.

이렇게 되면 결과적으로 read_got 즉, read함수를 호출했을 때 read@plt는 read@got를 참조를 하는데 read@got가 read함수의 주소가 아니라 system함수의 주소를 갖고 있는 것이 되므로 이후 read@plt를 통해 read함수를 호출하면 system함수가 호출이 되는 것이다.

read함수가 실행이 되었고 ret 이전의 스택 프레임이다.

 

ret 이후 

스택 프레임은 다음과 같고 pop rdi를 하면

함수의 첫번째 인자로 들어가는 rdi에 read_got+0x8위치의 값인 system_addr 이후 입력한 값인 '/bin/bash'문자열이 들어가게 되고 ret을 통해

pop rip; jmp rip하면

아무 의미 없는 가젯인 ret이 실행이 되고 ret을 통해 pop rip하면 rsp= rsp+8이 되고 jmp rip를 하면 첫번째 인자를 '/bin/bash'로 갖는 read_plt가 실행이 된다.

근데 read_plt를 통해 read의 got를 참조하면 system함수가 호출이 되므로 결과적으로 system('/bin/bash')가 실행이 된다.

 

실행시키면 쉘이 얻어지는 것을 볼 수 있다.

 

해당 문제를 풀며 많은 지식을 습득할 수 있었다.

 

우선 기본적으로, 32bit 운영체제에서는 접두사 E가 붙어 ebp ,esp 였지만 64bit 운영체제에서는 접두사가 E에서 R로 바뀌어 rbp, rsp로 바뀐다.

 

그리고 기본적으로 함수의 에필로그인 leave ret에서 스택프레임을 할당하지 않고 함수를 호출했으면 leave는 생략되고 ret만 실행이 된다.

 

함수의 인자로 들어가는 레지스터에는

첫번째 rdi

두번째 rsi

세번째 rdx

네번째 rcx

다섯번째 r8

여섯번째 r9가 있고

7개 이상의 인자부터는 스택을 사용한다.

 

ret은 system함수의 movaps를 우회하기 위한 아무쓸모없는 가젯이다.

 

어려웠지만 그만큼 습득한 것이 많은 문제였다.

 

++발생했던 오류

로컬에서는

p = process('./rop')

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")로 실행을 했고

 

원격으로 실행할때는 p = process('./rop') => p = remote ("dreamhackdjWjrnwjWjrn", 55555)

이런식으로 바꿔줬을 때 EOF 에러가 발생했다.

 

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") => libc = ELF('./libc.so.6')

로 바꿔주니 EOF 에러가 해결되었다.

 

'보안 > DreamHack' 카테고리의 다른 글

Dreamhack basic_rop_x86  (0) 2024.10.11
Dreamhack basic_rop_x64  (0) 2024.10.10
Dreamhack Return to Library  (1) 2024.10.07
Dreamhack ssp_001  (1) 2024.10.05
Dreamhack Return to Shellcode  (0) 2024.10.03