해설

Are you tired of hacking?, take some rest here.
Just help me out with my small experiment regarding memcpy performance. 
after that, flag is yours.

 

일단 ssh로 접속해보자. readme를 읽어보면 nc로 연결하란다. nc로 연결하면 memcpy의 사이즈를 지정하라는 문자열과 함께 총 10번의 입력을 해야 한다. 일단 대충 다음과 같이 입력해 보았다.

 

무슨일인지 끝까지 실행되지 않고 5번째 체크의 fast_memcpy 부분에서 멈추는거 같다. 코드를 보자.

 

// compiled with : gcc -o memcpy memcpy.c -m32 -lm
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <math.h>

unsigned long long rdtsc(){
        asm("rdtsc");
}

char* slow_memcpy(char* dest, const char* src, size_t len){
	int i;
	for (i=0; i<len; i++) {
		dest[i] = src[i];
	}
	return dest;
}

char* fast_memcpy(char* dest, const char* src, size_t len){
	size_t i;
	// 64-byte block fast copy
	if(len >= 64){
		i = len / 64;
		len &= (64-1);
		while(i-- > 0){
			__asm__ __volatile__ (
			"movdqa (%0), %%xmm0\n"
			"movdqa 16(%0), %%xmm1\n"
			"movdqa 32(%0), %%xmm2\n"
			"movdqa 48(%0), %%xmm3\n"
			"movntps %%xmm0, (%1)\n"
			"movntps %%xmm1, 16(%1)\n"
			"movntps %%xmm2, 32(%1)\n"
			"movntps %%xmm3, 48(%1)\n"
			::"r"(src),"r"(dest):"memory");
			dest += 64;
			src += 64;
		}
	}

	// byte-to-byte slow copy
	if(len) slow_memcpy(dest, src, len);
	return dest;
}

int main(void){

	setvbuf(stdout, 0, _IONBF, 0);
	setvbuf(stdin, 0, _IOLBF, 0);

	printf("Hey, I have a boring assignment for CS class.. :(\n");
	printf("The assignment is simple.\n");

	printf("-----------------------------------------------------\n");
	printf("- What is the best implementation of memcpy?        -\n");
	printf("- 1. implement your own slow/fast version of memcpy -\n");
	printf("- 2. compare them with various size of data         -\n");
	printf("- 3. conclude your experiment and submit report     -\n");
	printf("-----------------------------------------------------\n");

	printf("This time, just help me out with my experiment and get flag\n");
	printf("No fancy hacking, I promise :D\n");

	unsigned long long t1, t2;
	int e;
	char* src;
	char* dest;
	unsigned int low, high;
	unsigned int size;
	// allocate memory
	char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

	size_t sizes[10];
	int i=0;

	// setup experiment parameters
	for(e=4; e<14; e++){	// 2^13 = 8K
		low = pow(2,e-1);
		high = pow(2,e);
		printf("specify the memcpy amount between %d ~ %d : ", low, high);
		scanf("%d", &size);
		if( size < low || size > high ){
			printf("don't mess with the experiment.\n");
			exit(0);
		}
		sizes[i++] = size;
	}

	sleep(1);
	printf("ok, lets run the experiment with your configuration\n");
	sleep(1);

	// run experiment
	for(i=0; i<10; i++){
		size = sizes[i];
		printf("experiment %d : memcpy with buffer size %d\n", i+1, size);
		dest = malloc( size );

		memcpy(cache1, cache2, 0x4000);		// to eliminate cache effect
		t1 = rdtsc();
		slow_memcpy(dest, src, size);		// byte-to-byte memcpy
		t2 = rdtsc();
		printf("ellapsed CPU cycles for slow_memcpy : %llu\n", t2-t1);

		memcpy(cache1, cache2, 0x4000);		// to eliminate cache effect
		t1 = rdtsc();
		fast_memcpy(dest, src, size);		// block-to-block memcpy
		t2 = rdtsc();
		printf("ellapsed CPU cycles for fast_memcpy : %llu\n", t2-t1);
		printf("\n");
	}

	printf("thanks for helping my experiment!\n");
	printf("flag : ----- erased in this source code -----\n");
	return 0;
}

