* 이 글은 SK Infosec의 오픈소스 소프트웨어 보안가이드를 참조하여 작성하였습니다.


Node.js Security


1. 데몬 관리


1.1 root 권한 실행 금지

  Node.js로 서버를 구동시켰을 때, root 권한으로 실행하면 안된다. root 권한으로 구동된 서버의 경우 취약점으로 인해 Exploit 당해 실행 권한을 넘겨주었을 때 그 권한이 root일 경우 상황이 심각해지기 때문이다.


1.2 요청을 전달할 HTTP 서버/proxy 형태로 구성

  Apache, nginx 등과 같은 HTTP 서버를 통해 요청을 받아 전달하도록 구성해야한다.


1.3 NODE_ENV 설정을 production으로 설정

  개발 당시 develpoment로 설정하여 개발 할 수 있는데, 서비스를 시작할 때 이를 깜빡하여 그대로 서비스를 구동하는 경우가 있다. 이럴 경우 에러에 관해 상세한 정보가 출력되기 때문에 이를 이용해 공격자들이 활용할 수 있다.


* 진단 방법

# ps -ef | grep node





2. 로그 디렉토리/파일 권한 설정


  로그 파일에 공격자에게 유용한 정보가 들어있다. 그래서 권한관리가 필요하며 일반 사용자들이 접근하지 못하도록 권한을 설정한다.


2.1 권한 설정

 # chown nodeapp:node /[구동중인 Node 어플리케이션 로그 디렉토리]    

# chmod 750 /[ 구동중인 Node 어플리케이션 로그 디렉토리]                

# chown nodeapp:node /[구동중인 Node 어플리케이션 로그 디렉토리]/*   

# chmod 640 /[ 구동중인 Node 어플리케이션 로그 디렉토리]/*  


  권한은 WAS 서버 계정 소유여야하며, 디렉토리는 750(drwxr-x---) 파일은 640(-rw-r-----) 권한으로 설정해야한다.


* 진단 방법

# ls –lad /[Node 어플리케이션 로그 디렉토리] 

# ls –la /[Node 어플리케이션 로그 디렉토리]/*






3. 로그 포맷 설정


  로그 포맷을 설정하지 않으면 침해사고 발생 시 공격 여부 파악, 공격자 사용 툴 파악, 공격자 위치 파악이 불가능하다. 그러므로 필요한 정보들로 로그 포맷을 설정해두어야한다.


* Node의 기본 console.log 또는 Express 4.x 이전 버전으로 지원 가능한 로그 출력 기능 외 morgan 등의 추가 로거 모듈을 사용하는 경우 해당되며, 파일의 형태가 아닌 콘솔 출력을 의미함. 어플리케이션 코드 내에서 제어하는 형태로 사용.


3.1 모든 요청에 대해 combined 포맷의 로그를 출력하도록 설정


var express = require('express') 

var morgan = require('morgan')   


var app = express()   


app.use(morgan('combined'))   


app.get('/', function (req, res) {   

   res.send('hello, world!') 

})


* 로그 포맷 지시자


combined : 표준 Apache combined 로그 출력 

:remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"


common : 표준 Apache common 로그 출력

:remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]


dev : 개발을 위해 response에 따라 색상이 입혀진 축약 로그 출력 

:method :url :status :response-time ms - :res[content-length]


short : 기본 설정보다 짧은 로그 출력, 응답 시간 포함 

:remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms


tiny : 최소화된 로그 출력 

:method :url :status :res[content-length] - :response-time ms


* 사전에 morgan 모듈을 설치했다고 가정했다.


* 진단 방법

  소스 코드 내 log 내용 확인 또는 개발자 인터뷰

  로그포맷 설정 값이 combined가 아니거나 그에 준하지 않는 포맷 토큰 조함으로 설정되어 있는 경우 취약함.





4. 로그 저장 주기


  ‘정보통신망이용촉진및정보보호등에관한법률’, ‘개인정보보호법’, ‘회사사규’ 등에 따라 로그 파일은 최소 6개월 이상의 기간은 보관해야한다. 또한 담당자는 로그 기록을 정기적으로 백업 및 확인 감독!


 4.1 정보 보호 관련 법 및 개인정보보호법 등에 따라 최소 아래 기간 이상은 보관해야한다.


