19번째 푼 문제이다!
오늘은 BOF원정대의 고블린을 처치해보자!

고블린이 보인다.



(고블린)



먼저 C코드를 확인해보자.



(고블린 C코드)



C코드를 보니 코볼트 녀석과 동일하다. 다만 입력에 있어서 차이가 있다.

입력을 표준입력으로 넣어주기만 하면 동일하게 처치할 수 있다.

먼저 메모리 주소를 확인하기 위하여 gdb로 분석해보자.



(표준입력 메모리 위치)



표준입력이 어디에 올라가는지 확인해보자.
여기서 버퍼를 배열을 사용한다. 배열은 힙 자료구조와 같이 높은주소로 자라기 때문에 배열로 된 버퍼가 주어지면 RET 주소를 쉽게 덮어 쓸 수 있다.

먼저 주어진 배열 버퍼를 꽉채워보자. A 16개를 입력한다.



(메모리 상태)



16바이트가 A로 가득 찬 모습을 볼 수 있다. 마지막 00 널문자가 다음 메모리 영역으로 초과된 것을 볼 수 있다. 이제 우리는 여기서 +4바이트(Saved EBP자리) 를 A로 더 채워준 후 RET주소를 EBP+8의 주소로 덮어 쓸 것이다. EBP+8에 우리의 쉘코드를 올릴 것이기 때문이다. :)

그렇다면 우리가 쉘코드를 올릴 EBP+8의 위치를 먼저 살펴보자.



(쉘코드가 올라갈 위치)



쉘코드가 올라갈 위치까지 확인했다면 끝이다.

정리를 하면
A 20개를 입력한 후 그 뒤에 RET주소에 쉘코드가 올라갈 위치로 덮어쓴다. 그리고 그 뒤에 이어서 쉘코드를 올려서 입력할 것이다.

고!



(공격 성공)



바로 쉘이 떨어지는 것을 확인 할 수 있다.

whoami로 확인 후 my-pass를 확인해보면 고블린 문제도 해결할 수 있다.



(고블린 처치)


'WarGame > 500 Project' 카테고리의 다른 글

(21/500) Lord of the BOF - wolfman  (0) 2017.06.20
(20/500) Lord of the BOF - orc  (0) 2017.06.20
(18/500) Lord of the BOF - cobolt  (0) 2017.05.05
(17/500) Lord of the BOF - gremlin  (0) 2017.05.05
(16/500) Wargame.kr - ip log table  (0) 2017.04.20


18번째 문제 풀이이다.! :)

cobolt를 풀어보자!



(홈디렉토리)



파일이 2개 들어있다.
먼저 C코드를 확인해본다.



(C코드)



C코드를 보니 그렘린 문제와 비슷한데 다른점이 버퍼의 크기가 작다는 것이다..
16바이트. 우리의 쉘코드는 32바이트이다.

우리의 쉘코드를 줄이던가 다른 방법이 필요하다.



(메모리 구조)



그렇게 생각하던 도중 스택의 더 높은 주소를 쓰는게 어떨까 하는 생각이 들었다.
문제의 버퍼는 배열이므로 높은 주소방향으로 채워진다. 즉 긴~ 문자열을 입력하면 RET주소를 넘어 그 아래까지 덮어 쓸수 있다는 것이다.
그래서 나는 버퍼를 A로 가득 채운후 Saved EBP 범위까지 A로 채우고
그 다음 RET주소에 RET+4주소로 덮어 쓸것이다. 그 후 RET+4 주소에 우리의 쉘코드를 올릴 것이다.

그렇게 되면 쉘을 떨어뜨리는데는 문제가 없을 것이다.!



(BOF)



주소부분을 C로 채워보았고 그 그 오른쪽 주소부터 쉘코드가 올라가는 지점이다.

쉘코드의 주소 EBP+8을 확인해보자.



(쉘코드 위치)



쉘코드의 위치를 확인한다.

이제 실제 주소값을 넣어보고 잘 넣어지는지 확인해본다.




(테스트)



확인해보니 주소도 잘 들어갔고 쉘코드 또한 잘 들어갔다. :)

이제 실제 프로그램에서 공격해보면 된다.




(공격화면)



공격을 해보니 바로 쉘이 떨어지는 것을 확인 할 수 있었다.



(문제 해결)

'WarGame > 500 Project' 카테고리의 다른 글

(20/500) Lord of the BOF - orc  (0) 2017.06.20
(19/500) Lord of the BOF - goblin  (0) 2017.05.07
(17/500) Lord of the BOF - gremlin  (0) 2017.05.05
(16/500) Wargame.kr - ip log table  (0) 2017.04.20
(15/500) Wargame.kr - lonely guys  (0) 2017.04.18


17번째 풀이 문제이다!

요즘 시스템공부를 하는 겸 BOF 원정대 문제를 풀어 나갈것이다. :)

BOF 원정대 메인 화면



(메인 화면)



첫 계정은 gate : gate 이다.

들어가보게 되면 파일이 2개 있다.




(바이너리 파일)



mygremlin은 gdb 분석을 위해 내가 cp명령을 이용해 복사한 파일이다.

먼저 C코드를 확인해보자 :)