코드를 보면, dest에 입력한 size 만큼을 할당하고 memcpy 실험을 하는것 같다. slow_memcpy는 그냥 바이트 단위로 옮기는 것이고, fast_memcpy는 64 바이트 블럭 단위로 옮기는 것 같다. fast_memcpy 코드를 보면 중간에 어셈이 있고 movdqa와 movntps라는 명령어를 사용해서 뭔가 메모리 카피를 하는것 같다. 즉 movdqa를 통해 src 부분의 주소를 xmm 레지스터에 저장하고, movntps를 통해 dest 부분에 xmm 레지스터에 저장되었던 src의 주소가 가리키는 메모리 영역을 복사하는 것 같다.

 

movntps의 설명을 좀 더 찾아보면, 주소가 16바이트 단위로 align 되어 있어야 함을 알 수 있다. 그런데 앞선 입력에서는 처음에 8바이트를 입력했기 떄문에, 여기서 뭔가 꼬인것 같다. 그럼 16바이트 배수로 맞춰서 입력해보자.

 

?? 분명 16바이트 배수로 맞춰서 사이즈를 입력했음에도 중간에 멈춰 버렸다. 왜그런걸까?

 

사실 dest 메모리를 할당하는 malloc 함수는 입력 받은 size만큼만 메모리를 할당하는 것이 아니라, bookkeeping이라는 기능을 위해 추가적인 메모리를 더 할당한다. 우선 기본적으로 malloc은 memory alignment에 의해 32비트 기준으로 8바이트로 정렬된 메모리 사이즈를 할당한다. 따라서 실제 입력된 사이즈를 8바이트 배수에 맞게 패딩하여 할당하는 것이다. 여기에 추가로 bookkepping을 위한 8바이트를 더 할당하게 된다. bookkeeping은 동적 할당된 메모리의 free를 위해 저장되는 추가 정보이다.

 

따라서 입력할 size+8 이 16의 배수가 되도록 입력하면 된다.

 

'pwn' 카테고리의 다른 글

pwnable.kr - unlink  (0) 2020.08.04
pwnable.kr - asm  (0) 2020.07.22
pwnable.kr - uaf  (0) 2020.07.21
pwnable.kr - cmd2  (0) 2020.07.20
pwnable.kr - cmd1  (0) 2020.07.16

해설

Mommy, what is Use After Free bug?

 

use-after-free 버그에 관한 문제이다. uaf.cpp 코드는 다음과 같다.

#include <fcntl.h>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;

class Human{
private:
        virtual void give_shell(){
                system("/bin/sh");
        }
protected:
        int age;
        string name;
public:
        virtual void introduce(){
                cout << "My name is " << name << endl;
                cout << "I am " << age << " years old" << endl;
        }
};

class Man: public Human{
public:
        Man(string name, int age){
                this->name = name;
                this->age = age;
        }
        virtual void introduce(){
                Human::introduce();
                cout << "I am a nice guy!" << endl;
        }
};

class Woman: public Human{
public:
        Woman(string name, int age){
                this->name = name;
                this->age = age;
        }
        virtual void introduce(){
                Human::introduce();
                cout << "I am a cute girl!" << endl;
        }
};

int main(int argc, char* argv[]){
        Human* m = new Man("Jack", 25);
        Human* w = new Woman("Jill", 21);

        size_t len;
        char* data;
        unsigned int op;
        while(1){
                cout << "1. use\n2. after\n3. free\n";
                cin >> op;

                switch(op){
                        case 1:
                                m->introduce();
                                w->introduce();
                                break;
                        case 2:
                                len = atoi(argv[1]);
                                data = new char[len];
                                read(open(argv[2], O_RDONLY), data, len);
                                cout << "your data is allocated" << endl;
                                break;
                        case 3:
                                delete m;
                                delete w;
                                break;
                        default:
                                break;
                }
        }

        return 0;
}