1) 사용자접속 기록

사용자 로그인/로그아웃/정보변경 등 -> 6개월 이상


2) 개인정보취급자의개인정보처리시스템접속기록

정보주체 식별정보/개인정보취급자 식별정보/ 접속일시/접속지 정보/ 부여된 권한 유형에 따른 수행업무 등 -> 2년 이상


3) 개인정보취급자권한변경기록 

개인정보취급자 권한 생성/변경/삭제 등 -> 5년 이상


4.2 담당자는 접속 기록을 월 1회 이상 정기적으로 확인·감독하여, 접속과 관련 된 오류 및  부정행위가 발생하거나 예상 되는 경우 즉각적인 보고 조치가 되도록 해야 한다.


4.3 접속 기록이 위·변조 되지 않도록 별도의 물리적인 저장 장치에 보관하여야 하며  정기적인 백업을 수행 해야 한다.


* 진단 방법

  서버 운영 또는 담장자 인터뷰






5. 헤더 노출 정보 방지


  HTTP 요청에 대한 응답 시 헤더에 서버 이름, 버전 등의 정보가 포함되어 공격자가 해당 정보를 공격에 이용할 수 있다.


* Node의 헤더 관련 개별 설정 방법 외 helmet과 보안 관련 HTTP 헤더 모듈을 사용한 경우엥 해당된다.


5.1 Node 어플리케이션 코드 내에 모듈 사용 설정 

var express = require('express');   

var helmet = require('helmet'); 

 

var app = express(); 

 

app.use(helmet()); 


  위 설정은 Apache, nginx 등의 웹 서버 환경 설정을 통해서도 같은 설정이 가능하다.


* 진단 방법

  소스 코드 내 헤더 설정 관련 확인 또는 개발자 인터뷰





* 보안 패치


  최신의 보안 패치 필요. Node.js 뿐 아니라 추가한 Express 모듈 또한 패치에 신경 써야한다. 주기적으로 보안 패치를 적용하지 않으면 exploit 공격, 제로데이 공격 등의 서버 침해가 발생할 수 있다.


* Node.js 0.10.42 이전 0.10.x, 0.12.10 이전 0.12.x, 4.3.0 이전 4.x, 5.6.0 이전 5.x 버전들에서 원격 공격자의 조작된 Content-Length HTTP 헤더를 통한 HTTP request smuggling 공격을 허용할 수 있다.


(* HTTP Request Smuggling 공격은 Web Hacking 카테고리에 글을 포스팅 하였다. ) 


HTTP Request Smuggling


Normaltic 작성

 

HTTP Request Smuggling (HRS) ?

HTTPContent-Length 헤더 변수를 조작하여 원하는 데이터 혹은 패킷을 Smuggling(밀수) 몰래 들여보내는 것이다. 이 공격을 통해 웹 서버 앞의 방화벽(F/W)을 우회하거나, 웹 캐시 서버의 Cache-Poisoning 등을 할 수 있다.

 

* 본 자료는 http://www.cgisecurity.com/lib/HTTP-Request-Smuggling.pdf 을 참조하여 작성하였다.


(1) Web Cache Poisoning

  이 공격을 가능하게 하는 취약점은 바로 HTTPPost 패킷에 Content-Length 헤더가 2개가 들어있을 때 받아들이는 방식이 웹 캐시 서버(Proxy)와 웹 서버(W/S)가 다르다는 점이다.

 

-> 그렇다면 HTTP Post 패킷에 Content-Length 헤더가 다른 값으로 2개 들어가있으면 서버는 어떻게 처리할까?

이는 서버마다 다 다르다. 예를 들면, SunONE W/S 6.1 (SP1)의 경우 첫 번째의 Content-Length 헤더를 사용한다. 반대로 SunONE Proxy 3.6 (SP4)의 경우 두 번째의 Content-Length 헤더를 사용한다.

 

위와 같은 차이로 인해 악의적인 Web Cache Poisoning이 가능하다. SunONE W/S 6.1 (SP1)의 서버 앞에 SunONE Proxy 3.6 (SP4)Proxy 서버로 있으며 SITE라는 DNS 주소를 가진 서버라고 가정하고 공격해보겠다.