(C코드)



C코드를 보면 프로그램실행시 인자를 넘겨주어 동작하게 되어있다.
넘겨준 인자를 버퍼에 strcpy를 이용해 복사한다. 
버퍼의 크기는 256바이트. strcpy를 이용하기에 bof 취약점이 존재한다.

gdb로 분석을 해보면.



(AAA인자 전달)



먼저 입력이 어떻게 들어가는지 확인하기 위하여
AAA를 전달하였다.



(저장된 값 확인)



strcpy 함수 명령이 지난후 메모리를 덤프해 확인해보니 AAA가 들어가있는 것을 확인 할 수 있다.

사용할 쉘코드는 
\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x52\x8d\x54\x24\x04\x52\x31\xc0\xb0\x0b\x8b\x1c\x24\x89\xe1\x31\xd2\xcd\x80
이며 32바이트이다.

총 256바이트(버퍼) + 4바이트(Saved EBP) + 4바이트(RET) 까지 덮어쓰면된다.
즉 우리 쉘코드
쉘코드(32바이트) + 패딩(228바이트) + RET주소(4바이트) 이렇게 맞출 수 있다.



(메모리 주소 확인)



코드가 올라갔을 때의 메모리 주소를 확인한다.

0xbffff918 에 우리의 쉘코드가 올라가게된다. 그러니 RET주소를 

0xbffff918로 덮어쓰면 된다.!



(덮어쓰기)



문제풀이를 위해 캡쳐하는 과정에서 오타가 나서 여러번 다시 찍고는 했다. 위 캡쳐에서 적혀있는 RET 주소는 잘못된 RET 주소이다. 하지만 과정상의 사진으로 보고 이해하면된다. 실제로 RET주소는 위에 우리가 구한 주소를 적어주면 된다.

아래는 주소부분에 C4개를 넣어 과연 주소부분을 잘 덮을지 실제 주소를 넣기 전에 테스트 해보고 있는 사진이다.



(RET주소)



RET주소에 C4개가 들어간 것을 확인 할 수 있다. 
이제 이 자리에 우리가 아까 구한 쉘코드의 위치 주소를 넣어주면 된다.

이제 우리의 gremlin을 실행시켜 인자를 전달해보자.



(인자 전달)



실행시 bof가 발생하여 쉘이 떨어지는 것을 확인 할 수 있다.
SETUID가 설정되어있기 때문에 gremlin의 비밀번호를 확인 할 수 있다.



(문제 해결)


'WarGame > 500 Project' 카테고리의 다른 글

(19/500) Lord of the BOF - goblin  (0) 2017.05.07
(18/500) Lord of the BOF - cobolt  (0) 2017.05.05
(16/500) Wargame.kr - ip log table  (0) 2017.04.20
(15/500) Wargame.kr - lonely guys  (0) 2017.04.18
(14/500) Wargame.kr - strcmp  (0) 2017.04.16


실습3
if( eax >0 && ebx < 10 || ecx >= 2){
 ebx =1;
} else{
 ebx = 0;
}

알고리즘
1. 비교 eax와 0 비교
: eax가 0보다 같거나작을경우 (eax <=0) ecx를 비교하러 가야함.
1-1. (eax <=0)
 ecx와 2 비교
: ecx가 2보다 같거나 크면 (ecx >=2) -> ebx = 1
아니면 ebx = 0
1-2. (eax >0)
-> ebx를 10과 비교
: ebx가 10보다 작으면 (ebx <10) -> ebx = 1
아니면 ecx 비교해야한다.
-> ecx 비교해서 2보다 크면 1
작으면 0

위의 C코드를 어셈블리 언어로 표현하면 아래와 같다.


(실습코드)



이 코드를 조금 더 간략하게 표현하자면 아래와 같이도 표현할 수 있다.



(실습 코드2)



그러면 우리가 만들었던 어셈블리 코드가 실제로 C코드를 컴파일한 코드와 비교해보자.



(C코드)



컴파일 후 어셈블리어 코드를 확인해보면



(어셈블리어 코드)



코드를 보면 우리가 만들었던 것과 조금 차이는 있지만 이 코드를 보고 우리가 어떤 식으로 동작하는지 이해할 수 있는 정도는 되었다.

이번에는 switch 문을 사용한 C코드를 컴파일한 어셈블리어를 if 코드와 비교해보겠다.



(switch문 사용)




컴파일 후 어셈블리어 코드 확인



(switch문)



코드를 보면 if문과 조금의 차이는 있지만 구조적으로 보면 jmp를 사용하기에
똑같다고 볼 수 있다.

이제 반복문을 알아보겠다.
반복문을 이용한 C코드를 컴파일해서 어떤식으로 이루어지는지 먼저 확인해보겠다.



(for문)



컴파일 후 실행해보면



(실행결과)



실행결과 우리가 생각하는 반복문 실행이 되는 것을 확인 할 수 있다.

이 코드를 어셈블리어를 확인해보면



(어셈블리어 코드)



반복문 또한 우리가 공부했던 jmp 분기문을 이용해 이루어지는 것을 볼 수 있다.

그렇다면 같은 내용을 while로 구현해보겠다.



