달력

4

« 2024/4 »

  • 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
2017. 8. 7. 17:29

[문제풀이] Nebula, Level16 Wargames/e-exercises.com2017. 8. 7. 17:29

※ LEVEL 16


Q. There is a perl script running on port 1616.


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
#!/usr/bin/env perl
 
use CGI qw{param};
 
print "Content-type: text/html\n\n";
 
sub login {
  $username = $_[0];
  $password = $_[1];
 
  $username =~ tr/a-z/A-Z/# conver to uppercase
  $username =~ s/\s.*//;        # strip everything after a space
 
  @output = `egrep "^$username" /home/flag16/userdb.txt 2>&1`;
  foreach $line (@output) {
      ($usr$pw= split(/:/$line);
  
 
      if($pw =$password) {
          return 1;
      }
  }
 
  return 0;
}
 
sub htmlz {
  print("<html><head><title>Login resuls</title></head><body>");
  if($_[0== 1) {
      print("Your login was accepted<br/>");
  } else {
      print("Your login failed<br/>");
  }    
  print("Would you like a cookie?<br/><br/></body></html>\n");
}
 
htmlz(login(param("username"), param("password")));
cs



A. 이번 문제는 명령어 인젝션 문제이다. 

이전에도 구동중인 서비스에 명령어 인젝션하는 문제가 있었는지는 가물가물하지만, 인젝션 문제는 자주 나왔던거 같다. 가벼운 트릭 하나만 쓰면 쉽게 풀리는 문제니까 바로 문제풀이 들어가도록 하겠다. 


[그림 1] 실행 파일 작성


flag16 계정이 접근할 수 있는 위치에 파일을 생성한 뒤 other 권한에 실행 권한을 부여한다. 우리는 명령어 인젝션을 할 것이니까~~!


[그림 2] 명령어 인젝션


명령어를 실행시키기 위해 ` 문자로 감싸고 경로에 별표를 삽입하여 모든 경로를 탐색할 수 있게 한다. 

요것이 바로 트릭이라면 트릭~ 사전에 이 문자의 기능을 알고 있었다면 쉽게 풀었을 것이다 :)


[그림 3] 플래그 획득



플래그 획득~ 






:
Posted by einai
2017. 7. 25. 20:56

[문제풀이] Nebula, Level15 Wargames/e-exercises.com2017. 7. 25. 20:56

※ LEVEL 15


Q. 

strace the binary at /home/flag15/flag15 and see if you spot anything out of the ordinary.


You may wish to review how to “compile a shared library in linux” and how the libraries are loaded and processed by reviewing the dlopen manpage in depth.


Clean up after yourself :)



A. 이 문제는 라이브러리를 로드하는 순서의 우선 순위를 이용하는 문제이다. 

이번 문제는 차근차근 확인해보도록 하겠다. 


[그림 1] 문제 실행 화면


문제에서 주문한 바와 같이 strace 명령어를 이용하여 프로그램의 상태를 확인해보았다. 참고로 strace 명령어는 간단히 말하자면 시스템 함수 호출과 시그널을 추적하는 툴이다. 


[그림 1]에서 보는 바와 같이 "libc.so.6"을 open 함수의 인자 값으로 설정하여 호출하지만 계속 실패하게 된다. 


[그림 2] objdump 명령어로 섹션 확인 


objdump 명령어를 통해 문제를 확인해보면 RPATH 가 설정되어 있는 것을 알 수 있다. 이 RPATH는 실행 파일이나 라이브러리에 런타임 시 필요한 라이브러리를 찾기 위해 설정해 놓는다(필수 설정 값은 아님). 


단, 해당 설정을 해놓으면 /lib, /usr/lib 과 같은 표준 라이브러리 경로를 검색하기에 앞서 RPATH로 설정한 경로를 검색한다. 그럼 이제 우리는 동일한 라이브러리 이름을 갖는 라이브러리를 만들어 해당 문제에서 사용하는 함수의 이름을 사용하여 함수를 만들 것이다.  


[그림 3] 라이브러리 코드


[그림 3]을 컴파일하여 라이브러리로 만들 것이다. 간단히 소스코드 내용을 말하자면 생성자를 활용하여 메인 함수보다 먼저 실행하도록 하였고 이는 system 함수를 호출한다. 물론 문제에서 동작하는 다른 함수를 대체해도 문제가 될 것은 없으나 좀 더 번잡스러워진다. 그 이유는 선택한 함수가 실행되기 전까지의 모든 함수를 라이브러리에 만들어줘야 하기 때문이다. 같은 이유로 __cxa_finalize 함수가 선언되었다. 


아래의 [그림 4]는 __cxa_finalized 함수가 선언되기 전에 발생한 에러 메시지이다. 단순 참고용 !


[그림 4] 에러 구문 


[그림 5] 컴파일 후 문제 실행 


마지막으로 해당 위치에 libc.so.6 이름의 라이브러리 파일을 생성한 뒤 문제를 실행하면 쉘을 획득할 수 있다. 



:
Posted by einai

이번 문제는 윈도우 커널과 관련있는 문제이다 .


윈도우 커널 쪽 분석은 시도한 적도 없고 굉장히 생소해서 신선한 경험을 해볼 수 있었다. 

이 문제는 키보드를 후킹하는 드라이버 파일을 분석하여 문제를 푸는 것이다. 문제를 푼 지금도 드라이버 동작 과정이나 커널 내부 동작 과정이 정확히는 그려지지 않고 아 이런식으로 동작하겠구나 정도로 겉핥기식 개념만 잡은 것 같다. 이 문제를 계기로 윈도우 커널쪽도 좀 더 공부해봐야 겠다.


그럼 문제풀이를 시작해보겠다. 



문제를 다운로드 받아서 풀면 위와 같이 3개의 파일로 구성되어 있다. 

아래는 ReadMe 파일인데 인증을 소문자로 해달라고 부탁하고 있다. 



일단 문제를 동작시켜 보았다. 맨 처음엔 비활성화 된 입력란이었지만 옆에 버튼을 누르면 활성화가 되면서 문자를 입력할 수 있게 변경된다. 문자열을 입력하고 Check를 클릭하면 내가 입력한 값이 맞았는지 틀렸는지 확인할 수 있다. 




우선 WindowsKernel.exe 파일은 SCM을 이용하여 드라이버를 로드시키고 있다. SCM을 이용하면 레지스트리 키가 생성되며 드라이버는 페이징되지 않는다. 아래 캡쳐는 로드되는 부분의  과정 중 일부이다. 



위 과정 중 StartService 함수를 만나면 아래와 같이 드라이버가 로드된다. 이는 Winker.sys 드라이버에 삽입된 디버깅 메시지이다. 



일단 WindowsKernel.exe 에서 DeviceIoControl 함수를 통해 디바이스 드라이버에 IRP 처리를 요청할 수 있다. 드라이버는 특정 IRP 요청에 맞춰 IRP를 처리할 수 있게 설계할 수 있다. 이는 드라이버 오브젝트의 MajorFunction을 수정하는 것으로 가능하다. DeviceIoControl 함수로 IRP 요청을 보낼 경우 IRP_MJ_DEVICE_CONTROL 에 설정된 함수에서 이를 처리하게 된다. 아래 캡쳐는 그 과정을 보여준다. 



위에서는 CREATE, CLOSE, DEVICE_CONTROL과 관련된 Major Function을 초기화해주고 있다. 실제 DEVICE_CONTROL 함수 쪽 내부를 따라 들어가면 몇몇 변수를 초기화해준다. 


그리고 이 문제를 풀기 위해 다른 중요한 개념은 DPC이다. 

원래는 여기에서 주저리 주저리 설명을 하려했는데 지저분하고 흐름을 끊어버리는 것 같아서 다음에 시간을 내서 별도로 작성하도록 하겠다. 



위에 있는 내용은 DPC 객체를 초기화하는 과정이다. EDI 레지스터에 KeInitalizeDpc 함수의 주소가 들어있다. 여기서 설정된 루틴이 이후에 해답을 찾을 때 매우매우 중요한 역할을 한다. 일단 이 부분에서는 초기화만 하고 지나간다. 



이후 흐름을 따라 내려가다보면 다시 한번 DPC 객체를 초기화하고 이를 큐에 삽입한다. 여기서 삽입된 DPC 루틴은 IDT의 키보드 관련 ISR을 후킹한다. 여기까지 왔으면 거의 다 푼거나 마찬가지다. 해당 루틴이 실행되고 나면 아래와 같이 IDT가 변경된다. 



해당 부분을 분석해보면 READ_PORT_UCHAR 함수를 이용하여 키보드가 입력할 때마다 값을 가져오고 이 값을 비교한다. 해당 함수를 통해 입력한 문자를 확인하면 보통 아스키코드 값이랑은 다르다. 어떤 의미인지 정확히는 모르겠지만 이 값을 이용하여 비교문을 잘 통과하면 답을 얻을 수 있다. 






:
Posted by einai

※ LEVEL 13


Q. There is a security check that prevents the program from continuing execution if the user invoking it does not match a specific user id.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
 
#define FAKEUID 1000
 
int main(int argc, char **argv, char **envp)
{
  int c;
  char token[256];
 
  if(getuid() != FAKEUID) {
      printf("Security failure detected. UID %d started us, we expect %d\n", getuid(), FAKEUID);
      printf("The system administrators will be notified of this violation\n");
      exit(EXIT_FAILURE);
  }
 
  // snip, sorry :)
 
  printf("your token is %s\n", token);
  
}
cs


A. 이는 간단한 리버싱 문제이다. "// snip, sorry : )" 라고 표시되어 삭제된 코드 부분을 맞추는 문제이다. 


위의 14번째 라인만 우회하면 토큰 생성 로직으로 넘어가게 되는데 어셈블리어를 스스로 분석해도 되고 그냥 넘어가도 알아서 토큰이 생성되어 출력되므로 뭐 편할대로 하면된다. 간단하니까 별도 설명없이 캡쳐만 첨부하도록 하겠다. 


[그림 1] 로직 우회 후 토큰이 생성되는 것을 확인


[그림 2] 토큰 값을 패스워드로 사용하여 로그인 




※ LEVEL 14


Q. This program resides in /home/flag14/flag14. It encrypts input and writes it to standard output. An encrypted token file is also in that home directory, decrypt it :)


A. 이는 토큰 파일에 암호화되어 있는 문자열을 복호화하는 문제이다. 이 또한 굉장히 단순한 리버싱 문제이다. 


[그림 3] 토큰 값 확인 


[그림 4] 프로그램을 실행하여 암호화 로직 확인 


보는 바와 같이 굉장히 간단한 암호(?)화 방식이다. 실행 방식만 봐도  규칙을 알 수 있다. 문자열 인덱스 값을 문자 아스키코드에 더해 나오는 방식이다. 그럼 뭐 간단히 코드를 짜서 복호화해도 되고 얼마 안되니 수동으로 해도 되고.. 


[그림 5] 토큰 값 확인 


[그림 6] 플래그 확인 



이번 두 문제는 오히려 이전 레벨보다도 너무나 쉬웠다.

이런 문제들도 가끔 나와야지 ~ ㅋ






:
Posted by einai

※ LEVEL 11


Q. The /home/flag11/flag11 binary processes standard input and executes a shell command.

There are two ways of completing this level, you may wish to do both :-)


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
101
102
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
 
/*
 * Return a random, non predictable file, and return the file descriptor for it.
 */
 
int getrand(char **path)
{
  char *tmp;
  int pid;
  int fd;
 
  srandom(time(NULL));
 
  tmp = getenv("TEMP");
  pid = getpid();
  
  asprintf(path, "%s/%d.%c%c%c%c%c%c", tmp, pid,
      'A' + (random() % 26), '0' + (random() % 10),
      'a' + (random() % 26), 'A' + (random() % 26),
      '0' + (random() % 10), 'a' + (random() % 26));
 
  fd = open(*path, O_CREAT|O_RDWR, 0600);
  unlink(*path);
  return fd;
}
 
void process(char *buffer, int length)
{
  unsigned int key;
  int i;
 
  key = length & 0xff;
 
  for(i = 0; i < length; i++) {
      buffer[i] ^= key;
      key -= buffer[i];
  }
 
  system(buffer);
}
 
#define CL "Content-Length: "
 
int main(int argc, char **argv)
{
  char line[256];
  char buf[1024];
  char *mem;
  int length;
  int fd;
  char *path;
 
  if(fgets(line, sizeof(line), stdin) == NULL) {
      errx(1"reading from stdin");
  }
 
  if(strncmp(line, CL, strlen(CL)) != 0) {
      errx(1"invalid header");
  }
 
  length = atoi(line + strlen(CL));
  
  if(length < sizeof(buf)) {
      if(fread(buf, length, 1, stdin) != length) {
          err(1"fread length");
      }
      process(buf, length);
  } else {
      int blue = length;
      int pink;
 
      fd = getrand(&path);
 
      while(blue > 0) {
          printf("blue = %d, length = %d, ", blue, length);
 
          pink = fread(buf, 1sizeof(buf), stdin);
          printf("pink = %d\n", pink);
 
          if(pink <= 0) {
              err(1"fread fail(blue = %d, length = %d)", blue, length);
          }
          write(fd, buf, pink);
 
          blue -= pink;
      }    
 
      mem = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
      if(mem == MAP_FAILED) {
          err(1"mmap");
      }
      process(mem, length);
  }
 
}
cs


A. 이 문제의 흐름 자체는 복잡하지 않은데 답을 얻기 위해선 조금 센스가 필요해보인다. 

물론, 나는 그 센스가 없었다 ... ㅠㅠ


우선 이 문제를 풀기 위해서 접근할 방식은 내가 입력한 크기가 1024 바이트가 넘어 else 구문으로 도달하도록 할 것이다. 흐름을 간단히 정리해보면 아래와 같다. 


1. 비교 구문을 우회하기 위한 값 입력

Content-Length: 2048


2. flag11 계정의 권한을 얻기 위해 flag11 계정의 .ssh 디렉토리에 Level11 계정의 공개키를 복사하기 위해 이전에 Level 05 문제에서 나왔던 ssh 직접 접속 기능을 사용할 것이다. 


3. 무작위로 생성되는 경로 예측 

TEMP 환경변수와 PID, random 함수의 결과 값의 조합으로 생성되는 경로를 예측한다. 


4. 예측한 경로에 심볼릭 링크를 생성 한 후 2번에서 생성한 값을 전송

말 그대로, 값을 전달한다. 


5. level11 계정으로 flag11 계정에 ssh로 로그인한다. 

ssh flag11@nebula 



그럼 작성한 코드와 캡쳐를 이용하여 간단한 설명 들어가겠다. 


[그림 1] 비대칭키 생성


ssh 접속에 사용될 비대칭키 쌍을 생성한다. 이는 이후에 flag11 홈디렉토리 이하 .ssh/authorized_keys로 저장될 것이다. 여기까지 준비가 되었다면 문제를 풀기 위한 핵심 로직(?)을 봐보도록 하겠다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int getrand(char **path, int pid, int time)
{
char *tmp;
int fd =  0;
 
srandom(time);
 
tmp = getenv("TEMP");
asprintf(path, "%s/%d.%c%c%c%c%c%c", tmp, pid,
  'A' + (random() % 26), '0' + (random() % 10),
  'a' + (random() % 26), 'A' + (random() % 26),
  '0' + (random() % 10), 'a' + (random() % 26));
  return fd;
}
 
 
pid = getpid()+1;
 
getrand(&path, pid, time(NULL));
symlink("/home/flag11/.ssh/authorized_keys",path);
cs


여기에서 추측해야 하는 건 문제에서 생성하는 랜덤한 경로(random 함수의 결과 값과 PID)이다. random 함수의 경우 seed 값이 동일하다면 반환하는 값이 동일할 것이고, PID의 경우 파이프라인을 이용하여 값을 전달할 때 현재의 프로세스에서 1을 더한 값으로 나타난다고 한다. 이러한 과정들이 위 코드에서 1번 라인부터 20번 라인에 해당한다.


[그림 2] 로그인 성공 


문제를 봐보면 이 문제의 답을 풀 수 있는 방법이 2가지로 하였는데 어디를 찾아봐도 두 가지를 찾을 순 없었다. 문제가 간단해보이면서도 답을 얻기가 어려웠다. 일단 내가 발견한 해답은 이것 뿐이었다.




※ LEVEL 12


Q. There is a backdoor process listening on port 50001.


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
local socket = require("socket")
local server = assert(socket.bind("127.0.0.1"50001))
 
function hash(password)
  prog = io.popen("echo "..password.." | sha1sum""r")
  data = prog:read("*all")
  prog:close()
 
  data = string.sub(data, 140)
 
  return data
end
 
 
while do
  local client = server:accept()
  client:send("Password: ")
  client:settimeout(60)
  local line, err = client:receive()
  if not err then
      print("trying " .. line) -- log from where ;\
      local h = hash(line)
 
      if h ~= "4754a4f4bd5787accd33de887b9250a0691dd198" then
          client:send("Better luck next time\n");
      else
          client:send("Congrats, your token is 413**CARRIER LOST**\n")
      end
 
  end
 
  client:close()
end
cs


A. 이는 단순하게 명령어 인젝션으로 해결할 수 있었다.

명령어를 인젝션해서 플래그 값을 얻거나 패스워드 검증 로직을 우회할 수 있다.


아래는 패스워드 검증 로직을 우회하는 부분이다. 


[그림 3] 패스워드 검증 로직 우회





워게임은 센스도 필요하고 기반 지식도 필요하고~

한 문제에 오래 매달리자니 다른 공부가 안되고, 적당히 하고 해답을 보자니 뭔가 허무하고. 

이 간격을 조절하는 게 어렵넹 ㅠㅠ


욕심부리지 말고 천천히 가자 천천히~ 꾸준히 ~ 




Reference

[1] graugans/nebula-level11.md, https://gist.github.com/graugans/88e6f54c862faec8b3d4bf5789ef0dd9


:
Posted by einai

※ LEVEL 09


Q. There’s a C setuid wrapper for some vulnerable PHP code…


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
<?php
 
function spam($email)
{
  $email = preg_replace("/\./"" dot "$email);
  $email = preg_replace("/@/"" AT "$email);
  
  return $email;
}
 
function markup($filename$use_me)
{
  $contents = file_get_contents($filename);
 
  $contents = preg_replace("/(\[email (.*)\])/e""spam(\"\\2\")"$contents);
  $contents = preg_replace("/\[/""<"$contents);
  $contents = preg_replace("/\]/"">"$contents);
 
  return $contents;
}
 
$output = markup($argv[1], $argv[2]);
 
print $output;
 
?>
cs



A. 여기는 /e modifier의 기능으로 발생되는 취약점이다. 


e(PCRE_REPLACE_EVAL) modifier 

이 변경자를 지정하면 preg_replace()는 변경할 문자열을 PHP 코드로 처리하고, 그 결과를 검색된 문자열을 이용하여 일반적인 치환을 한다. 작은 따옴표, 큰 따옴표, 백슬래시와 NULL 문자는 백슬래시로 이스케이프된다. 

preg_replace()만 이 변경자를 사용하고 다른 PCRE 함수는 무시한다. 

출처 : http://php.net/manual/kr/reference.pcre.pattern.modifiers.php


지금은 preg_replace 함수에서 지원되지 않는 변경자란다. 대신 preg_replace_callback 함수를 사용하라는디~

(e modifier : This feature was DEPRECATED in PHP 5.5.0, and REMOVED as of PHP 7.0.0) 



그럼 문제풀이로 들어가보도록 하겠다. 

 

[그림 1] e modifier 테스트 


인터넷에 찾아보니까 크게 두가지로 명령어를 실행시키는 것 같았다. 그래서 일단 테스트 목적상 php 파일을 만들어놓고 동작시켜보았다.


[그림 2] 테스트 결과


결과는 두 가지 방법 모두 실행되었다.


[그림 3] 문제풀이 실행


하지만 실제 문제에 대입하였을 때에는 ` ` 로 감싸는 방식의 명령어 실행은 동작하지 않았다. 뭐가 문제인지 모르겠다.


[그림 4] 플래그 획득 


사실 치환 대상이 되는 문자열이라서 PHP 엔진이 다르게 인식하는 지 몰라도 실제 PHP 파일에서는 {${ }} 와 같은 대괄호를 씌우지 않고 단순히 system(sh) 라고만 하여도 실행이 되더라. 물론 파싱하는 과정에서 e modifier가 인식할 수 있도록 도와주는 지시자일 것이라 어림짐작은 하고 있다만..

(정확한 원인에 대해 아시는 분이 있다면 알려주시길 바랍니당 -ㅅ-)




※ LEVEL 10 


Q. The setuid binary at /home/flag10/flag10 binary will upload any file given, as long as it meets the requirements of the access() system call.


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
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
 
int main(int argc, char **argv)
{
  char *file;
  char *host;
 
  if(argc < 3) {
      printf("%s file host\n\tsends file to host if you have access to it\n", argv[0]);
      exit(1);
  }
 
  file = argv[1];
  host = argv[2];
 
  if(access(argv[1], R_OK) == 0) {
      int fd;
      int ffd;
      int rc;
      struct sockaddr_in sin;
      char buffer[4096];
 
      printf("Connecting to %s:18211 .. ", host); fflush(stdout);
 
      fd = socket(AF_INET, SOCK_STREAM, 0);
 
      memset(&sin, 0sizeof(struct sockaddr_in));
      sin.sin_family = AF_INET;
      sin.sin_addr.s_addr = inet_addr(host);
      sin.sin_port = htons(18211);
 
      if(connect(fd, (void *)&sin, sizeof(struct sockaddr_in)) == -1) {
          printf("Unable to connect to host %s\n", host);
          exit(EXIT_FAILURE);
      }
 
#define HITHERE ".oO Oo.\n"
      if(write(fd, HITHERE, strlen(HITHERE)) == -1) {
          printf("Unable to write banner to host %s\n", host);
          exit(EXIT_FAILURE);
      }
#undef HITHERE
 
      printf("Connected!\nSending file .. "); fflush(stdout);
 
      ffd = open(file, O_RDONLY);
      if(ffd == -1) {
          printf("Damn. Unable to open file\n");
          exit(EXIT_FAILURE);
      }
 
      rc = read(ffd, buffer, sizeof(buffer));
      if(rc == -1) {
          printf("Unable to read from file: %s\n", strerror(errno));
          exit(EXIT_FAILURE);
      }
 
      write(fd, buffer, rc);
 
      printf("wrote file!\n");
 
  } else {
      printf("You don't have access to %s\n", file);
  }
}
cs


A. 본 문제는 레이스 컨디션에 관련된 문제이다. 

즉 access 함수로 접근 가능 여부를 판단하고 open 함수로 파일을 여는 시간 차이를 활용한다. 


 [그림 5] token 파일 접근 권한


본 워게임은 token 파일의 내용을 얻어야 하는데 보시다시피 읽기 권한이 없는 것을 알 수 있다. 따라서 조금 전에 말한 바와 같이 우리는 두 함수가 실행되는 그 차이를 이용해서 token 파일을 읽어올 것이다. 


간단히 순서는 !

1. fake_token 파일 생성

2. fake_token 파일과 token 파일을 링크할 링크 파일 생성

3. 포트 오픈 

4. flag10 실행 파일의 인자로 2번에서 생성한 링크 파일을 제공 


위 순서를 계속 반복할 것이다. 


[그림 6] 파일 생성 및 링크 


[그림 7] flag10 실행


[그림 8] token 획득


[그림 9] 플래그 획득 


이상~!

:
Posted by einai

※ LEVEL 05 


Q. Check the flag05 home directory. You are looking for weak directory permissions.


A. 해당 문제는 백업 디렉터리가 존재하고 ssh 접근 시 사용되는 정보들이 해당 디렉터리에 저장되어 있는 것을 알 수 있다. 여기서 문제는 이 정보들을 이용할 경우 패스워드를 직접 입력하지 않아도 로그인이 가능하다는 것이다. 


[그림 1] 백업 파일 확인


[그림 2] 해당 파일을 복사한 뒤 압축 해제


[그림 3] ssh 프로토콜을 활용하여 접속 시도


[그림 4] 로그인 성공 및 플래그 획득 


추가적으로 설명하자면 ssh 프로토콜은 원격 접속 대상(서버)의 특정 경로(기본 경로 : ~/.ssh 디렉토리 이하)에 위치한 authorized_keys 파일에 클라이언트의 공개키가 저장되어 있을 때 클라이언트가 서버로 ssh 접속 요청을 할 때 해당 키에 대응되는 개인키가 존재할 경우 로그인을 허용하게 된다. 


즉, 


Server A's Home Directory/.ssh/authroized_keys 

Client's Home Directory/.ssh/id_rsa 


두 개가 서로 대응되는 비대칭키 쌍이어야 한다는 의미이다.  

위와 같은 관계가 형성될 경우 패스워드 인증을 대신하여 비대칭키 인증으로  ssh 로그인이 가능하다. 





※ LEVEL 06


Q. The flag06 account credentials came from a legacy unix system.


A. 부적절한 암호(DES)를 적용한 패스워드를  passwd 파일에 저장한 상태이다. 크랙 툴(john the ripper)을 사용하면 저장된 암호문을 평문으로 크랙하여 확인할 수 있다. 


[그림 5] 패스워드 파일 확인


[그림 6] 추출된 패스워드 파일을 이용하여 크랙


[그림 7] 크랙된 패스워드 확인 


해당 패스워드를 가지고 flag06 계정으로 로그인을 하면 된다. 





※ LEVEL 07


Q. The flag07 user was writing their very first perl program that allowed them to ping hosts to see if they were reachable from the web server.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/perl
 
use CGI qw{param};
 
print "Content-type: text/html\n\n";
 
sub ping {
  $host = $_[0];
 
  print("<html><head><title>Ping results</title></head><body><pre>");
 
  @output = `ping -3 $host 2>&1`;
  foreach $line (@output) { print "$line"; }
 
  print("</pre></body></html>");
  
}
 
# check if Host set. if not, display normal page, etc
 
ping(param("Host"));
cs



A. 이 CGI 프로그램은 thttp 데몬을 이용하여 서비스하고 있으며 권한은 flag07로 동작하고 있다. 따라서 우리는 명령어 인젝션을 통해 플래그를 획득할 수 있겠다. 처음에 나는 해당 서비스가 동작을 안하고 있길래 잠시 혼란을 겪었으나 리부팅을 하니 서비스가 올라와 정상적으로 문제를 해결할 수 있었다. 


[그림 8] 설정파일 확인 (서비스 구동 권한)


[그림 9] 서비스 확인


[그림 10] 해당 CGI 요청 후 플래그 획득 




※ LEVEL 08


Q. World readable files strike again. Check what that user was up to, and use it to log into flag08 account.


A. 이는 인증 정보가 담긴 네트워크 패킷 파일이 모든 사용자에게 읽기 권한이 부여되어 있어 문제가 되는 문제(?)이다. 


[그림 11] 패킷 덤프 파일 확인


[그림 12] 와이어샤크로 분석 


저기에 "." 으로 표기되는 문자는 실제 DEL 키를 나타내는 헥사값 "0x7F"이므로 순서를 다시 조합하여 flag08 패스워드로 이용하면 된다. 


:
Posted by einai
본 문제에 대한 문제풀이를 하기 전에 간단히 워게임 특성을 말하자면~ 
이 워게임은 권한있는 사용자로 "/bin/getflag" 명령어를 실행하면 된다. 
(권한있는 사용자 ? flag00, flag01, .... flag19)

간단하지만 왜 설명하냐면, 나는 처음에 이해를 못했으니까 -ㅅ-
난 처음엔 다른 워겜처럼 다음 계정의 패스워드나 이런게 나올 줄 알았당.

그럼 롸이럽 시작해보도록 하겠다. 
간단한건 여러개 묶어서 간단하게 하고 넘어가겠다. 



※ LEVEL 00 

Q. This level requires you to find a Set User ID program that will run as the “flag00” account. You could also find this by carefully looking in top level directories in / for suspicious looking directories.

A. SetUID로 검색하던가 그냥 사용자로 검색하던가 편한대로 검색하면 된다. 

[그림 1] Level 00 미션 성공




※ Level 01 

Q. There is a vulnerability in the below program that allows arbitrary programs to be executed, can you find it?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
 
int main(int argc, char **argv, char **envp)
{
  gid_t gid;
  uid_t uid;
  gid = getegid();
  uid = geteuid();
 
  setresgid(gid, gid, gid);
  setresuid(uid, uid, uid);
 
  system("/usr/bin/env echo and now what?");
}
cs



A. 심볼릭 링크를 걸고 PATH 환경 변수를 조작해주면 된다. 


[그림 2] flag01 파일 실행 시 


[그림 3] 심볼릭 링크 설정 


[그림 4] PATH 환경 변수 설정 후 문제 실행 





※ LEVEL 02


Q. There is a vulnerability in the below program that allows arbitrary programs to be executed, can you find it?


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
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
 
int main(int argc, char **argv, char **envp)
{
  char *buffer;
 
  gid_t gid;
  uid_t uid;
 
  gid = getegid();
  uid = geteuid();
 
  setresgid(gid, gid, gid);
  setresuid(uid, uid, uid);
 
  buffer = NULL;
 
  asprintf(&buffer, "/bin/echo %s is cool", getenv("USER"));
  printf("about to call system(\"%s\")\n", buffer);
  
  system(buffer);
}
cs


A. 환경변수를 통한 명령어 인젝션 공격이다. Let's inject !!


[그림 5] 인젝션 및 flag 획득





※ LEVEL 03


Q. Check the home directory of flag03 and take note of the files there. There is a crontab that is called every couple of minutes.


[그림 6] crontab 에 등록되어 있는 스크립트 


A. crontab에 flag03 사용자 권한으로 해당 쉘 스크립트를 동작시키는 작업이 스케쥴되어 있다는 문제다. 나는 나보고 돌리라는 줄 알고 -ㅅ-.. 삽질했넹 


[그림 7] 해당 경로에 스크립트 작성


[그림 8] 동작되는 스크립트 내용 


[그림 9] 스케줄링 된 작업이 동작하면서 획득한 플래그 




※ LEVEL 04


Q. This level requires you to read the token file, but the code restricts the files that can be read. Find a way to bypass it :)


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
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
 
int main(int argc, char **argv, char **envp)
{
  char buf[1024];
  int fd, rc;
 
  if(argc == 1) {
      printf("%s [file to read]\n", argv[0]);
      exit(EXIT_FAILURE);
  }
 
  if(strstr(argv[1], "token"!= NULL) {
      printf("You may not access '%s'\n", argv[1]);
      exit(EXIT_FAILURE);
  }
 
  fd = open(argv[1], O_RDONLY);
  if(fd == -1) {
      err(EXIT_FAILURE, "Unable to open %s", argv[1]);
  }
 
  rc = read(fd, buf, sizeof(buf));
  
  if(rc == -1) {
      err(EXIT_FAILURE, "Unable to read fd %d", fd);
  }
 
  write(1, buf, rc);
}
cs


A. 이 문제도 심볼릭 링크로 해결할 수 있다. 단 이번 문제는 token 파일에 들어있는 값을 flag04 계정의 패스워드로 활용하면 된다. 

[그림 10] 심볼릭 링크 후 플래그 획득 / 로그인 성공




이것으로 Level 00 ~ Level 04 까지의 문제풀이를 마치겠다. 


:
Posted by einai
2017. 7. 9. 15:20

[문제풀이] pwnable.kr - ascii_easy 2017. 7. 9. 15:20

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

2017. 7. 9. 15:19

[문제풀이] pwnable.kr - otp Wargames/pwnable.kr2017. 7. 9. 15:19





※ 사전지식



SIGNAL 정의 

신호는 유닉스, 유닉스 계열, POSIX 호환 운영체제에 쓰이는 제한된 형태의 프로세스 간 통신이다. 

신호는 프로세스나 동일 프로세스 내의 특정 스레드로 전달되는 비동기식 통보이다. 

출처 : https://ko.wikipedia.org/wiki/유닉스_신호




사용자가 임의로 발생시킬 수 있는 신호도 존재한다.

  • Ctrl+C : SIGINT 발생, 기본적으로 프로세스를 종료하는 역할을 한다.
  • Ctrl+Z : SIGTSTP 발생, 기본적으로 프로세스가 실행을 유예시키는 역할을 한다.
  • Ctrl+\ : SIGQUIT 발생, 기본적으로 프로세스를 종료시킨 뒤 코어를 덤프하는 역할을 한다.
  • Ctrl+T : SIGINFO 발생, 명령에서 지원하는 경우 기본적으로 운영체제가 실행 중인 명령에 대한 정보를 표시한다.


요즘 운영체제에서 이러한 기본 키 조합들은 stty 명령으로 변경시킬 수 있다. 




SIGNAL 종류 


출처 : https://ko.wikipedia.org/wiki/유닉스_신호





ulimit 명령어


해당 명령어는 프로세스의 자원 한도를 설정하는 명령으로 Soft 한도와 Hard 한도 두 가지가 존재한다.


Soft  : 새로운 프로그램을 생성하면 기본으로 적용되는 한도

Hard : 소프트 한도에서 최대로 늘릴 수 있는 한도 


  • ulimit [옵션] 값

-a : 모든 제한 사항을 보여줌.

-c : 최대 코어 파일 사이즈

-d : 프로세스 데이터 세그먼트의 최대 크기

-f : shell에 의해 만들어질 수 있는 파일의 최대 크기

-s : 최대 스택 크기

-p : 파이프 크기

-n : 오픈 파일의 최대수

-u : 오픈파일의 최대수

-v : 최대 가상메모리의 양

-S : soft 한도

-H : hard 한도


 

  • 항목 설명 

core file size          (blocks, -c) 0                        

       // 코어파일의 최대크기

data seg size           (kbytes, -d) unlimited           

       // 프로세스의 데이터 세그먼트 최대크기

scheduling priority             (-e) 0                      

file size               (blocks, -f) unlimited           

      // 쉘에서 생성되는 파일의 최대 크기

pending signals                 (-i) 14943

max locked memory       (kbytes, -l) 64

max memory size         (kbytes, -m) unlimited       

      // resident set size의 최대 크기(메모리 최대크기)

open files                      (-n) 1024                    

      // 한 프로세스에서 열 수 있는 open file descriptor의 최대 숫자(열수 있는 최대 파일 수)

pipe size            (512 bytes, -p) 8                          

      // 512-바이트 블럭의 파이프 크기

POSIX message queues     (bytes, -q) 819200

real-time priority              (-r) 0

stack size              (kbytes, -s) 10240

cpu time               (seconds, -t) unlimited             

      // 총 누적된 CPU 시간(초)

max user processes              (-u) 1024                  

      // 단일 유저가 사용가능한 프로세스의 최대 갯수

virtual memory          (kbytes, -v) unlimited     

      // 쉘에서 사용가능 한 가상 메모리의 최대 용량

file locks                      (-x) unlimited



사전지식 끝! 




※ 문제풀이


먼저 소감을 잠시 말하자면 기발했다. 

내가 예상한 접근 방법이 다 틀렸고 다시 한번 사고의 확장을 할 수 있었다. 


문제를 낸 사람이나 푼 사람이나 기발하다.


Signal 과 ulimit 명령어의 조합으로 이런 것도 가능하다니...

아직 경험 못해본 것이 너무 많다. 



그럼 문제풀이로 들어가도록 하겠다. 


[그림 1] 문제 구동시


사이트 문제에서도 알 수 있드시 문제에서 생성한 랜덤 숫자를 맞추는 문제이다.

코드는 아래와 같다. 


#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], 8, 1, fp);

        fclose(fp);


        printf("OTP generated.\n");


        unsigned long long passcode=0;

        FILE* fp2 = fopen(fname, "r");

        if(fp2==NULL){ exit(-1); }

        fread(&passcode, 8, 1, fp2);

        fclose(fp2);


        if(strtoul(argv[1], 0, 16) == passcode){

                printf("Congratz!\n");

                system("/bin/cat flag");

        }

        else{

                printf("OTP mismatch\n");

        }


        unlink(fname);

        return 0;

}


위 코드에서 굵은 글자 형태로 표현한 것이 핵심인 듯하다. 


쉘에서 파일을 생성하는 과정이다. 이제 저 부분을 가지고 장난을 친다. 


쉘에서 생성할 수 있는 파일의 크기를 0으로 제한한다.

이럴 경우 SIGXFSZ 신호가 발생하는데 이를 무시하고 프로세스를 동작할 경우 비교 값은 NULL이 된다. 

따라서 내가 NULL을 입력할 경우 조건이 만족하면서 플래그 값을 알 수 있다. 


어메이징!! 쇼킹!! 



[그림 2] 정답



문제풀이에 사용된 소스는 아래와 같다 .

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>



int main(int argc, char*argv[]) {


  sigset_t mask;

  sigemptyset(&mask);

  sigaddset(&mask, SIGXFSZ);


  sigprocmask(SIG_BLOCK, &mask, NULL);


  char *argv[] = { "otp", "\x00", NULL};

  char *env[] = {NULL};


  execve(argv[1], arg, env);

 

  return 0;

}



문제풀이 끝! 



색다른 방법을 배울수 있는 좋은 문제였따!!





:
Posted by einai