문제 설명을 보면 bash에 관한 충격적인 뉴스가 있다고 한다.
문제 서버에 접속하여 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;
}

system 함수로 bash 셸 스크립트로 shock_me를 출력하고 있다. 문제 설명에도 나와 있듯이 bash에 관한 취약점인것 같아서 bash 취약점을 키워드로 검색해보니 bash shellshock 취약점을 찾을 수 있었다.
환경변수와 함께 선언되는 함수 뒤에 원하는 명령어를 삽입할 수 있는 취약점이다.
환경변수를 설정할 때 (){ return; };처럼 함수를 설정하고 ; 뒤에 실행할 명령어를 입력하면 된다.


pwd 명령어를 실행하도록 입력하였더니 shellshock 에서 bash명령을 실행할 때 등록된 환경변수를 읽어와서 pwd명령을 실행한 결과를 보여준다. 따라서 다음과 같이 flag를 출력하도록 하고 ./shellshock를 실행하면 shellshock_pwn 계정의 권한으로 flag를 읽은 결과를 출력해준다.

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] Poet  (0) 2021.07.27
[HackCTF] RTL_World  (0) 2021.07.24
[pwnable.kr] input  (0) 2021.06.15
[HackCTF] BOF_PIE  (0) 2021.06.01
[LoS] golem  (0) 2021.05.19

문제에 접속하여 flag 파일을 읽으려하면 권한 문제로 읽히지 않는다.

input파일을 실행해보면 다음과 같이 출력이 나온다.

또한 input.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
#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 != 100) return 0; // 인자가 100이 아니면

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, 4, 1, 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;

// port 정보

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, 4, 0) != 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;

}

Colored by Color Scripter
cs

 

//argv로 주석이 되어 있는 부분의 코드를 보면 인자의 크기가 100이 아니면, 인자의 'A'인덱스 즉 65번째 인덱스가 "\x00"가 아니고, 인자의 'B' 인덱스 즉 66번째 인덱스가 "\x20\x0a\x0d"가 아니면 프로그램을 종료하고, 위 조건들을 만족시키는 경우 Stage 1 clear을 출력한다.

 

//stdio로 주석이 되어 있는 부분의 코드를 보면 read 함수로 buf를 읽어오는데, 0(stdin)이 "\x00\x0a\x00\xff"이 아닐 경우, 2(stderr)이 "\x00\x0a\x02\xff"가 아닐 경우 조건을 프로그램을 종료하고, 조건들을 만족시킬 경우 Stage 2 clear을 출력한다.

 

//env로 주석이 되어 있는 부분의 코드를 보면 getenv()함수로 환경변수 "\xde\xad\xbe\xef"의 값이 "\xca\xfe\xba\xbe"가 아닐 경우 프로그램을 종료하고, 조건을 만족하면 Stage 3 clear을 출력한다.

 

//file로 주석이 되어 있는 부분의 코드를 보면 fopen()함수로 "\x0a"라는 이름의 파일을 열고 파일을 읽어 파일의 내용이 "\x00\x00\x00\x00"가 아니면 프로그램을 종료하고, 조건을 만족하면 Stage 4 clear을 출력한다.

 

//network로 주석이 되어 있는 부분의 코드를 보면 sockaddr_in 구조체를 사용하고 있고, 소켓 생성에 실패(socket 함수 -1 반환)하면 socket error을 출력하고 프로그램을 종료한다. saddr의 포트 정보에 인자의 'C' 인덱스 즉 67번째 인덱스의 값을 넣고, bind 함수로 소켓 통신할 준비를 해준다. 이 과정에서 에러가 발생할 경우 bind error use another port를 출력하고 프로그램을 종료한다. accept()함수로 소켓을 연결하고, 에러가 발생할 경우(return -1) accept error tell admin을 출력하고 프로그램을 종료한다. 이후 소켓에서 4바이트를 받아와서 받아온 값이 "\xde\xad\xbe\xef"가 맞다면 Stage 5 clear을 출력한다.

 