(while 사용)




(어셈블리어코드)



어셈블리어 코드에서 보면 for문과 while 문의 차이는 없었다.
반복문은 jmp 분기문을 이용해 똑같이 만들어지는 것을 확인 할 수 있다.

그렇다면 직접 반복문을 만들어보자.!

아래와 같은 반복문을 만들것이다.
ebx = 0;
eax = 1;
while(eax <= 10){
 ebx += eax;
 eax++;
}


(실습 어셈블리어 코드)



분기문 하기에 앞서 사칙연산을 정리해보겠다.

사칙연산하는 C코드의 어셈블리어는 어떻게 구성될까?



(C코드)




컴파일 후 확인해본다!




(어셈블 코드)



저번 글에서 만들었던 형태가 비슷하게 나오는 것을 확인 할 수 있다.

오늘은 분기문을 공부 할 것이다.
jmp는 해당 주소로 이동하는 것이다.

이것을 이용하면 조건문도 만들 수 있고 반복문도 만들 수 있다.
실제로 C코드에서 if와 switch는 jmp 명령어로 이루어져있고
어셈블 차원에서 보면 똑같다.

사용 형식은
jmp addr
이런식으로 써준다.

그렇다면 jmp로 무한루프를 만들어보겠다.




(무한루프 코드)



jmp 명령에 의해 _start로 돌아간다.
_start는 레이블 이름으로 주소값이다. 컴파일 전에 주소값을 우리는 모르니 레이블 이름을 달아주어 사용한다.
컴파일한다.



(컴파일)



컴파일 후 실행해보면



(실행 모습)



실행되는 모습이다.

그렇다면 C코드의 기본적인 if문을 어셈블러로 구현해보자!
먼저 C코드의 기본적인 if문 코드이다.



(if문 코드)




(실행모습)



여기서는 조건 분기를 사용한다. 위의 jmp는 무조건 분기로 다른 조건없이 바로 해당 주소로 넘어가지만 조건분기는 조금 다르다.

조건 분기
 - EFLAGS 레지스터를 참조해서 분기할지 말지를 결정한다.
조건 분기문을 쓰기전에 cmp를 해줘야 EFLAGS에 기록하고 이걸 보고 조건문기문이
동작한다.

 - cmp

조건분기 종류를 살펴보면
 - je (jmp equal) = jz
 - jne (jmp not equal) = jnz
 - jl (jmp less)
 - jg (jmp greater)
 - jnl
 - jng
 - jle
 - jge
 - ja (jmp above) 초과
 - jb (jmp below) 미만
 - jna
 - jnb
 ...

3. cmp : 비교명령어
 cmp vleft vright
 -> 두개의 값이 같은지 비교
- vleft 값과 vright 값의 차를 구한다.
- 그 결과에 따라서 EFLAGS 레지스터의 플래그를 조절한다.
cmp에서 사용하는 flg
- ZF, SF
1) vleft - vright의 결과가 0인 경우
 : ZF = 1, SF=0 -> 두 값이 같다고 판단
2) vleft - vright의 결과가 음수인 경우
 : ZF=0, SF=1 -> 오른쪽이 더 크다.
3) vleft - vright의 결과가 양수인 경우
 : ZF=0,SF=0
조건분기문 사용 직전에 cmp를 해줘야한다.

이를 이용해 기본적인 if C코드를 어셈블러 코드로 구현하면 아래와 같다.



(조건분기문 사용)




(실행결과)



실행 결과 잘 나오는 듯하다.
(하지면 이 코드에는 문제점이 있다.)

값을 5보다 작게 2로 설정하여 확인해보겠다.



(작은 값 입력)




(실행결과)



실행결과를 보니 2가 작음에도 크다고 출력되었다.

왜 이럴까??
원인은 바로 점프하지 않고도 그 아래에 출력해주는 명령문이 있기 때문에 아래 코드가 순차적으로 실행 되었던 것이다. 이것을 막아주기 위해서는 아래 처럼 추가해주면된다.



(추가 코드)




(실행결과)



실행한 결과 작을 때는 이제 출력되지 않는 우리가 원하던 C코드의 동작을 하게 되었다.

다르게 표현을 하자면 jmp문 하나로도 완성 가능하다.



(다른 표현)



C코드를 컴파일 한 내용과 비교해보겠다.
상수끼리 비교하면 컴파일러가 알아서 계산을 해놓아서...
변수를 따로 선언을 해주었다.



(C코드 수정)



컴파일 후 어셈블러를 확인해보면



(어셈블 코드)



우리가 만든 코드와 비슷하게 나온 것을 확인 할 수 있다.

그렇다면 실습으로 아래와 같은 코드를 어셈블 코드로 만들어보자!



(실습 코드)



실습 결과는 아래와 같이 만들 수 있다.



(실습 결과)



jmp 문에 대한 내용이었다.




오늘은 어셈블리로 사칙연산 명령어를 공부할 것이다.

1. 덧셈
명령어 : add
형식
add     dst,     src
:dst에 더한값을 저장한다.
dst에는 레지스터, 메모리가 올 수 있고
src에는 레지스터 메모리 상수가 올 수 있다.