use-after-free 버그는 free한 동적 메모리와 동일한 사이즈만큼을 재할당할 경우, 다른 힙 주소가 아니라 바로 이전에 free한 힙 주소와 동일한 주소가 할당되는점을 악용한 버그이다. 코드를 보면 Human 클래스에 give_shell 함수가 있는데, 이 함수를 어떻게든 실행하면 될 것 같다.

 

main 함수에는 총 3가지 케이스문이 있는데, 첫번째 use는 man과 woman 클래스의 멤버 함수인 introduce를 실행한다.

두번째 after는 커맨드라인 입력을 받아 첫번째 인자를 사이즈, 두번째 인자를 파일 디스크립터로 하여 해당 파일에서 len만큼을 읽어 new를 통해 할당한 메모리에 저장한다. 이 때 new는 동적할당임을 명심하자 따라서 이부분에서 uaf가 발생할 가능성이 있다. 세번째 free는 말그대로 기존의 man, woman object들을 delete 해준다. delete는 free와 같다고 생각하면 된다.

 

따라서 악용할 입력은 after에 있고, 이 부분에 입력을 잘 준 다음 free된 메모리를 재할당하여 메모리 데이터를 조작하는 식으로 공격을 수행하면 되지 않을까라고 생각할 수 있다.

 

그럼 어떤 부분을 어떻게 바꿔야 할까? 잠깐 실행 흐름을 보면서 생각해보도록 하자. 다음은 main의 각각의 swith case문에 대한 어셈 코드이다.

 

use 코드

5>:   mov    rax,QWORD PTR [rbp-0x38]
   0x0000000000400fd1 <+269>:   mov    rax,QWORD PTR [rax]
   0x0000000000400fd4 <+272>:   add    rax,0x8
   0x0000000000400fd8 <+276>:   mov    rdx,QWORD PTR [rax]
   0x0000000000400fdb <+279>:   mov    rax,QWORD PTR [rbp-0x38]
   0x0000000000400fdf <+283>:   mov    rdi,rax
   0x0000000000400fe2 <+286>:   call   rdx
   0x0000000000400fe4 <+288>:   mov    rax,QWORD PTR [rbp-0x30]
   0x0000000000400fe8 <+292>:   mov    rax,QWORD PTR [rax]
   0x0000000000400feb <+295>:   add    rax,0x8
   0x0000000000400fef <+299>:   mov    rdx,QWORD PTR [rax]
   0x0000000000400ff2 <+302>:   mov    rax,QWORD PTR [rbp-0x30]
   0x0000000000400ff6 <+306>:   mov    rdi,rax
   0x0000000000400ff9 <+309>:   call   rdx

 첫줄 부터 보면 앞 부분은 생략 되었지만 rbp-0x38 부분이 man object를 가리키는 주소가 있는 스택 부분이다. 브레이크 포인트를 걸고 이를 gdb로 확인하면 다음과 같다.

 

따라서 rax에는 0x186ec50이 저장된다. 두번째 줄은 0x186ec50이 가리키는 주소를 rax에 저장하라고 하는데, 해당 부분도 마찬가지로 확인해보자.

 

해당 부분에 있는 값들을 더 참조하고 유추해보면, 19는 25, 즉 나이를 나타냄을 알 수 있고, 0x1863c38 부분에는 Jack이라는 문자열이 담겨 있음을 알 수 있다. 그리고 0x401570 부분을 확인해보자.

 

이 부분에 무언가 주소값들이 담겨 있는거 같은데, 하나하나 확인해보자.

확인 해본 결과, 멤버 함수들을 가리키는 주소임을 알 수 있다. 그 중에서도 0x40117a는 우리가 최종적으로 실행해야할 거 같은 함수인 give_shell 함수이다.

 

여기까지 확인하고 어셈 코드를 더 보자. 세번째 줄은 rax에 8을 더한다. 원래 rax는 0x401570이고, 이 부분은 0x40117a를 가리키고 있었는데, 8을 더해 0x401578이 되어 0x4012d2인 introduce 함수를 가리키게 된다. 그리고 그 다음줄에서 정확히 0x401578이 가리키는 부분을 rdx에 저장하고, 그 뒤로 최종적으로 call rdx를 통해 introduce 함수가 호출 된다. 밑에 부분은 마찬가지로 woman의 introduce 함수를 호출하는 동일한 과정을 거친다.

 