< 공격 모습 >


  위와 같은 패킷들이 서버에 전달 되었을 때, Proxy 서버의 경우 1-7 번 라인을 POST Request라고 먼저 인식하게 된다. 그리고 Content-Length 헤더가 두 개 있다는 것을 알게 되는데 여기서 Proxy서버의 경우 첫 번째 Content-Length 헤더를 무시하고 두 번째의 Content-Length 헤더를 사용하여 Body의 길이가 44라고 인식하게 된다. 여기서 8-10 번 라인이 정확하게 44 바이트이다. 그러므로 8-10 번 라인을 첫 번째 POST 패킷의 Body라고 인식하게 되는 것이다. 그 다음으로 Proxy 서버는 11-14 번 라인을 GET Request로 인식한다.

 

  이제 웹서버의 경우를 보자. 웹 서버의 경우 Proxy 서버와 달리 첫 번째 Content-Length 헤더를 사용한다. 그래서 Body 길이가 0이라고 인식한다. 그렇게 되면 8번부터 새로운 GET 요청으로 인식하게 되는 것이다. 여기서 주목할 점은 10번 라인 다음에 CRLF 이 없다는 것이다. 그렇기 때문에 11번 라인이 새로운 GET Request로 인식되지 않고 8번 라인의 GET Request 패킷의 Bla 헤더의 으로 인식 되는 것이다.

 

  정리해보면 다음 표와 같다.


< 정리 >


  ProxyWeb 서버에서 둘 모두 2개의 Request가 왔다고 인식한다. 하지만 그 2개의 내용이 다르다. 어떤 2개의 Request인지는 위의 표로 정리하였고 내용은 다음과 같다.

 

  Proxy에서 2번 째 Request“http://SITE/page to poison.html”을 요청한다고 인식하였고, Web 서버에서 2번째 Request“/poison.html”을 요청한다고 인식하였다. Web 서버는 “/poison.html” 요청의 결과를 돌려주는데 이 때 Proxy 서버가 이를 캐싱해 둔다면 Proxy 서버는 “http://SITE/page to poison.html” 요청과 “/poison.html”의 결과를 매칭시켜 저장하게 된다.

 

* 더 강력하게 효과 보는 경우

  만약 서버가 가상 호스팅하고 있는 경우라면, 효과가 더 강력할 수 있다. IP는 같으므로 HTTP 패킷 안에 host 헤더만 다시 설정하므로써 다른 많은 사이트들에 대해 공격을 할 수 있다.

 

  이를 통해 악의적으로 Web Cache를 조작할 수 있게 된다. 이 것이 Web Cache Poisoning의 공격에 활용 될 수 있는 방법이다.


(2) Firewall/IPS/IDS 우회

 

방화벽이나 IPS, IDS의 경우 특징 기반으로 악성 패킷을 검출한다. 예를 들어 “cmd.exe”라는 문자열이 URL에 들어가 있다거나 하는 특징들이 있다. 혹은 SQL Injection에서 사용하는 특징들을 생각해 볼 수 있다.

 

이 자료에서 보여주는 공격은 IIS/5.0 기반 서버에서 이루어진다. ISS/5.0는 큰 Body를 가진 POST Request를 처리할 때 버그가 일어난다. 바로, RequestContent-Type헤더에 정해진 타입이 아닌 경우 49,152 Bytes(48K) 만큼 잘라서 읽는다. 그래서 48K + X 의 크기로 보내게 되면 X 만큼의 데이터를 Smuggling할 수 있게 된다.

 

아래 공격 예시는 ISS/5.0 서버이고 앞 단에 Firewall이 설치 되어있는 경우이다. 공격 목적은 Firewall을 우회하여 서버에 “cmd.exe” 문자열이 도착하게 하는 것이다.