*주의 한번에 두개의 메모리를 참조할 수 없다.
ex) add 메모리, 메모리
(이건 mov도, 기타등등 여러 명령어에서도 마찬가지이다.)

그러면 add명령어를 사용해보겠다.



(add 코드)



eax에 2를 저장하고 add 명령어를 이용해 3을 더했다.

실행하게되면


(실행화면)



결과는 5가 나온다.

이번에는 레지스터가 아닌 메모리를 사용해 덧셈을 해보겠다.



(메모리 사용)



sum 주소에 2를 저장하고 3을 더해주었다.
대신 주소값을 쓰는게 아니라 주소에 있는 데이터값을 쓰는것이므로 
[ ] 브라켓을 씌워준다.


(실행결과)



실행 결과 역시 5가 나왔다.

2. 뺄셈
명령어 : sub
형식
sub     dst,     src
내용은 덧셈과 같다.

그렇다면 뺄셈을 사용해보겠다.



(뺄셈 코드)



(실행화면)



실행화면을 보면 10-5의 결과로 5가 제대로 나온 것을 볼 수 있다.

* 실습 문제
 - 다음과 같은 두 개의 값의 덧셈 결과와 뺄셈 결과를 출력
segment  .data
 num1     dd     20
 num2     dd     10



(실습 코드)




코드를 보면 알겠지만 조금 오바해봤다.

실제 계산부분만 보면 되겠다.
실행해보면



(실행결과)




덧셈과 뺄셈이 잘 되는 걸 확인 할 수 있다.

이번에는 덧셈은 레지스터, 뺄셈은 (꾸이꾸이) 메모리를 사용해 해보겠다.



(실습 코드2)




(실행결과)



실행 결과가 같다.

이번에는 주소값과 주소에 있는 데이터와의 차이를 보겠다.

아래의 코드를 먼저 본다.



(어셈블 코드)



위에는 그냥 num 아래는 [num] 을 사용했다.
실행하여 결과를 확인해보자.



(실행결과)



실행 결과를 보니 그냥 num을 쓴 것은 주소값이 출력되었고
[num]은 그 주소에 있는 데이터 10의 값이 출력되었다.

여기서 이제 mov와 lea를 비교하면서 lea가 뭔지 살펴보겠다.
lea는 mov와 거의 비슷하다. 다만 조금 다른점이 있는데 직접 눈으로 확인해보겠다.

아래에 lea와 mov를 추가해서 확인해본다.



(mov와 lea 추가)




(실행결과)




실행 결과를 보니 mov로는 [num]이기 때문에 우리가 알던대로
num의 주소에 있는 데이터인 10이 출력되었다.

근데 이상한점이 있다. lea로 [num]을 넣어주었는데
주소값이 출력되었다.
바로 이게 차이점이다.
lea는 [ ] 브라켓을 꼭 써야하고
그 안에있는 값 그대로 출력해준다. 그래서 num의 주소값이 그대로 출력된 것이다.

바로 이 점을 이용해 덧셈을 할 수 있는데
아래 코드에서 마지막 부분을 보면 된다.



(lea를 통한 덧셈)




(실행 결과)



실행 결과를 보면 14로 덧셈이 잘 된것을 확인 할 수 있다.
lea eax, [ebx+4]
이렇게 하면 우리가 ebx에 10을 넣어주었으니 ebx는 10이되고 10+4로 14의 값이 그대로 전달되는 것이다.

3. 곱셈
명령어 : mul, imul
형식
mul     피연산자
-> 부호없는 곱셈

imul 피연산자
 imul 피연산자, 피연산자, 피연산자
 -> 최대 3개까지 올 수 있다.
 -> 부호 있는 곱셈

mul 는 a 레지스터를 쓰게 되어있다.
무조건 a레지스터에 있는 값과 곱하게 된다.

그렇다면 곱셈을 사용해보자!



(곱셈 코드)



(실행화면)



실행화면을 보면 곱셈의 결과 6이 나온 것을 확인할 수 있다.

이제 곱셈의 이상한?점을 볼 차례이다.
바로 사이즈가 커진다는 것인데
곱셈은 결과가 원래 사이즈를 넘어갈 수 가 있다.
그렇기 때문에 결과를 큰 사이즈에 저장하게된다.
위에서도 보다시피 1바이트 곱셈인데 결과는 우리가 eax를 푸쉬해서 출력했다.

아래처럼 255로 꽉채워서 곱셈을 하게되면 사이즈가 초과되기 때문에 결과는 eax로 저장이 된다.



(초과 곱셈)



(실행결과)



그렇기 때문에 곱셈의 결과가 정확하게 나올 수 있는 것이다.

그러면 이제 imul을 사용해보겠다. 이 명령어는 부호가 있는 곱셈이라고 했다.
부호가 있을때 사용하라는 말이 아니라 비트중 MSB를 인식한다는 뜻이다.

우리가 255 255 곱셈을 하게되면 어떻게 될까? 왜냐하면 MSB가 모두 1이기 때문에 궁금하다.



(imul)



(실행결과)



실행결과 255를 -1로 인식하여
-1 곱하기 -1 을 하여 1이 나왔다.