위 5가지의 조건을 모두 만족시키면 플래그를 읽을 수 있다.

 

문제를 해결하기 위해 pwntools를 이용하여 파이썬 코드로 작성하였다.

첫번째 조건을 만족시키기 위해 인자값을 조건에서 주어진 대로 설정하고, 인자를 전달하려면 process 함수의 argv 옵션을 이용하여 전달할 수 있다.

 

두 번째 조건을 만족시키기 위해 stdin으로는 "\x00\x0a\x00\xff"를 전달하였고, stderr일 경우 전달하는 파일을 만들어 "\x00\x0a\x02\xff"을 저장하여 process함수의 stderr옵션을 이용하여 전달하였다.

 

세 번째 조건을 만족시키기 위해 process함수의 env 옵션을 이용하여 환경변수를 설정하였다.

 

네 번째 조건을 만족시키기 위해 조건에 주어진 이름의 파일을 만들고, 파일 내용도 조건에 주어진 대로 지정해 주었다.

 

다섯 번째 조건을 만족시키기 위해 인자의 67번째('C')인덱스에 포트를 지정하고 지정한 포트로 nc 연결을 하여 조건에서 주어진 값을 전달하였다.

따라서 코드로 작성하면 다음과 같다.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22
from pwn import \\\*



argvs = [str(i) for i in range(100)]

argvs[65] = '\x00'

argvs[66] = '\x20\x0a\x0d'

argvs[67] = '3000'



p = process(executable='/home/input2/input', argv=argvs, stderr=open('./stderr'), env={'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'})



pay = '\x00\x0a\x00\xff'

with open('./stderr', 'w') as f:

f.write('\x00\x0a\x02\xff')



with open('\x0a', 'w') as f:

f.write('\x00\x00\x00\x00')



p.sendline(pay)



r = remote('localhost',3000)

r.send('\xde\xad\xbe\xef')



p.interactive()

Colored by Color Scripter
cs

또한 flag파일은 현재 경로에 없기 때문에 ln \\-s /home/input2/flag flag 명령으로 현재 경로에 심볼릭 링크를 걸어준 후 파이썬 코드를 실행하면 플래그를 얻을 수 있다.

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] RTL_World  (0) 2021.07.24
[pwnable.kr] shellshock  (0) 2021.06.16
[HackCTF] BOF_PIE  (0) 2021.06.01
[LoS] golem  (0) 2021.05.19
[HackCTF] Offset  (0) 2021.05.13

문제를 접속하여 mistake의 소스코드를 보면 다음과 같다.

PW_LEN은 10으로, XORKEY는 1로 정의하고 있다.

xor 함수에서는 문자열과 길이를 인자로 받아 길이만큼 반복하며 문자열의 각 문자를 1씩 xor 연산을 한다.

main 함수의 첫번째 if문을 보면 할당연산자보다 비교연산자가 우선순위가 높아서 비교연산을 먼저 수행하면 open함수의 리턴값은 파일을 잘 읽어와서 fd 값을 반환하고, fd값은 음이 아닌 정수이기 때문에 비교 연산의 결과는 거짓이므로 fd에는 flase(0)값이 들어가고, 조건문은 수행하지 않는다.

 

PW_LEN+1 크기의 pw_buf를 선언하고,  if 함수의 조건식에서 read함수를 실행한다. fd값이 위에서 0으로 저장되었기 때문에 fd 0 은 stdin 이라 PW_LEN만큼 pw_buf에 입력을 받는다. 입력을 받으면 크기는 0보다 커지기 때문에 not 연산으로 조건식은 거짓이 되어 조건문은 실행하지 않는다.

 

PW_LEN + 1 크기의 pw_buf2를 선언하고, 입력받는다. 이후 xor함수로 xor연산을 진행한다. 이후 조건문에서 xor한 pw_buf2와 pw_buf가 같다면 시스템 함수로 플래그를 출력할 수 있다.

 