< 공격 패킷 >


  위와 같은 모습으로 패킷을 전달하게 되면, Firewall 입장에서는 첫 번째 Request49223 Byte 크기의 Body를 가지고 있다고 인식하게 된다. 위에서 보면 10번 라인 까지를 첫 번째 Request로 인식한다. 그 다음 Firewall은 두 번째 Request11번 라인부터 인식하게 되는데 주목할 점은 12번 라인이다. 12번 라인 뒤에 CRLF가 없다. 그렇기 때문에 13번라인부터가 두 번째 RequestBla헤더의 값에 포함되어 인식되는 것이다. 그렇게 되면 Firewall은 이 2가지 패킷을 막지 않는다. 이유는 URL에 공격 패턴이 없었기 때문이다.

 

  하지만 Firewall을 지나서 서버에 도착하면 상황이 달라진다. IIS/5.0 서버에서 첫 번째 Request를 보면 Content-Type이 없다. 그렇기 때문에 서버는 49152 크기로 Body를 잘라버린다. 그러면 1-6번 라인이 첫 번째 Request로 인식 되는 것이다. 그리고 두 번째 Request7번 라인부터 인식이 되는데, Content-Length30으로 되어있다. 그래서 Body11-12번 라인이 포함된다. 그렇게 되면 서버 입장에서는 다음 Request13번부터 받아들이게 되는데 13-15번 라인의 Request를 보면 URL“cmd.exe” 문자열이 있는 것을 확인 할 수 있다. 그렇게 되면 서버 입장에서는 “cmd.exe”를 실행되어 공격이 이루어 질 수 있다. 인식하는 Request를 정리해보면 아래 표와 같다.


< 정리 >


(3) Forward VS Backward HRS

 

일반적으로 HRS 공격은 3개의 Request를 보내는데, Content-Length를 조작하여 각각 다른 2개의 Request로 보이게 하는 원리로 공격이 일어난다. 첫 번째 예에서는 req1, req2를 웹 서버가 인식하고, req1, req3Proxy가 인식하므로써 공격이 일어났는데, 이러한 경우를 Forward Smuggling이라고 한다.


< Forward Smuggling >


  이와 반대로 Backward Smuggling이 있다.

< Backward Smuggling >


  Forward Smuggling보다 Backward Smuggling 공격이 훨씬 어렵다. 그 이유는 ProxyRequest를 받고 웹 서버에 전달하는데 그에 대한 응답을 받을 때 까지 다음 Request를 보내지 않기 때문이다. 이럴 경우 Proxy는 첫 번째 Request라고 인식하여 보내고 그에 대한 응답을 기다리는데 웹 서버의 경우 Request가 다 도착하지 않았으므로 기다리게 된다. 그러면 Deadlock에 빠지게 된다. 이렇게 보면 이론적으로 Backward Smuggling이 불가능한 것처럼 보인다. 하지만 예외의 경우가 있다.


  이번 예시는 DeleGate/8.9.2 캐시 서버를 사용한다. 그리고 웹 서버로는 IIS/6.0, Tomcat, SunONE 서버 등을 사용할 수 있다. 이번 트릭은 바로 이 것이다. 우리는 GET Request를 사용할 것이다. 그런데 이 GET RequestContent-Length 헤더와 값을 넣어서 보낼 것이다. DeleGateGET RequestContent-Length0이라고 생각한다. , Body가 없다고 생각하는 것이다. (실제로 GET에는 Body가 없다.) 반대로 웹 서버는 Content-Length 값 만큼의 Body가 있는 Request라고 생각한다. 그렇지만 웹 서버는 Body를 받기 전에 응답을 보내준다. 바로 이 점에서 Backward Smuggling이 가능한 것이다. 웹 서버는 Body가 있는 Request라고 생각하지만, GET 요청이므로 Body부분이 도착하기 전에 응답을 보내는 것이다.

 

  공격 예시는 다음과 같다.


< Backward Smuggling >


  위의 패킷을 보내게 되면, DeleGateContent-Length 헤더를 무시한다. 이유는 GET 요청이기 때문이다. 첫 번째 요청을 1-6 번 라인이라고 인식한다. 그 후 7-10번 라인 까지를 두 번째 요청이라고 생각한다. 이유는 8번 라인 다음에 CRLF가 나오지 않았기에 9-10번 라인이 Bla 헤더의 값이라고 생각하기 때문이다. 이와 반대로 웹 서버에서는 첫 번째 요청이 40 바이트 크기의 Body를 가진 요청이라고 생각한다. 그렇기에 1-6번 라인까지 받았을 때 GET이기 때문에 응답은 하되, 나머지 40 바이트 크기의 Body를 기다린다. 그 후 7-8번 라인까지 40 바이트가 들어오면 첫 번째 요청의 Body라고 인식한다. 그 후 9-10번 라인의 요청을 두 번째 요청이라고 생각하는 것이다. 이렇게 악의적으로 인식되는 요청은 3번째(후자) 이므로 Backward Smuggling이 가능하게 되는 것이다