따라서 생각의 흐름은, 원래 man object가 할당 되었던 부분의 메모리 값 0x401570을 그보다 8 적은 0x401568로 수정할 수 있다면, 그 뒤의 실행 흐름 상 자연스럽게 introduce 함수가 아닌 give_shell 함수가 실행 될거라고 예측할 수 있다.

 

그럼 uaf를 어떻게 이용하면 될까? 우선 free를 할 경우에, 원래 할당 되었던 man object와 woman object가 delete되고, after를 호출할 경우 새로 메모리를 할당하는 과정에서 이전에 free 되었던 메모리가 재할당 될 수 있음을 알면, 해당 부분의 메모리 값을 조작할 수 있을 것 같다.

 

우선 free를 해서 메모리가 어떻게 변경되는지 살펴보자. free를 한 이후에 0x186ec50 부분을 확인하면 다음과 같다.

 

원래 0x186ec50 부분에 저장되어 있던 0x401570 값이 없어졌다.

 

after 코드

0x0000000000401000 <+316>:   mov    rax,QWORD PTR [rbp-0x60]
   0x0000000000401004 <+320>:   add    rax,0x8
   0x0000000000401008 <+324>:   mov    rax,QWORD PTR [rax]
   0x000000000040100b <+327>:   mov    rdi,rax
   0x000000000040100e <+330>:   call   0x400d20 <atoi@plt>
   0x0000000000401013 <+335>:   cdqe
   0x0000000000401015 <+337>:   mov    QWORD PTR [rbp-0x28],rax
   0x0000000000401019 <+341>:   mov    rax,QWORD PTR [rbp-0x28]
   0x000000000040101d <+345>:   mov    rdi,rax
   0x0000000000401020 <+348>:   call   0x400c70 <_Znam@plt>
   0x0000000000401025 <+353>:   mov    QWORD PTR [rbp-0x20],rax
   0x0000000000401029 <+357>:   mov    rax,QWORD PTR [rbp-0x60]
   0x000000000040102d <+361>:   add    rax,0x10
   0x0000000000401031 <+365>:   mov    rax,QWORD PTR [rax]
   0x0000000000401034 <+368>:   mov    esi,0x0
   0x0000000000401039 <+373>:   mov    rdi,rax
   0x000000000040103c <+376>:   mov    eax,0x0
   0x0000000000401041 <+381>:   call   0x400dc0 <open@plt>
   0x0000000000401046 <+386>:   mov    rdx,QWORD PTR [rbp-0x28]
   0x000000000040104a <+390>:   mov    rcx,QWORD PTR [rbp-0x20]
   0x000000000040104e <+394>:   mov    rsi,rcx
   0x0000000000401051 <+397>:   mov    edi,eax
   0x0000000000401053 <+399>:   call   0x400ca0 <read@plt>
   0x0000000000401058 <+404>:   mov    esi,0x401513
   0x000000000040105d <+409>:   mov    edi,0x602260
   0x0000000000401062 <+414>:   call   0x400cf0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x0000000000401067 <+419>:   mov    esi,0x400d60
   0x000000000040106c <+424>:   mov    rdi,rax
   0x000000000040106f <+427>:   call   0x400d50 <_ZNSolsEPFRSoS_E@plt>

after 코드를 보면, 0x401020 라인에서 새로운 메모리가 할당되고 해당 주소가 rax에 들어있음을 알 수 있다. 따라서 free 후에 after를 실행하면, 재할당 되는 주소가 0x186eca0임을 확인할 수 있다. 왜 0x186ec50이 아니라 a0일까? a0이 바로 woman의 obejct를 가리키고 있던 주소기 때문이다. 따라서 한번 더 after를 실행하면 0x186ec50 메모리도 재할당됨을 확인할 수 있다. 

 