xor 함수는 각 자리를 1로 xor 연산 하므로 1 xor 1 = 0 이 되므로 첫번째 입력값에는 0을 10개, 2번째 입력에서는 1을 10개 입력하면 플래그를 확인할 수 있다.

문제 주소에 접속하여 문제의 소스코드를 보면 다음과 같다.

rand 함수를 이용해 변수 random에 값을 저장하는데, rand()함수만 사용하면 프로그램이 생성될 때 값이 정해지기 때문에 계속 실행해도 같은 값이 저장되어 있다. key를 입력받아서 key ^ random 값이 0xdeadbeef면 시스템 함수로 flag를 읽을 수 있다. random 값을 알아야 하기 때문에 gdb로 프로그램을 실행시켜 main 함수를 보면 다음과 같다.

rand 함수가 실행되고 난 후(main+18)에 breakpoint를 걸고 실행한 후에 현재 레지스터를 확인해주면 다음과 같다.

산술연산 및 함수 반환값은 rax에 들어가기 때문에 0x6b8b4567이 rand()함수로 반환된 값이다. 

0x6b8b4567과 0xdeadbeef를 xor한 값이 passcode에 key로 입력되면 if 조건문을 만족시킬 수 있을 것 같다.

두 값을 xor 연산 후 10진수로 변환하면 3039230856이 나온다. random 을 실행하고 10진수로 변환한 값을 넣으면 플래그를 획득할 수 있다.

 

서버에 접속하여 passcode.c 파일을 보면 다음과 같다.

main 함수에서 welcome 함수와 login 함수를 실행하고 있고, wlecome 함수에서는 이름을 입력받아 출력하고 있고, login함수에서는 passcode1과 passcode2를 입력받고, 두 값이 모두 조건과 일치하면 시스템 함수로 cat flag를 실행한다. 그러나 두 값을 입력받는 곳을 보면 주소 연산자를 사용하고 있지 않아서 입력받아도 passcode1과 passcode2에는 입력받은 값이 들어갈 수 없다.

또한 login 함수에 있는 fflush(stdin)은 입력 버퍼를 지우는 코드이다. 그러나 리눅스에서 해당 코드는 실행이 안된다고 한다. 

welcome함수와 login함수를 gdb로 보면 다음과 같다. 

스택의 구조를 생각해보면 welcome 함수에서 name을 입력받기 전 0x70만큼의 공간을 확보하고 있고, login함수에서는 passcode를 입력받기 전 0x10만큼의 공간을 확보하고 있다. name의 크기는 100바이트 지만 0x70-0x10=0x60은 10진수로 96이므로 4바이트가 남고, int형 정수인 passcode1의 값을 변경시킬 수 있다. fflush의 got을 flag를 출력하는 시스템 함수의 주소로 바꾸면 flag를 출력할 수 있을 것 같다.

fflush plt에 들어가 fflush의 got주소를 구할 수 있다.

fflush의 got주소는 0x0804a004이다.  시스템 함수의 시작점은 0x080485e3이지만 scanf함수에서 정수로 받기 때문에 10진수로 변환하면 134514147 이다. 

따라서 다음과 같이 payload를 작성하면 플래그를 얻을 수 있다.

 

문제 파일을 file 명령어로 확인해보면 리눅스 64비트 실행파일임을 확인할 수 있다.

gdb로 파일을 열어보면 함수 목록도 확인할 수 없었고, main 함수도 확인할 수 없었다.

문제 설명을 보니 pack 라는 단어가 있었고, 리버싱 문제라고 한다. 그래서 exeinfo라는 툴을 통해 패킹 된 파일인지 확인해 보았다.

upx라는 패커로 패킹된 것을 확인할 수 있었고, exeinfo에서 제공해주는 github 사이트에서 언팩 툴을 구할 수 있었다.