(4) Request Hijacking ( HTTP Request Smuggling Through a Proxy Server )


  HTTP Request Smuggling 공격을 통해 XXS 공격과 비슷한 결과를 만들 수 있다. XXS 공격이란 간단히 설명하면 클라이언트(피해자) 컴퓨터에서 악성 스크립트가 실행되도록 하는 공격이다. 비슷한 결과를 만들지만 XXS 공격보다 훨씬 더 성능이 좋다. 그 이유는 HttpOnly Cookie 정보나 HTTP 인증 정보들을 직접 가져갈 수 있기 때문이다. ( Cross Site Tracing이 필요없다. )

 

(* 참고 : Cross Site Tracing : HTTPTRACE 방식을 지원 할 때 가능. )

 

  Request Hijacking의 선행 조건

 

    1. 서버 앞단에 Proxy 서버가 존재해야한다. ( Web Cache Poisoning과 달리 캐싱기능은 없어도 된다. )

    2. 해당 웹 서버에 XXS 취약점이 존재해야한다.


< Request Hijacking 원리 >


  공격 예시는 위와 같다. 위 데이터를 보내게 되면, Proxy 서버에서는 두 번째 Content-Length로 인식하고 위 전체 내용이 하나의 요청으로 전달된다. 하지만 서버에 도착해서 서버가 해석하기를 첫 번째 Content-Length로 인식하여 1-7번 라인(this=that까지) 의 요청으로 인식하고 응답한다. 그 뒤 데이터를 POST 요청으로 인식한다. 여기서 Content-Length95로 잡아주었는데 여기서 94 바이트만 제공되어있다. 그러면 나머지 한 바이트를 서버는 기다리게 된다. 이 때 피해자가 GET 방식으로 요청을 했다고 가정하자. 그렇게 되면 한 바이트를 기다리면 요청이 있는데 여기에 데이터의 맨 앞 ‘G’라는 문자가 소모된다


< 완성되는 요청 >


 

  그렇게 되면 위와 같은 요청이 완성된다. 이 요청에 대한 응답으로 cookie를 출력하는 스크립트가 피해자에게 전송되는 것이고 그렇게 피해자의 컴퓨터에서 임의의 스크립트가 실행되는 것이다.

 

  그런데, 이는 HTTP Request Smuggling을 통해 어떻게 악성 스크립트가 실행되는지를 확인한 것이다. 이를 이용해 세션정보가 있는 cookie와 인증 정보를 탈취하기 위해서는 약간의 트릭이 필요하다.


< 자바 스크립트 >


  위 자바 스크립트는 document의 텍스트 정보들을 단일 문자열로 concat한 후, 앞에서부터 300바이트를 잘라내는 동작을 한다. 이를 이용해 다음과 같은 공격 패킷들을 구성했다고 해보자.


< 스크립트를 추가한 패킷모습 >


  param1 변수부터 시작되는 body의 길이는 277바이트만 제공하였다. 이렇게 되면 나머지 300바이트를 웹 서버는 기다리게 된다. 피해자가 보낸 요청으로부터의 300바이트를 사용하게 된다. 이 상태의 응답이 클라이언트(피해자)에게 전송되고 피해자의 브라우저에서 해당 응답이 로딩될 때, 삽입한 스크립트가 실행된다. document의 텍스트 부분 앞에서 300바이트를 잘라내는데 이 안에 보통 Cookie와 인증 정보 헤더가 담겨있다. 또한 이와 함께 URL도 담겨있는데 이를 활용해 해당 사이트의 인증 정보와 쿠키를 Hijacking 할 수 있다.


(5) Request Credential Hijacking ( HTTP Request Smuggling Through a Proxy Server )

 

  클라이언트의 크리덴셜을 가지고 특정 스크립트를 실행시키는 또 다른 방법이 있다. 이 방법은 클라이언트의 크리덴셜을 가지고 악성 스크립트를 실행한다는 것으로 CSRF(Cross-Site Request Forgery) 공격과 비슷하다. 그러나 CSRF보다 훨씬더 효과가 좋다. 그 이유는 클라이언트(피해자)와 상호작용할 필요가 없기 때문이다.