따라서 공격의 흐름은, 3->2->2->1 순서로 코드를 실행하도록하고, 0x186ec50 부분에 0x401570이 아닌 0x401568이 저장 되도록 하면 될 것 같다.

 

/tmp 디렉토리에 python -c 'print "\x68\x15\x40\x00\x00\x00\x00\x00"' >> asdf를 통해 조작할 주소값 바이트를 저장하는 파일을 만들고 다음과 같이 실행하면 쉘을 얻을 수 있다.

 

 

'pwn' 카테고리의 다른 글

pwnable.kr - asm  (0) 2020.07.22
pwnable.kr - memcpy  (0) 2020.07.21
pwnable.kr - cmd2  (0) 2020.07.20
pwnable.kr - cmd1  (0) 2020.07.16
pwnable.kr - lotto  (0) 2020.07.16

해설

Daddy bought me a system command shell.
but he put some filters to prevent me from playing with it without his permission...
but I wanna play anytime I want!

 

cmd1과 비슷한 문제이다. cmd2.c의 코드는 다음과 같다.

#include <string.h>

int filter(char* cmd){
	int r=0;
    r += strstr(cmd, "=")!=0;
    r += strstr(cmd, "PATH")!=0;
    r += strstr(cmd, "export")!=0;
    r += strstr(cmd, "/")!=0;
    r += strstr(cmd, "flag")!=0;
    return r;
}

extern char** environ;
void delete_env(){
	char** p;
    for(p=environ; *p; p++) memset(*p, 0, strlen(*p));
}

int main(int argc, char* argv[], char** envp){
	delete_env();
    putenv("PATH=/no_command_execution_until_you_becom_a_hacker");
    if(filter(argv[1])) return 0;
    printf("%s\n", argv[1]);
    system( argv[1] );
    return 0;
}

코드를 보면 cmd1과 비슷하게 filter 함수가 있고, 프로그램의 환경변수를 다 지워버리는 delete_env 함수가 있다. 따라서 cmd2는 cmd1과 다르게 PATH 변수 뿐만 아니라 환경변수가 아예 다 날라간 상태로 프로그램이 실행된다.

이 문제는 여러가지 방법으로 풀 수 있는데, subshell이라는 개념을 이용해 풀 수도 있고, shell builtin command를 이용해 풀 수도있다. 내가 푼 방법은 shell builtin command를 이용한 것이다.

 

shell builtin command는 이름 그대로 쉘 자체에 내장된 커맨드로, 환경변수 없이 그냥 실행할 수 있는 커맨드들을 말한다. 빌트인 커맨드중에 command라는 커맨드가 있는데, command -p <명령어>는 PATH 환경변수를 미리 정의된 디폴트 값으로 사용하여 명령어를 수행하는 커맨드이다. 따라서 cmd2에서 아무리 환경변수를 지워도  ./cmd2 "command -p cat *"를 통해 flag를 출력할 수 있다.

'pwn' 카테고리의 다른 글

pwnable.kr - memcpy  (0) 2020.07.21
pwnable.kr - uaf  (0) 2020.07.21
pwnable.kr - cmd1  (0) 2020.07.16
pwnable.kr - lotto  (0) 2020.07.16
pwnable.kr - blackjack  (0) 2020.07.16

해설

Mommy! what is PATH environment in Linux?

 

ssh로 접속하여 cmd1.c 코드를 확인해보자

 

#include <string.h>

int filter(char* cmd){
    int r = 0;
    r += strstr(cmd, "flag")!=0;
    r += strstr(cmd, "sh")!=0;
    r += strstr(cmd, "tmp")!=0;
    
    return r;
}
int main(int argc, char* argv[], char** envp){
    putenv("PATH=/thankyouverymuch");
    if(filter(argv[1])) return 0;
    system( argv[1] );
    return 0;
}