imul은 mul처럼 사용할 수 도 있지만
인자를 2개 3개까지 받을 수 있는데 살펴보겠다.

2개를 쓸 때



(2개 사용 코드)




(실행 모습)



2개를 쓸 때는 이렇게 사용한다.

3개를 쓸 때는?



(3개 사용)




(결과)



2번째 3번째 곱셈의 결과를 1번째 인자에 저장한다.

곱셈은 결과가 사이즈가 초과할 수 있어 그보다 더 큰 레지스터에 저장한다고 했다.
그렇다면 eax 4바이트 단위의 곱셈은 어디다 저장이 되는 걸까?

해보자!



(초과 결과 코드)



(실행 모습)



eax를 출력해보니 뭔가 완전하지 않은듯한 느낌이든다.

원래 곱셈의 결과라면
0x11111111  *  0x22222222 는 아래와 같다.



(결과)



그렇다면 앞 부분은 어디갔다는 말인가?!

바로 edx에 저장된다.
edx도 함께 출력해보겠다.



(edx 추가 출력)




(실행화면)



실행화면을 보면 eax와 edx에 나누어서 결과가 저장이 된 것을 확인 할 수 있다.

나머지와 mod 연산은 다음 글에서 이어서 써보겠다. :)




오늘은 데이터를 저장하는 방식에 대해 이야기해보겠다.

크게 나누면 메모리를 이용하는 방식과 스택을 이용하는 방식이 있다.

그 중 메모리를 이용하는 방법을 알아볼 것이다.

먼저 데이터 단위이다.
* 데이터의 기본 단위
Unit  bytes  Letter
byte  -> 1 bytes : 표현 B
word -> 2 bytes : 표현 W
double word -> 4 bytes : 표현 D
quad word -> 8 bytes : 표현 Q
ten bytes -> 10 bytes : 표현 T
paragraph -> 16 bytes

* 데이터를 저장하기 위해 사용가능한 메모리
1. 데이터 메모리 : C에서는 전역변수 개념

1) 초기화된 데이터 메모리 영역 :.data
-> 지난 시간까지 사용했던 영역이다.
-> 중간에 type이 db라고 써있는 것에 대해 설명하자면 data 영역에서는 앞에 d를 붙여줘야한다.
    그 뒤에 나오는 것은 데이터 단위로 b는 byte로 1 바이트를 나타낸다.

2) 비초기화된 데이터 메모리 영역 : .bss
중간에 type에 앞쪽에 res를 적어주고 그 뒤에는 데이터 타입을 적어준다.
ex) resb

어셈블리 기본 명령어 구조(인텔기반)
- 명령어( pushfd, nop, ret,  ... ) 피연산자가 없는 경우도 있다. 단일명령어형태
- 명령어 피연산자( pop esp,      jmp addr, ...)
- 명령어 피연산자1, 피연산자2 ( mov ebx, 2  ...)
- 명령어 피연산자1, 피연산자2, 피연산자3 ( mul, ...)

* mov 명령어
mov : 데이터 이동
 mov dst, src
- dst에 올수 있는 것은 메모리와 레지스터 외에 다른 값이 올 수 없다.
- src : 메모리, 레지스트리, 일반 값 전부 다 올 수 있다.

이것들을 이용해 실습하면서 확인해보겠다.
먼저 data 영역에 데이터를 저장하고 출력해보겠다.




(어셈블 코드)



이 코드를 실행하면 numbers에 있는 값들이 출력될까?
실행해보면



(실행결과)



우리가 저장한 값은 전혀 아니라는 것을 확인 할 수 있다.
이 값은 numbers의 주소 값이다.
기본적으로 전달할때 어셈블리언어에서는 주소를 전달한다.

그렇다면 data영역에 numbers 에 우리가 1,2,3,4,5 를 저장했는데 이 값을 출력하려면 어떻게 해야할까?



(출력 코드)



이런식으로 [ ] 브라켓에 담아주면된다.
이렇게 표현을 하면 C언어에서 포인터 같은 역할을 하게된다.
주소값이 아닌 그 주소에 있는 데이터 값을 가져온다.
위 코드를 실행하면 1이 출력이 될것이고
그렇다면 그 다음 값인 2를 출력하려면?



(2 출력 코드)



DWORD가 4바이트이므로 4바이트+ 시켜준 주소값으로 브라켓으로 값을 가져오면 된다.



(실행모습)



2가 출력되는 것을 확인 할 수 있다.

DWORD 써준이유?
numbers 라고 우리가 주소값을 전달해주는데 얼마나 가져오라는 말이 없다.
즉 이 말을 안써주면 어디까지 우리가 가져와야하는지 모르니까 DWORD처럼 가져올 단위를 적어줘야한다.
레지스트리에서 안쓴 이유는 레지스트리 이름 자체에 크기를 뜻하기 때문이다.

이번엔 bss 영역에 초기화 되지 않은 변수들에다가 값을 저장하고 그 값을 출력해보겠다.



(bss 코드)



mov 명령어를 통해 number의 위치에 데이터를 10을 저장한다.
그 후 number 위치의 값을 가져와 출력한다.



(실행 결과)