< 공격 패킷 >


  이 때 피해자가 아래와 같은 요청을 했다고 가정하자.

< 피해자의 요청 >


  그렇게 되면 위 공격 패킷의 빨간 부분(GET 요청)에 최종적으로 아래와 같은 패킷이 만들어지게 된다

만들어진 위조된 요청 >

이 요청은 ‘/some_page.jsp’의 페이지에서 피해자의 크리덴셜을 가지고 요청이 되는 것이다. 그렇게 되면 CSRF와 비슷한 효과를 낼 수 있다. 만약 이 ‘/some_page.jsp’ 페이지가 비밀번호 변경 페이지라던가, 큰 돈을 전송하는 페이지였다면 피해가 컸을 것이다.


Rootkit의 원리를 이해하기위해
간단한 Rootkit을 만들었다.

특수 코드인 1018을 입력으로 넣으면 root 권한을 넘겨주는 rootkit을 만들것이다.


(Prototype)


모듈은 디바이스로 관리가 된다. 디바이스는 리눅스에서 파일처럼 다뤄진다.
디바이스와 입출력을 하기위해 open, read, write 함수를 정의해준다.
open과 read는 여기서 사용하지 않을 것이므로 모양만 만들어준다.

여기서 write함수를 채워준다.


(write 함수)


write함수를 보면 유저 영역의 입력을 받는다.
입력을 받아서 첫 4자리를 비교한다. 첫 4자리가 1018이라면
root권한인 id를 0으로 만들어준다.

Makefile을 만든다.


(Makefile)


그 후 insmod로 만든 모듈을 로딩한다.

로딩한 후, chmod를 이용하여 /dev 아래에 있는 해당 디바이스를 666으로 만들어준다.

그 이유는 루트 권한이 아닌 일반계정에서 실행할 수 있도록 하기 위해서이다.


(권한 설정)


실행하기 전 권한을 확인한 모습이다.


(권한)


일반 사용자 계정이다.

그 후 /dev 아래에 만들어진 normaltic_door 가 있다.

이 디바이스에 1018이라고 입력을 넣는다.


(매직값 입력)


그렇게 되면,

위에서 작성한 write에 의해서 사용자 권한을 0으로 만들어준다.


(root 권한 획득)


root 권한을 획득했다.

간단한 rootkit을 만들어보면서
rootkit의 원리를 이해할 수 있었다.

유저 영역이 아닌, 커널 영역에서 root권한으로 권한상승시킨다는 개념이 이렇게
커널 영역에서 이루어지는 것을 확인할 수 있었다.

* 참조

https://0x00sec.org/t/kernel-rootkits-getting-your-hands-dirty/1485


최근 워게임을 풀다가 RootKit이라는 개념을 접하게 되었다.
RootKit...
이름은 많이 들어본것 같이 익숙한듯 낯설다.

그리하여, RootKit에 대해 알아보았다.

RootKit
- 특정 시스템을 해킹 한 후 시스템의 제어권을 획득할 목적으로 설치하는 악성 프로그램.
(Post Exploit에 해당한다.)

Rootkit의 성능 지표
1) 원하는 대로 제어권을 획득 할 수 있는지
2) 탐지 되지 않는지

이 조건을 높은 수준으로 충족시키기 위해서는 커널 모드에서 동작하는 RootKit을 작성하여야 한다.

...!
....!
RootKit을 한번 제작해보고 싶어졌다. (아주 간단한 옛날 버전의 리눅스더라도...!)
그래서 나의 프로젝트에 RootKit 제작이 추가되었다.

현재, 학교의 엄청난 과제양과 이미 벌려놓은 수많은 프로젝트들이 있지만,
그래도 정말 하고싶다.! 그래서 시작!!!

(성능좋은) RootKit을 제작하기 위해서는 커널 영역에서 놀 줄 알아야한다.

그러한 의미로 LKM을 공부해보았다.
LKM 입문? 정도다.

LKM은 Loadable Kernel Module 이다.

- 커널영역은 유저영역에서 직접 접근할 수 없다. 커널영역을 사용해야할 때는 system call을 이용하여 커널영역을 사용하게 된다.