upx.exe -d 옵션으로 언패킹 할 수 있었다.

언팩 된 flag 파일을 아이다로 열어보면 main 함수를 확인할 수 있었고 main 함수에서 flag를 들어가 보면 플래그로 보이는 문자열을 찾을 수 있었다.

 

문제에 접속하여 파일 목록을 보면 col과 flag, col.c 파일이 있고, flag 파일은 col 계정으로 읽을 권한이 없으며 col에 setuid가 걸려있기 때문에 col을 통해 flag파일을 읽을 수 있을 것 같다. col의 소스 파일은 다음과 같다.

hashcode 변수에 0x21DD09EC로 설정되어 있다.

check_password 함수를 보면 인자를 정수로 형변환하고 있고, 4번 반복하여 res에 하나씩 더한다. 정수형 변수이기 때문에 4바이트 이다. res = ip[0] + ip[1] + ip[2] + ip[3] + ip[4]로 총 20바이트가 된다.

프로그램을 실행할 때 인자가 없으면 usage로 시작하는 문자열이 출력되면서 종료되고, 인자 크기가 20이 아니면 passcode 가 꼭 20바이트가 되야 한다는 내용의 문자열이 출력되며 프로그램이 종료된다. 

hashcode의 값(0x21DD09EC)과 check_password 함수에 인자를 넣은 값이 같다면 시스템 함수로 flag 파일을 읽는다.

0x21DD09EC를 5로 나누면 0x6C5CEC8가 나오므로  0x6C5CEC8 * 4 + 0x6C5CECC 는 0x21DD09EC가 된다. col을 실행시키면서 저 값을 리틀 엔디언으로 넣으면 다음과 같이 플래그를 얻을 수 있다.

 

'Project H4C Study Group' 카테고리의 다른 글

[Project H4C][pwnable.kr] flag  (0) 2021.04.02
[Project H4C][pwnable.kr] bof  (0) 2021.04.01
[Project H4C][pwnable.kr] fd  (0) 2021.03.30
[Project H4C] FTZ 12  (0) 2021.03.29
[Project H4C] FTZ (8 ~11)  (0) 2021.03.27

문제 서버에 접속하면 다음과 같이 flag 파일이 있고, set uid가 걸려있는 파일이 있다.

flag 파일은 fd 계정으로는 읽을 권한이 없어서 setuid가 걸린 fd를 통해 flag파일을 확인할 수 있을 것 같다. fd의 소스코드로 보이는 fd.c의 내용은 다음과 같다.

fd를 실행할 때 인자가 없으면 pass로 시작하는 문자열을 출력하고 프로그램을 종료한다.

fd를 argv[1] - 0x1234로 초기화하고 len에 read함수로 buf에서 32바이트를 읽어오고 있다. read함수의 첫번째 인자는 fd(파일 디스크립터)라고 한다. 파일 디스크립터는 0은 표준 입력, 1은 표준 출력, 2는 표준 에러 이다. 따라서 위 소스에서 fd가 0이 되면 입력 받을 수 있고, 입력받은 내용이 buf에 들어간다. 그리고 buf가 LETMEWIN이라면 시스템 함수로 flag를 출력한다.

fd가 0이되려면 0x1234는 4660이므로 인자로 4660을 넣으면 fd가 0이되서 표준 입력으로 read함수를 실행하기 때문에 LETMEWIN을 입력하면 조건문을 만족시켜 플래그가 출력된다.

'Project H4C Study Group' 카테고리의 다른 글

[Project H4C][pwnable.kr] bof  (0) 2021.04.01
[Project H4C][pwnable.kr] collision  (0) 2021.03.30
[Project H4C] FTZ 12  (0) 2021.03.29
[Project H4C] FTZ (8 ~11)  (0) 2021.03.27
[Project H4C] FTZ (1 ~ 7(문제오류))  (0) 2021.03.24

+ Recent posts