실행 결과 우리가 10을 너어주었는데
10이 잘 출력된 것을 확인 할 수 있었다.




어제까지 배운 지식으로 토끼모양을 출력하는 프로그램을 어셈블 언어로 작성해보자!

코드는 다음과 같다.



(어셈블 코드)




(출력 결과)



오늘은 레지스터에 대해 알아볼 것이다.

* 레지스터



1. 범용 레지스터

-> 이름이 범용이 들어가는 것 처럼 이곳저곳에서 많이 쓰이는 레지스터이다. 레지스터의 크기에 따라 명칭이 달라진다.


레지스터 크기 1, 2, 4, 8 바이트
8바이트 : RAX, RBX, RCX, RDX
4바이트 : EAX, EBX, ECX, EDX, ...
2바이트 : AX, BX, CX, ...
1바이트 : AH, AL, BH, BL, CH, CL ...


종류가 다르기보다는 크기를 나타내는 명칭이라고 생각하면된다. 같은 레지스터의 영역이다.


내용을 확인할 수 있는 프로그램을 만들면서 확인해보겠다.



(어셈블 코드)



위 코드는 레지스터 eax에 0101010101010101 이 들어간 값을 출력하는 프로그램이다.
레지스터의 내부구조를 살펴보기위해 만든 코드이다.
출력은 16진수로 표현된다. 그러므로 출력결과는 55555555 로 될것이다.



(출력 결과)



출력 결과가 55555555이 나왔다. 여기까지는 크게 다른 내용은 없다.
계산기에서 55555555을 입력하여 비트단위로 보면 0101010101010101인것을 확인 할 수 있다.



(55555555 (16))



여기서 eax 그대로에 ax를 사용하려 했으나 eax가 여기서 함수호출로 인해 사용되므로 ebx로 바꿔서 계속 진행해보겠다.
ebx 레지스터에 55555555을 넣고 bx 레지스터에 2222를 넣었다.
그리고 ebx를 출력해보면 출력결과가 어떻게 나올까?



(어셈블코드)



그 내용을 담은게 위의 코드이다.
실행해보겠다.



(실행결과)




(비트단위)



ebx 결과를 보면 ebx 내부 2바이트가 2222로 덮여쓰여진 것을 볼 수 있다.

계속해서 1바이트를 표현하는 bl, bh를 사용해보겠다. 이 둘은 상위1바이트 하위 1바이트를 가리키는 레지스터이다. ebx는 그대로 사용한 채 bh에 44 bl에 33을 넣어보겠다.



(어셈블코드)



실행하게 되면



(실행 모습)




(실행결과)




상위 1바이트는 44로 표현이 되었고 하위 1바이트는 33으로 표현되었다.

이렇게 범용레지스터는 크기를 나타내는 용어가 다르다. 즉 1바이트를 사용하고싶거나 2바이트, 4바이트 이렇게 사용하고 싶은 크기에 따라 다른 레지스터를 사용하면 된다.

2. 포인터 레지스터
 - 주소를 표현하는 레지스터이다. 이 레지스터는 용도가 분명하게 있다.
 - 용도가 정확하게 정해져 있기 때문에 다른용도로쓰면 세그먼트 폴트등 오류가 난다.



1) 스택 메모리에서 사용되는 레지스터
 - EBP ( Extended Base Pointer )
 - ESP ( Extended Stack Pointer )
 - EIP ( Extended Instruction Pointer ) : 다음 명령을 가리키는 주소


2) 문자열 복사등에 사용되는 레지스터
 - ESI ( Extended Source Index )
 - EDI ( Extended Destination Index )
-> 다른용도로 쓰이기도한다.. 중요성이 덜해서 다른용도로 쓰기도한다.
 
3) 플래그 레지스터
 - EFLAGS
 0: CF (Carry Flag ) : 올림수가 발생한 경우 비트가 1로 셋팅된다.
 6 :ZF ( Zero Flag ) : 연산의 결과가 0인 경우 비트가 1로 셋팅된다.
 7 : SF ( Sign Flag ) : 부호가 발생한 경우(음수) 비트가 1로 셋팅된다.
 11 : OF ( Overflow Flag ) : 오버플로우가 발생한 경우 비트가 1로 셋팅된다.
-> 나중에 디버거에서 직접 확인해보겠다.




시스템 공부에 들어서면서
바이너리 분석에 들어가 볼 것이다.

* 바이너리(실행파일)란?
 - 우리가 흔히 보는 윈도우즈의 .exe 확장자의 실행파일이라고 생각하면 된다.
 - 0과 1로 되어있는 기계어로 번역되있는 파일이다.

이 바이너리분석을 하기 위해서는 실행 파일이 어떻게 만들어지는지 공부할 필요가 있다.
즉,
다시말하면
프로그래밍된 코드가 어떻게 기계어로 번역되는지 이런 과정을 크게 Compile이라고 한다.
이 Compile 과정에 대해 알아보겠다.
우리는 C언어로 작성된 코드가 실행파일이 되는 과정을 살펴볼 것이다.

먼저! 간단하게 C코드를 작성한다. 



(sample.c 작성)



* gcc는 확장자를 보기 때문에 파일 이름 뒤에 .c를 붙여줘야한다.

