540 likes | 1.1k Views
Return to Dynamic Linker. 리눅스 ASLR/DEP 환경에서 동적 링커를 이용한 원하는 함수 호출 진 용휘. 목차. 기존 리눅스의 공격기법 및 방어기법 소개 Return To Dynamic Linker 시연 ( PoC 코드 설명 ). 기존 리눅스 공격방법. 메모리의 모든 영역이 실행 가능하다. 스택 오버플로우. 지역변수가 넘친다면?. ::. main 함수의 스택 구조. buf[256]. RET. SFP. main(). buf[256]. SFP. RET.
E N D
Return to Dynamic Linker • 리눅스 ASLR/DEP 환경에서 동적 링커를 이용한 원하는 함수 호출 • 진 용휘
목차 • 기존 리눅스의 공격기법 및 방어기법 소개 • Return To Dynamic Linker • 시연 ( PoC 코드 설명 )
기존 리눅스 공격방법 • 메모리의 모든 영역이 실행 가능하다.
스택 오버플로우 • 지역변수가 넘친다면?
:: main 함수의 스택 구조 buf[256] RET SFP main()
buf[256] SFP RET ………………………………… ……. ……. • SFP: 함수 다음 명령의 스택 위치 • RET: 함수 다음 명령의 실행 위치
char buf[256] SFP [4] RET [4] 공격해보기 • RET을 버퍼의 위치로 조작한다! • 버퍼에 내가 원하는 쉘코드를 넣는다. [ ~ ] : 바이트
RET, SFP의 저장 방식 • 스택, 코드의 메모리 주소를 담고 있어야 한다. • 인텔 32비트 x86 컴퓨터 기준으로 32비트의 주소를 지원한다.그래서 주소는 0~2^32-1까지이다. • 32비트 = 4바이트
RET, SFP의 저장 방식 2 • 32비트 주소 = 4바이트 • Little-Endian 저장방식 • 주소가 0x0123ABCD라면? 0xCD 0xAB 0x23 0x01 4바이트 참고 : 16진수는 0~9, a~f까지 있는 진법, 2자리로 0~255까지 표현 가능
리눅스 방어기법의 등장 - DEP, ASLR -
DEP 란 ? • Data Execution Prevention • “데이터 영역”을 “실행 불가능한 영역”으로 지정 가능 • 최신 리눅스에서 코드를 제외한 거의 모든 영역에 적용되어있다.
DEP가 적용된 메모리 구조 r: 읽기, w: 쓰기, x: 실행, p: 프로세스 전용 -가 있으면 “불가능”
DEP가 적용된 메모리 구조 r: 읽기, w: 쓰기, x: 실행, p: 프로세스 전용 스택: 읽기/쓰기 전용!
DEP FOR HACKERS • DEP가 적용되있는 경우 • 스택 영역에 코드를 올리고 점프시킬 수 없다. • 다른 방법을 써보자!
DEP FOR HACKERS공격 방법: Return To Library • 외부 라이브러리의 함수를 이용하는 방법 • 이미 있는 함수들(gets, system 등등)을 이용한다.
DEP FOR HACKERS공격 방법: Return To Library • RET를 실제 외부 함수 주소로 이동시킨다.주소는 디버거 등으로 얻을 수 있다. [ ~ ] : 바이트 libc.so.6 (C 표준 함수 라이브러리) … SFP [4] RET [4] RET2 [4] 인자1 [4] 인자2 [4] … ~ printf system gets ~ function
DEP FOR HACKERS공격 방법: Return To Library • 실행파일에서 그 함수를 호출하는 부분을 써도 된다.
ASLR • 라이브러리, 실행 파일이 랜덤한 위치에 로딩된다
ASLR FOR HACKERS • 라이브러리 주소가 계속 바뀌어서원하는 함수를 쓰기 어렵다. • 해결법은 없을까?
ASLR FOR HACKERS기존의 해결법 • 한번 나온 라이브러리 주소를 기준으로 계속 대입한다.→ 시간이 오래 걸린다. • 실행 파일에 있는 외부함수 주소를 얻어올 방법을 찾는다.→ !!
ASLR FOR HACKERS기존의 해결법: 공격 절차 • 한 함수의 실제 주소를 얻어온다. • 그 부분의 상대적인 위치를 찾는다. • 라이브러리 베이스를 계산한다. • 목표 함수의 상대주소를 더하면 끝!
ASLR FOR HACKERS기존 방법들의 한계 • 해당 라이브러리의 버전을 알아야한다. • 해당 라이브러리의 바이너리를 가지고 있어야 한다.
ASLR + DEP • 최신 리눅스에는 • 외부 라이브러리, 스택의 주소는 랜덤이다. • 코드 영역 외의 실행은 불가능하다.코드 영역은 기본적으로는 덮어쓸 수 없다.
Return to Dynamic Linker발표할 기법 • ELF 실행파일은 실행할 때 • 모든 외부함수의 주소를 로딩하지 않는다? • “Lazy Binding”
Lazy Binding 이란 ? • “뒤늦게 고정시킨다” • 실행할 때 “함수의 위치를 얻는 함수”만 알아온다.
Lazy Binding 장점 • 실행 속도를 높여준다 • -> 거의 모든 프로그램에 쓰인다
Lazy Binding 인가 ? • lazy binding 방식 사용 여부는file 명령어로 확인할 수 있다.
Lazy Binding 구조 • PLT • GOT • _dl_runtime_resolve: 로딩 과정을 수행한다.
PLT • PLT는 외부 라이브러리를 “연결” 해주는 함수 • 코드 내에서도 실제 주소 대신 이 함수를 호출
이부분은 DL에 쓰인다. PLT • PLT 함수는 이렇게 생겼다.
1 2 PLT • 다시보자. 실행 중에는 이런 식으로 변한다.
GOT • PLT 함수들은 GOT라는 테이블의 주소로 점프한다. • GOT는 외부 라이브러리의 함수/변수의 주소를 저장한다.
Lazy Binding 과정 1 • GOT+4 주소에 로딩 정보를 놓는다. • 모든 함수는 맨 처음 공통된 과정을 실행한다.과정: 1. 개별 함수 번호를 스택에 넣는다 : push 0x0 2. 공통적인 주소로 점프한다. : jmp _loader • _loader은 가상의 함수이다.
Lazy Binding 과정 2 • … 3. 프로그램 파일의 정보를 스택에 넣는다 : push (GOT+4) 4. _dl_runtime_resolve 함수로 점프한다. 5. 함수 정보, 프로그램의 정보로 목표 함수를 불러온다. • _dl_runtime_resolve 함수의 주소는 GOT+8에 위치한다.로딩 후에도 함수는 유지된다. • 로딩 후 GOT에는 실제 함수 주소가 들어간다.
Return to Dynamic Linker공격하기 • Dynamic Linker 에는 두 가지 인자가 들어감 • 두 인자 조작이 가능하다면 원하는 함수 획득 가능
공격하기1. 첫번째 인자만 조작 • 함수별로 다른 첫번째 인자만 조작한다. • 조작해야할 범위가 줄어들어서 편하다. • 함수 정보가 담긴 위치에서 벗어나는 번호를 넣는다.
공격하기2. 두 인자 모두 조작 • 입맛대로 정보를 구성 가능하다. • 비교적 많은 공간이 필요하다. (약 500바이트) • 비효율적이다.
공격하기 - 최적의 방법첫번째 인자로 공격 • PoC 코드 • 3가지 퍼즐(구조체)이 서로 엉켜있는 형태 • 퍼즐끼리 서로 연결되어있고, 마지막으로 함수이름에 연결된다. • 로더 함수에서 주소 획득, 실행까지 해준다.
조금 자세히 들어가보자 • _dl_runtime_resolve 함수 정보 (인자) : 1. 함수번호 를 조작한다 2. 로딩정보 (.dynamic section 재구성 정보)우리는 첫번째 인자인 함수번호를 조작한다.
조금 자세히 들어가보자 2 • _dl_runtime_resolve 함수: 1. JMPREL[함수번호] 에는 Elf32_Rel 구조체가 있다. Elf32_Rel — r_offset : 덮어쓸 GOT 주소 — r_info : 불러올 함수의 정보 • 계산 1함수 번호 = “페이로드 주소 - JMPREL”
조금 자세히 들어가보자 3 • … JMPREL[함수번호] Elf32_Rel — r_offset : 덮어쓸 GOT 주소 — r_info : 불러올 함수의 정보 - 제 2의 함수번호 • r_info: 0xABCDEF07마지막 바이트가 0x07 : “불러올 함수”0xABCDEF를 계산해서 넣어줘야함.
조금 자세히 들어가보자 4 • 2. SYMTAB[r_info * 16] 에는 Elf32_Sym 구조체가 있다. Elf32_Sym — st_name [4] (함수 이름의 상대적 위치) — st_value [4] (상관없음) — st_size [4] (상관없음) — st_info [1] (상관없음) — st_other [1] (최하위 2비트가 0이어야됨) — st_index [2] (상관없음) • 계산 2SYMTAB[r_info * 16] -> 페이로드 안에 위치
조금 자세히 들어가보자 5 • 계산 3 Elf32_Sym — st_name [4] (함수 이름의 상대적 위치) …st_name = “system\x00” 위치 - STRTAB
조금 자세히 들어가보자 6 • 결과적으로 • JMPREL[함수번호] : Elf32_Rel 구조체 “Rel” • SYMTAB[Rel.r_info * 16] : Elf32_Sym 구조체 “Sym” • Sym.st_name = “system\x00” 위치 - STRTAB • 이대로 호출하면 system함수가 바로 호출된다.
공격!! • 지역변수 사용 : 128-16=112 바이트 스택 오버플로우 • 입력된 크기만큼 고정된 주소의 전역변수에 복사
RET after“system” 조작된 함수 번호 SFP [4] _loader [4] char buf[256] AAAAAAAAAAAAAAAAAAAAAAAAA… ~~~~ 1. 스택 오버플로우 • 문자 256개 + SFP + Dynamic Linker + RET(after library function) + “함수 고유 번호”
2. 구조체 구성 • 전역 변수에 구조체들을 담는다. • Rel + Dummy + Sym + “함수이름\x00” + “명령어 #” • Dummy = SYMTAB[16 * r_info] 에서 16의 배수 맞춰주기 ex) 20글자를 A로 채움
이 방법의 장점 • 실행파일에서 참조되지 않은 함수를 바로 호출할 수 있다. • 보편적으로 적용되는 컴파일방식이므로 활용도가 큼 • 로딩된 라이브러리 전체에 적용 가능
이 방법의 단점 • 페이로드가 조금 복잡하다 • 페이로드가 있는 곳의 주소를 알아야 한다. • Full-RELRO일 경우 불가능 ( 다음 장에 설명 ) • PIE 문제를 해결해주진 않는다. ( 다음 장에 설명 )