[취약점분석] Command Injection(시스템 해킹 실습 1일차)
프로그램
- 사람의 언어인 소스코드를 컴파일이라는 과정을 통해 프로그램으로 만들어준다.
- 따라서, 컴퓨터가 실행할 수 있는 언어로 변환 과정이 필요한데, 이것이 컴파일과 링킹이다.
- 실행 가능한 상태 = 프로그램
프로세스
- 프로그램은 실행 가능한 상태일 뿐, 실행되지 않은 정적인 상태이다.
- 프로그램이 실행되기 위해선 메모에 적재(Load)되는 과정이 필요하고, 메모리에 적재된 프로그램은 실행 중인 상태인 '프로세스'가 된다
리버스 엔지니어링
목적)
- 이미 만들어진 프로그램 동작 원리 이해, 유사 프로그램 제작
- 프로그램의 보안 문제, 동작 문제, 오류 등을 이용, 제품 출시 전 문제점과 오류 제거/검토
- 시스템 해킹을 위한 프로그램의 취약점 식별
정적 분석
프로그램을 실행하지 않고, 디스어셈블러 혹은 디컴파일러를 통해 프로그램의 동작을 파악하는 과정
디스어셈블/디컴파일
어셈블리어는 기계어와 1ㄷ1 매칭되는 영문자이기 때문에, 프로그램을 다시 어셈블리어로 변환하여 보는 것이 가능하다. 프로그램을 어셈블리어로 변환하여 보는 것을 디스어셈블이라 하며, 작성된 프로그램을 소스코드 형태로 변환하여 보는 것을 디컴파일이라 한다.
동적 분석
디버거와 같은 도구를 사용하여, 프로그램을 실행하여 구조를 파악하는 과정
레지스터
내용 살짝
test.c 작성
gdb -q test // 디버거 붙이기
disass main
정적 분석이라 볼 수 있나?
체크 박스 부분이 순서대로 a,b,c라 추측이 가능.
스택에는 지역 변수가 저장됨. 지금 a,b,c가 함수 내에 있는 변수기 때문에 위와 같이 그림으로 볼 수 있는 것.
명령어 : r
메모리에 프로그램을 올린다 cpu가 실행할 수 있게 프로세스로 / 이때 메모리 구조가 있다.
cpu는 내가 실행할 명령어를 메모리에서 계속 가지고 온다, -> 명령어는 메모리 어딘가에 저장되어 있음을 알 수 있음. 이때 메모리에 저장되어 있는 명령어는 규칙이 있다. 용도에 따라 구분해서 올림.
eax를 보기 위해 브레이크 포인트 지점을 선택한다. +47은 메모리로부터 47바이트 떨어져 있음을 의미한다.
offset 방식으로 +47 부분에 break point 생성한 후, r 입력
mov esi, eax를 실행하면 eax값이 eis로 간다.
그래서 eax 값을 확인 해봤고, esi는 저런 값이 들어 있다.
ni로 mov esi, eax를 해보고, esi를 확인해보면 esi 값이 ni 전 eax 값이 들어간 것을 확인 가능하다.
새로 b *main+12에 bp를 걸고 실행 후 rbp 값을 확인해 보면
cmp p.143
cmp.c 위처럼 작성
cmp.c 컴파일 진행
pd main - disass과 같은 기능인데 불필요한 하이라이트가 삭제되어 있음- 강사님 추천
cmp 부분이 c코드에서 if문(=분기문)이기 때문에 if문 부분을 보기 위해서는 그 위에부터 bp를 걸고 확인해보기
cmp 다음 부분에 jle이는 작거나 같은 때 해당 주소로 점프하겠다.
# bp 적당히 잘 걸어서 내가 입력한 값이 잘 들어가는지 확인해보면 된다. ni로 넘기면서 확인해 봐라.
사전 설명)
스택 프레임 -> 개념 확실히
컴파일할 때 -m32 -> 32 비트로 컴파일한다.
no-pie를 추가해서 컴파일 다시 실행
disassemble 확인
b *main+49
r
call : 함수를 실행하고 리턴하겠다.
add 함수가 끝나면 <main+54>로 돌아와야 함.
그래서 <main+54>의 주소인 0x80491ce
add 함수의 호출을 보고 싶기 때문에 ni 가 아닌 si 명령어 사용. ni는 next into로 함수 내용을 넘어 뒴
4바이트만큼 사용한 것을 알 수 있다.
현재 메인 함수에서 add 함수 부른 상태. 즉, 현재 스택 포인터가 가리키는 ebp는 돌아갈 복귀 주소를 저장한 것.
현재 실습 내용)
- 함수를 호출할 때 그 인자가 스택에 들어가게 된다.
- 함수의 집합 = 프로그램 / 프로그램을 실행한다는 뜻은 함수를 순차적으로 부른다로 말할 수 있다.
- 함수를 사용하려면 함수 시작 전에 사용할 공간을 할당, 선언해야 함 -> 이게 프롤로그.
- 왜? 함수가 끝나고 돌아갈 주소, 부모 함수의 주소를 저장해 놓아야 하기 때문
ex. mov dword ptr [ebp-4], 1 -> 베이스 포인트로부터 얼마큼 떨어져 있느냐. ebp -> 현재 실행하고 있는 함수의 베이스 주소
mov dword ptr [ebp-4], 1 ===> mov dword ptr [ebp-8], 1 아닌가?
mani 함수 시작 시 스택 => 0x1000
ebp = 0x1000
ebp - 8(0xffc) = 1 (int a)
ebp - 4(oxff8) = 2 (int b)
call add
add 함수 시작 시 스택 => 0xff8
[PUSH] ebp // 현재 ebp에 한 바이트 쓰고 내려감
0xff4 | 0x1000 | 아래 함수가 다 끝나면 현재 줄의 저장된 복귀 주소로 이동할 수 있게 저장함.
mov ebp, esp
---------------------------------
ebp => 0xff8
함수 에필로그가 할 일
- 부모 함수의 스택 프레임 포인터로 ebp를 복구
- 복귀 주소로 eip 레지스터로 이동
leave
- mov esp, ebp :
- pop ebp : 현재 ebp가 가리키고 있는 값을 ebp에 넣는다.
leave
mov esp, ebp
esp (0xff4) | 0x1000 |
pop ebp
exp => 복귀 주소
ret
- pop eip : leave가 끝난 esp는 메인 함수의 복귀 주소 무조건 가리키고 있는데, 이 복귀 주소를 eip에 넣고 싶어서 pop eip를 실행하는 것
- jmp eip : 다음 실행할 명령어로 jump!
이제는 함수가 인자를 어떻게 전달할지를 보겠다.
64비트
- 콜 하기 전에 레지스터에 싹 다 만들어 놓는다. = 레지스터에 mov instruction을 하겠죠? call 하기 전에 인자가 다 정렬? 이 되어 있어야 함
32비트
- 32비트는 인자를 넘겨줄 때 스택에 저장함.
- call 함수 호출하기 전에 PUSH 개수를 보고 인자가 몇 개인지 짐작할 수 있다. = Calling Convention (규칙?)
실제 int a=1, int b=2를 인자를 넘겨주기 위해 push 두 번 진행.
call <add>를 main의 ebp를 스택에 넣고 자신의 ebp로 선정한다.
위 그림으로 봤을 때, <add+4> 자리에는 복귀주소, <add+8> 인자 1이 있는 것을 이해하자.
p.179 gdb 명령어 참고하면 수월하다~
버그
p. 189
Command Injection - 단위 공격 기술 학습
실습
입력받을 때 ls, whoami 가 아니면 컷
+달러 세미콜론 컷
즉, ls 또는 whoami로 시작하면서 특수문자를 포함하지 않게 해야 함
scout tcp-listen:8888,fork,reuseaddr exec:./command_injection,stderr
하고, 새로운 터미널에서 nc localhost 8888. -> 이 터미널에서 진행
결과 : ``안에 있는 값을 먼저 실행 후, 해당 결과를 앞의 명령어(ls)로 실행시킨다.