- 커널 모듈은 커널에 올라가는 모듈이다. 운영체제 리눅스는 커널 모듈들의 모음이라고 할 수 있다. 다양한 커널모듈들이 있고 각각의 모듈들이 디바이스를 관리하는 역할을 한다. 우리는 LKM을 간단히 만들어 커널영역에 올리는 작업을 해볼 것이다.

그러기 위해 필요한 것이 있다. 바로 linux 헤더이다.


(linux-headers)


자기 운영체제 버전에 맞는 것을 설치해야한다.
먼저 search로 검색을 한 후,

알맞은 운영체제 버전을 선택해서 설치하면 된다.


(설치)


그렇게 되면 준비는 끝난다.

간단한 LKM 을 만들기 위해

hello.c 파일을 작성하였다.


(hello.c)


MODULE 로 시작한 코드들은 해당 모듈에 관한 정보를 적는 것이다.

그 아래 static으로 변수를 만들어 주었다.
주의할 점은 이러한 모듈을 만들 때 전역변수를 함부로 사용하면 안된다. 이유는 이 모듈은 커널에 올라가게 되는데, 전역변수를 사용하게 되면 이곳저곳의 프로세스에 영향을 줄 수 있기 때문이다. 그렇기에 static으로 하여 해당 모듈에서만 영향력있게 만든다. 전역변수를 썼다가 혹여나 다른 프로세스에서 겹치는 변수가 있게 되어 꼬여버릴 수 있기 때문이다.

그 아래 module_param 함수는 변수에 관한 설정을 해주는 것이다. 첫번째는 변수 이름이고, 두번째는 type이다. 여기서는 char pointer 이므로 charp 라고 적어준 것이다. 마지막은 접근 권한이다.

그 아래 init과 exit 함수가 있다. init 함수는 모듈이 올라갈 때 실행되며, exit 함수는 모듈이 해제될 때 실행되는 함수이다. 여기서는 printk를 이용하여 커널 로그에다가 print하는 코드를 넣었다.

맨 마지막에 module_init과 module_exit함수로 init함수와 exit 함수를 지정해준다.

이 파일을 컴파일 하기 위하여 Makefile을 만든다.


(Makefile)


현재 로컬 머신의 버전과 일치하는 컴파일을 하기 위하여 -C 옵션을 주어 경로를 변경한 후 M 모듈은 현재 경로에다가 컴파일 하도록 한다.

make 를 입력하여 컴파일한다.


(컴파일 완료)


그러면 hello.ko 가 생기게 된다. 이 커널 오브젝트 파일은 모듈인데 이 모듈을 올려보도록 할 것이다.


(커널 모듈 올리기)


insmod 명령어로 커널모듈을 올릴 수 있다.

올라간 모듈은 lsmod를 통하여 확인 할 수 있다.


(올라간 모듈 확인)


올라간 모듈을 해제하는 법은 rmmod 이다.


(모듈 해제)


모듈을 해제하고 확인해보니 올라간 모듈 리스트에 없는 것을 확인 할 수 있다.

커널 로그를 확인해보겠다.

아까 우리가 init과 exit 에 printk를 넣었으니, 모듈이 올라갈 때 내려갈 때 로그가 찍혔을 것이다.


(로그 확인)


커널 로그의 위치는 /var/log/kern.log 이다.

이번에는 아까 만든 변수에 값을 넘겨주며 올려보겠다.


(인자 넘기기)


인자는 위와같이 넘길 수 있다.

이 올라간 모듈에 대한 정보는 어디있을까?
/sys/module에 들어가본다.

(모듈 확인)


올라간 모듈 hello에 들어가본다.


(모듈 정보)


모듈 정보들이 보인다.

여기서 우리는 parameters를 확인해 볼 것이다.



(변수 확인)


우리의 변수인 name이 보이고 안을 열어보니 Normaltic이라고 아까 우리가 입력한 값이 저장되어있는 것을 확인 할 수 있다.

이 값을 수정할 수 있을까 했더니


(파일 권한)


파일 권한은 읽기 권한으로만 되어있었다.

마지막으로 인자를 넘긴 모듈을 해제해보겠다.



(모듈 해제)