코드를 보면 main에서 putenv 함수를 통해 PATH 환경 변수를 날려버리고, 입력 인자를 필터링한 후 system 함수를 통해 실행 시킨다. PATH를 날려버리기 때문에 ls, cat 같은 바이너리를 바로 실행할 수 없는데, 사실 /bin/ls, /bin/cat처럼 절대 경로를 지정해주면 충분히 실행할 수 있다. 거기에 필터링에 flag 문자열이 있기 때문에 ./cmd1 "/bin/cat flag"와 같은 명령어는 필터링에 걸리게 된다. 그러나 이것도 문제가 되지 않는게 cat은 와일드카드 입력을 받을 수 있다. 즉, 현재 디렉토리 내의 모든 파일을 출력하고싶다면 cat *을 하면 되므로, ./cmd1 "/bin/cat *"을 통해 필터링에 걸리지 않고 flag 파일을 출력할 수 있다.

 

사실 정말 문제가 의도한대로 푼건지 아니면 문제가 너무 쉬운 나머지 구멍이 있었던건지는 모르겠다.

'pwn' 카테고리의 다른 글

pwnable.kr - uaf  (0) 2020.07.21
pwnable.kr - cmd2  (0) 2020.07.20
pwnable.kr - lotto  (0) 2020.07.16
pwnable.kr - blackjack  (0) 2020.07.16
pwnable.kr - coin1  (0) 2020.07.16

해설

Mommy! I made a lotto program for my homework.
do you want to play?

 

로또를 맞추는 문제이다. 접속해서 lotto.c 코드의 play 함수를 확인하면 다음과 같다.

void play(){

        int i;
        printf("Submit your 6 lotto bytes : ");
        fflush(stdout);

        int r;
        r = read(0, submit, 6);

        printf("Lotto Start!\n");
        //sleep(1);

        // generate lotto numbers
        int fd = open("/dev/urandom", O_RDONLY);
        if(fd==-1){
                printf("error. tell admin\n");
                exit(-1);
        }
        unsigned char lotto[6];
        if(read(fd, lotto, 6) != 6){
                printf("error2. tell admin\n");
                exit(-1);
        }
        for(i=0; i<6; i++){
                lotto[i] = (lotto[i] % 45) + 1;         // 1 ~ 45
        }
        close(fd);

        // calculate lotto score
        int match = 0, j = 0;
        for(i=0; i<6; i++){
                for(j=0; j<6; j++){
                        if(lotto[i] == submit[j]){
                                match++;
                        }
                }
        }

        // win!
        if(match == 6){
                system("/bin/cat flag");
        }
        else{
                printf("bad luck...\n");
        }

}

코드를 읽다보면 뭔가 미심쩍은 부분을 발견할 수 있는데, 바로 이중 루프로 제출한 번호와 랜덤으로 생성된 로또 번호를 비교하는 부분이다. 이 부분에서 바이트별로 비교를 하는데, 로또 번호와 제출한 번호가 같을 때 continue를 해줘서 다음 로또 번호로 넘어가야할거 같지만 그러지 않고있다. 그러니까 제출한 번호가 만약 중복되어 있다면, 로또 번호와 1바이트만 같아도 번호 6개를 다 맞춘것처럼 match가 6이 되게 된다.

 