만든 후 gcc를 이용해 컴파일을 해볼 것이다. gcc는 GNU C Compiler의 약자로 리눅스에서 제공하는 컴파일러이다.
아래처럼 컴파일 해본다.



(컴파일)



컴파일 완료되면 위와 같이 실행파일이 생성된것을 확인 해 볼 수 있다.
또 이 실행파일을 실행시키면 우리가 작성한 대로 Hello World 문구가 나오는 것을 확인 할 수 있다.

이 실행파일은 바이너리로 되어있기 때문에 vi 편집기 혹은 cat으로 볼 수 없고
헥스값을 볼 수 있는 xxd 데몬을 이용해서 볼 수 있다.



(바이너리 파일)



* gcc 뒤에 -o 옵션을 주어 우리가 원하는 파일이름으로 컴파일하여 실행파일을 만들 수 있다.



(-o 옵션)



* gcc 뒤에 -v 옵션을 주면 컴파일 과정이 화면에 그대로 출력된다.
우리는 gcc -v 옵션을 주어 컴파일이 이루어지는 과정을 그대로 살펴볼 것이다.



(-v 옵션)



위 화면을 보면 컴파일 과정이 주르르륵 나온 것을 볼 수 있다.
하나하나 과정을 짚어가며 살펴보겠다.

* 컴파일 과정

(1) 전처리 과정 : 컴파일 중 가장 먼저 이뤄지는 작업으로 매크로, #include 문장 해석을 한다.
전처리 : precompile


출력 화면에 해당하는 문구

 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/cpp -lang-c -v -undef -D__GNUC__=2 -D__GNUC_MINOR__=91 -D__ELF__ -Dunix -Di386 -D__i386__ -Dlinux -D__ELF__ -D__unix__ -D__i386__ -D__i386__ -D__linux__ -D__unix -D__i386 -D__linux -Asystem(posix) -Asystem(unix) -Acpu(i386) -Amachine(i386) -Di386 -D__i386 -D__i386__ -D__tune_i386__ sample.c /tmp/ccmsu4yd.i



cpp -> 전처리기이다. 즉 cpp명령이 보이고 뒤에 -어쩌구들 해서 많은 옵션들이 보인다. 뒤에


sample.c /tmp/ccmsu4yd.i 이 보이는데 sample.c를 임시디렉터리에 .i파일을 만드는 과정이다.


하지만 /tmp 디렉토리에 들어가면 .i 파일을 확인할 수 없는데 그 이유는 컴파일이 끝나면 삭제시키기 때문이다. 우리는 이 파일들을 보면서 확인할 것이므로 이러한 파일들이 삭제되지 않는 추가적인 옵션을 주어야한다.

-save-temps 옵션을 주어 임시파일 .i 파일을 삭제 하지 않게 하겠다.



(-save-temps 옵션)



옵션을 추가하니 여러 부산(?)물들 파일이 생긴것을 확인 할 수 있다.

우리가 먼저 확인해볼 것은 sample.i (전처리 과정에서 생기는 파일) 이다.!



(sample.i 파일)



큰 변화가 없어보인다?...

전처리 과정에서 어떤 일이 일어나는지 직접 눈으로 확인해보기 위해 c코드를 조금 수정해보겠다.!
define문장을 추가해보자!



(define 문장 추가)




다시 컴파일 하겠다!




(재컴파일)



재컴파일 후
sample.i 파일을 확인해본다.



(sample.i 파일)



- define 문장이 사라지고 소스코드 안에 썻던 define이 100(우리가 설정했던 값)
으로 모두 치환되어 있는 것을 확인 할 수 있다.
-> 바로 이게 전처리기의 역할이다.

2. 어셈블 과정 : 전처리된 파일을 어셈블리 형태로 변환

해당 문구

 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/cc1 /tmp/ccmsu4yd.i -quiet -dumpbase sample.c -version -o /tmp/cc0q9vPg.s


.i 파일을 .s 파일로 바꿔준다. 이때 어셈블(기계어)형태로 바꿔준다.

왜 기계어라고 해도 무관하냐면 어셈블언어와 기계어가 1:1로 매칭되기 때문에 어셈블언어를 기계어라고도 한다. 즉 바이너리로 바꾸기 직전의 파일 .s 파일을 만들어준다.



(sample.s 파일)



sample.s 파일을 확인해보면 어셈블 언어로 바뀐것을 확인할 수 있다.

3. 컴파일 과정 : 어셈블 파일을 기계어(숫자)로 번역하는 과정이다. 정확히말하면 컴파일 과정은 이부분이지만 크게 말해 이 모든 과정을 컴파일이라고 통상적으로 말하곤 한다.

해당문구

as -V -Qy -o /tmp/ccs3N70j.o /tmp/cc0q9vPg.s


as : 어셈블러를 뜻한다. 즉 as 명령으로 -o옵션으로 .s파일을 .o 파일로 만든다.
.o 파일 (기계어로 뽑아낸 파일, 오브젝트 파일)



(sample.o 파일)



파일을 보면 이제부터는 vi 편집기로 볼 수 없다. 이제부터는 바이너리이기 때문에 xxd 혹은 objdump 등 바이너리를 다루는 도구를 통해 봐야한다. objdump를 이용해 확인해보겠다.