모듈 해제는 rmmod 로 같다.

그렇다면 kern.log는?


(커널 로그)


커널 로그에는 내가 넘겨준 인자와 함께 문구가 잘 찍힌 것을 확인 할 수 있었다.

LKM에 대해 간단히 이해를 해보았다.
RootKit을 만들고, 이해하는데 커널영역을 더 공부할 필요가 있을 것 같다.

참조
- http://derekmolloy.ie/writing-a-linux-kernel-module-part-1-introduction/

'Hacking > System Hacking' 카테고리의 다른 글

(RootKit) Simple Rootkit  (0) 2018.04.12
malloc에서 사용하는 syscall  (0) 2018.02.21
System Hacking - jmp (반복문)  (0) 2017.04.19
System Hacking - jmp (분기문)  (0) 2017.04.18
System Hacking - 어셈블리어(사칙연산)  (0) 2017.04.14
(* 참조 - https://sploitfun.wordpress.com/2015/02/11/syscalls-used-by-malloc/)


malloc은 메모리 할당을 위해 사용하는 함수이다. 그 내부에서는 실제로 메모리를 할당받기 위해 시스템콜을 사용한다.

두가지 brk와 mmap 시스템콜을 사용한다.


(malloc)


brk
: brk는 프로그램 break location을 증가시키므로써 메모리를 획득한다. 

brk 를 확인해보기 위해 아래와 같이 코드를 작성한다.


(brk 테스트)


코드에서 brk를 사용했는데, sbrk는 어떤 목적으로 사용하였을까?



(brk 메뉴얼)


brk 메뉴얼을 확인해보면, brk와 sbrk가 있다.

sbrk의 경우 인자 값으로 확장하고 싶은 사이즈를 적고, 리턴값으로는 확장되기 이전의 program break를 반환한다. 그렇기에 저 위의 코드 sbrk(0)를 사용하게 되면 확장 사이즈는 0이므로 program break의 변화는 없고, 이전(변화가 없으니 현재) break location을 알 수 있다.

실행한 결과를 확인해보자.



(brk 되기 전)


brk 되기 전 프로그램 Break Location의 주소이다.

실제 메모리 모습을 확인해보면


(메모리 모습(brk 전))


[heap] 영역의 끝이 프로그램 Break Location인 것을 확인 할 수 있다.

brk를 한 후 결과를 확인해본다.



(brk 후)


brk 후 프로그램 Break Location의 위치가 0x1000 늘어난 것을 확인 할 수 있다.

메모리 상황을 확인해보면


(메모리 (brk 후))


[heap]의 영역이 늘어난 것을 확인 할 수 있다.

마지막으로 brk를 원래 위치로 되돌리는 것 까지 확인해보자.


(원위치)



(메모리 상황)


brk를 이용해 다시 줄여보았다. 이로써 brk를 통해 메모리를 할당하는 방식을 알아보았다.

그 다음 방식은 mmap이다.
mmap
: mmap은 호출한 프로그램이 사용하고 있는 영역에 대해 메모리를 잡는 것이다. 

코드를 이용해 직접 확인해보자.



(테스트 코드)


mmap 되기 전 상황과 된 후 상황일 먼저 비교해볼 것이다.


(mmap 전)



(메모리 (mmap 전))


mmap 전 라이브러리 파일 영역이다.

그 후 mmap을 진행한다.



(mmap 실행)



(메모리 ( mmap 후))


기존에 있던 프로그램의 메모리 영역에 mmap을 통해 공간을 만든 것을 확인 할 수 있다.

munmap 후까지 확인해본다.



(munmap)



(메모리 ( munmap 후 ))


munmap 후 다시 프로그램에게 메모리가 반환되는 것을 확인 할 수 있었다.

'Hacking > System Hacking' 카테고리의 다른 글

(RootKit) Simple Rootkit  (0) 2018.04.12
(RootKit) LKM(Loadable Kernel Module)  (0) 2018.04.03
System Hacking - jmp (반복문)  (0) 2017.04.19
System Hacking - jmp (분기문)  (0) 2017.04.18
System Hacking - 어셈블리어(사칙연산)  (0) 2017.04.14


실습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로 셋팅된다.
-> 나중에 디버거에서 직접 확인해보겠다.



+ Recent posts