하나 주의할 점은 로또 번호라고해서 숫자를 넣으면 안된다는 것이다.,lotto 와 submit 배열은 unsigned char 형 배열이고 바이트는 1~45까지 취급하기 때문에, 아스키 코드표에서 48부터가 숫자 0이므로, 실제로 제출하는 submit 배열에는 숫자가 들어가선 안된다. 따라서 범위 내에 적당한 캐릭터(ex !#$%&+ 등등)을 하나 골라서 6개 제출하면 된다.

 

예를 들어 ++++++를 제출했을 경우, 몇 번 시도하다보면 결국 flag를 얻을 수 있다.

'pwn' 카테고리의 다른 글

pwnable.kr - cmd2  (0) 2020.07.20
pwnable.kr - cmd1  (0) 2020.07.16
pwnable.kr - blackjack  (0) 2020.07.16
pwnable.kr - coin1  (0) 2020.07.16
pwnable.kr - shellshock  (0) 2020.07.15

해설

Hey! check out this C implementation of blackjack game!
I found it online

I like to give my flags to millionares.
how much money you got?

 

nc로 연결하면 블랙잭 카드 게임을 할 수 있다. 백만장자에게 플래그를 준다고 했으니, 백만 달러를 벌어야 하는 모양인데, 시드 머니가 500달러 밖에 없다. 실제였으면 현실적으로 불가능했겠지만, 포너블 문제인 이상 뭔가 방법이 있을 것이다. 배팅을 할 때 음수를 배팅해도 그대로 진행이 되는 것을 보아하니, input sanitization 과정이 없는 것 같고, 실제로 음수를 배팅한 후에 졌을 때는 돈이 오히려 올라간다.

 

따라서 백만 달러를 벌기 위해서는 -1000000(-백만)을 배팅하고 일부러 져버리면 되겠다. 블랙잭 카드게임을 모르는 분들을 위해 설명을 하자면, 내 카드 점수가 21이 초과하면 자동으로 패배하니까 질떄까지 h(hit: 카드를 받음)를 누르면 된다.

 

이런 방식으로 플래그를 얻을 수 있다.

 

'pwn' 카테고리의 다른 글

pwnable.kr - cmd1  (0) 2020.07.16
pwnable.kr - lotto  (0) 2020.07.16
pwnable.kr - coin1  (0) 2020.07.16
pwnable.kr - shellshock  (0) 2020.07.15
pwnable.kr - mistake  (0) 2020.07.15

해설

Mommy, I wanna play a game!

 

바이너리 서치 코드를 짤 수 있으면 풀 수 있다. 너무 쉬워서 해설은 생략한다. 외부에서 nc로 연결할 경우 통신 딜레이 때문에 코드를 제대로 짰어도 타임아웃 될 수 있다. 따라서 pwnable.kr 서버로 ssh 접속한 후, nc localhost 9007을 통해 연결해서 풀면 된다. 물론 pwntools를 사용하면 remote("localhost", 9007)로 연결하면 된다.

'pwn' 카테고리의 다른 글

pwnable.kr - lotto  (0) 2020.07.16
pwnable.kr - blackjack  (0) 2020.07.16
pwnable.kr - shellshock  (0) 2020.07.15
pwnable.kr - mistake  (0) 2020.07.15
pwnable.kr - leg  (0) 2020.03.04

해설

Mommy, there was a shocking news about bash.
I bet you already know, but lets just make it sure :)

 

shellshock의 관한 문제다. shellshock는 2014년 발견된 bash shell의 취약점이다. 워낙 유명한 취약점이기 떄문에 자세한 설명은 생략한다.

 

접속하여 shellshock.c 파일을 확인하면 다음과 같다.

#include <stdio.h>
int main(){
        setresuid(getegid(), getegid(), getegid());
        setresgid(getegid(), getegid(), getegid());
        system("/home/shellshock/bash -c 'echo shock_me'");
        return 0;
}

shellshock의 핵심은, bash 프로세스가 어디서 실행되냐인데, shellshock 프로그램에서 시스템 함수로 취약점이 있을법한 bash shell을 실행하는거 같다. 실제로 bash 바이너리가 해당 경로에 존재한다. shellshock 취약점이 있는지 확인하는법은 간단하다. 다음과 같은 입력을 통해 shellshock 취약점을 판단할 수 있다.

 

환경변수를 처리하는 과정에서 함수 선언부 뒤의 임의의 커맨드를 실행할 수 있는지를 판단하면 된다. 따라서 echo vulnerable!이 실행된 시점에서 shellshock 프로그램에서 실행하는 bash는 취약하다고 판단할 수 있다. shellshock 프로그램은 setuid가 걸려있기 때문에, 해당 프로그램의 서브 프로세스로 실행되는 bash가 이 권한을 물려받는다. 따라서 다음과 같이 커맨드를 입력하면 flag가 출력 된다.

 

 

'pwn' 카테고리의 다른 글

pwnable.kr - blackjack  (0) 2020.07.16
pwnable.kr - coin1  (0) 2020.07.16
pwnable.kr - mistake  (0) 2020.07.15
pwnable.kr - leg  (0) 2020.03.04
pwnable.kr - input  (0) 2019.08.26

+ Recent posts