(sample.o 파일 확인)



파일을 보면 .s 파일에서 봤던 어셈블 언어가 모두 바이너리로 바뀐것을 확인 할 수 있다.

4. 링크 단계 : 완벽한 실행파일을 생성한다. 이 때 필요한 라이브러리를 모두 합친다.

해당 문구

 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/collect2 -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtbegin.o -L/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66 -L/usr/i386-redhat-linux/lib /tmp/ccs3N70j.o -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtend.o /usr/lib/crtn.o


: 오브젝트 파일과 라이브러리 오브젝트를 전부 합쳐서 하나의 실행파일로 만든다.



(a.out 파일 확인)




그렇다면 a.out 파일을 확인해보겠다.




(a.out 바이너리)



파일을 보면 왼쪽에는 메모리 주소인데 파일내용에서 해당 내용을 찾으려면(보통 파일구조 그대로 메모리로 올라가기 때문에) 대략 뒤 3자리를 참조하면된다. (항상 그런것은 아니다.)
확인해보면 3d0의 위치를 보자 



(3d0 위치)



3d0 위치에 우리가 확인했던 바이너리 문자를 볼 수 있다.

* 어셈블 언어로 코드를 작성해보고 컴파일 해보자!
Hellow World 를 출력하는 실행파일을 만들것이다. :)
nasm을 이용할 것이다! 그러기위해 .asm 확장자로 작성한다.



(sample.asm 파일 작성)




(sample.asm 내용)



컴파일 과정에서 어셈블 언어로 바꾸는 과정까지 우리가 직접 한 셈이다.
그러니 그 후 나머지 작업만 해주면 된다.

먼저 오브젝트 파일을 생성해야한다.(바이너리로 바꿔준 파일)
nasm을 이용하여 -f옵션 파일 타입 elf 로 명시한 후 sample.asm을 입력한다.



(sample.o 파일 생성)



그 후 우리가 printf 함수를 사용했으므로 이 함수의 라이브러리를 연결해주어야한다.
바로 링크단계이다.
여기서는 간단하게 static으로 구현해 볼것이다.



(링크 작업)



링크 작업이 끝나면 실행파일이 만들어지게 된다.



(실행파일)



실행 파일이 만들어진 것을 확인 할 수 있고
실행되는 것 또한 확인 할 수 있었다.



* 웹 해킹을 하면서, 웹 해킹 문제를 풀면서 Command Injection을 해야하는 상황이 종종 나타난다.

하지만, 이 또한 SQL Injection과 함께 널리 알려져 있기 때문에 보통 막아놓는다.
예를들어
$key = $_REQUEST["input"];
system("grep -i \"$key\" action.txt");
예를 들어 이러한 코드가 있다고 가정해보자!

Injection공격에 익숙한 사람들이라면 Injection 취약점이 있다는 것을 바로 알 수 있다.
key라는 변수를 GET방식으로 받아와 직접 명령어를 전달한다.
여기서 | (pipe) 혹은 ; (세미콜론)을 쓴다거나 등등 여러 방법으로 내가 원하는 명령을
함께 실행 할 수 있는 것이다.

그런데 만약 여기에
(preg_match('/[;|&`\'"]/',$key))
이러한 코드 문구가 있고 필터링 되고있다면??
우리가 알고있는 pipe도 못쓰고 세미콜론도 못쓰고...
게다가
%0a로 개행하여 명령을 전달하려해도 우리가 입력한 값이 "" 더블쿼터안에 들어가기 때문에
명령어로 인식이 안된다..

그렇다면 더블쿼터 안에서 명령어를 실행할 수 있는 방법을 찾으면 좋을텐데..!

그래서 오늘은 더블쿼터 안에서 명령어를 실행할 수 있는 방법을 정리하고자한다.
Command Injection으로 활용할 수 있다.

명령어로 인식되게 하는 특수문자가 리눅스에 있다.

예를들어 uname이라는 명령어를 리눅스에서 입력하면
현재 사용되고 있는 OS 정보를 보여준다.



(uname 명령)



여기서 mkdir로 폴더를 만드는데
#> mkdir uname
이라고 입력을 하면
uname이라는 폴더가 만들어진다.



(uname 폴더 생성)



하지만 여기서
#> mkdir `uname`
을 입력하면 `(백쿼터) 안에 있는 uname이 명령으로 인식되어 명령이 전달된 값 Linux라는 폴더를 만들게 된다.



(명령어로 인식)



아하!!
`(백쿼터) 안에 있으면 명령어로 인식되는 것을 알 수 있다.

하지만..
위에서 필터링 되고있는 것들을 보면 `(백쿼터)가 포함되어있다.
다른 방법이 하나 또 있다.
바로 $() 이 안에다 쓰면 명령어로 인식이 된다.
Linux라는 폴더를 지우고 다시한번 똑같이 해보겠다.



(명령어로 인식)



역시 명령어로 인식되는 것을 확인 할 수 있다.

명령어로 인식되게 할 수 있는 방법
1. ` uname` 
2. $(uname)  



+ Recent posts