문제 화면

알맞은 키를 입력하면 풀리는 문제인 것 같다.

 

 

전체 소스코드

 

(enter[i] ^ (i % 2)) == serial[i]

 

이게 주요 코드인 듯 싶다.

 

우리가 입력한 enter 값과 (i%2)를 XOR 연산하여 serial 값과 비교하고 있다.

A^B=C 라면 A=B^C 라는 속성을 가지기 때문에

enter[i] = (i%2) ^ serial[i] 이 된다.

 

 

 

소스코드와 결과

이렇게 소스코드를 짜면 아래와 같은 flag 값이 나온다.

'Study > Reversing' 카테고리의 다른 글

Reversing.kr _Easy Unpack 풀이  (0) 2021.05.18
CodeEngn Challenge : Basic RCE L14  (0) 2021.05.11
CodeEngn Challenge : Basic RCE L11  (0) 2021.05.06
CodeEngn Challenge : Basic RCE L10  (0) 2021.05.06
CodeEngn Challenge : Advance RCE L09  (0) 2021.04.28

[그림 1] 문제화면

1 2 3 각각 링크가 걸려있고, Password를 입력하는 칸이 있다.

 

 

[그림 2] 1 클릭

1을 클릭하면 no=1이 전달된 URL 주소로 바뀌며

Apple 문자열, Password 입력 칸이 나온다.

 

위와 같은 방식으로, 2를 클릭 시 no=2이 전달된 URL 주소로 바뀌며

Banana 문자열, Password 입력 칸이 나온다.

 

 

 

[그림 3] 3 클릭

3을 클릭하면

Secret 

column : id, no

no 3's id is password

이라는 문자열이 나온다. 

따라서, no=3일때의 id 값을 알아내야 한다는 소리인데,,

 

 

 

[그림 4] no=1일 때 페이지 소스코드 보기

일단 어느 페이지에서든 Password 칸에 값을 입력하면 원래 문제화면으로 돌아가며

webhacking.kr/challenge/web-09/index.php?pw=입력한 값

과 같이 입력한 값을 GET 방식으로 전달해주고 있다는 것을 알 수 있었다.

 

 

[그림 5] no = 0,1,2,3 이외의 값을 입력

전달해주는 no 값에 0,1,2,3 이외의 값을 입력하면 

저렇게 Password 입력창만 뜬다.

 

 

 

간단하게 SQL Injection을 시도해본 결과 

' , ", 공백, and, &, |, or, % 등 문자열이 필터링되면서

Access Denied가 뜬다.

 

이럴 때는 Blind SQL Injection을 사용해야 한다고 한다.

Blind SQL Injection이란, 쿼리 결과로 나오는 참, 거짓을 이용하여 DB 정보를 취득하고자 하는 기법이다.

 

※ 참고 ※

if (조건문,3,1)

맨 앞 조건문이 참이면 3을 반환, 거짓이면 1을 반환

 

블라인드 SQL 인젝션을 통해서 id의 길이를 먼저 알아내고자 했다.

no=if(length(id)like({id 문자열의 길이}),3,0)

 

→ id의 length와 입력한 값이 같다면 

no=3이 되어 화면에 Secret이 출력되고, 

만약 길이가 같지 않다면 no=0이 되어 초기화면이 보일 것이다.

 

pw 길이를 알기 위한 코드 작성
결과

no=3일 때의 id, 즉 pw의 길이가 11이라는 것을 알았다.

 

 

그리고 블라인드 SQL 인젝션에서 가장 많이 사용하는 함수인 substr 함수를 사용하여 11글자를 알아볼 것이다.

 

※ 참고 ※

substr (문자열, 시작위치, 개수)

문자열의 시작위치(1부터) 에서부터 개수만큼의 글자를 잘라 가져온다

 

no=if(substr(id,{문자인덱스},1)like({맞는지 확인할 문자의 헥스값}),3,0)

 

→ id 문자열의 문자인덱스(1부터) 한 자리 값이 확인할 문자의 헥스값과 같다면

no=3이 되어 화면에 Secret이 출력되고, 

만약 그렇지 않다면 no=0이 되어 초기화면이 보일 것이다.

 

아까 작성한 코드에 이를 추가
그 결과

pw를 알아냈다!!

 

 

그러나 맞지 않다고 뜬다.

이유를 알아보니,  MYSQL은 대소문자를 구분하지 않는다고 한다.

그래서 대문자 A의 아스키코드 0x41과 소문자 a의 아스키코드 0x61을 같은 문자로 인식하는 것이다.

 

따라서 이를 소문자로 적어줘봤다.

 

 

 

아직 작성 중인 글 입니다...

[그림 1] 문제화면
[그림 2] 소스코드

trim()

:: 문자열 앞뒤로 공백을 없애주는 함수

 

getenv()
:: 함수의 인자로 오는 변수에 따라 해당되는 환경 변수값을 알려주는 함수.

- REMOTE_ADDR : 웹 사이트를 접속한 컴퓨터의 ip address
- HTTP_USER_AGENT : 웹 사이트를 접속한 컴퓨터의 웹 브라우저 정보

 

addslashes()

:: DB 작업을 위해 쿼리를 작성할 때, 따옴표를 escape 시켜주는 함수.

ex) addslashes(You're my sunshine) >>> You\'re my sunshine

 

htmlentities()

:: html 태그를 그대로 출력하는 함수.


PHP 부분부터 살펴보면,

$agent 변수에 HTTP_USER_AGENT(접속 웹 브라우저 정보)을 저장하고,

$ip에는 REMOTE_ADDR(접속 ip주소)을 넣어준다.

만약 $agent 변수에 from 또는 FROM이 들어가면 Access Denied! 를 한다.

 

$count_ck에는 'select count(id) from chall8'의 결과가 배열의 형태로 저장되어 있다.

$count_ck에는 70개 이상의 값이 들어가면 저절로 삭제되게 되어있다.

 

그리고 $agent 변수가 현재 접속한 유저의 HTTP_USER_AGENT와 같은지 비교하고,

그중에서도 $agent의 id가 admin이면 문제가 풀리는 구조이다.

 

만약 $agent 변수가 존재하지 않는다면 agent와 ip, id를 insert 해준다.

 

 

우리가 현재 건드릴 수 있는 값은 $agent이고

$agent 변수에 'admin' 문자열을 넣는 것을 목표로 해야한다.

데이터를 넣는 것은 Insert 쿼리가 있는 맨 마지막 조건문을 이용해야 한다.

 

(!$ck){
  $q=mysqli_query($db,"insert into chall8(agent,ip,id) values('{$agent}','{$ip}','guest')") or die("query error");
  echo("<br><br>done!  ({$count_ck[0]}/70)");

}

 

$agent 에 test','1.1.1.1','admin')('test2 를 넣게 되면

쿼리문은 insert into chall8(agent, ip, id) values('test','1.1.1.1','admin')('test2',$ip값,'guest') 가 되는 것이다.

 

 

$agent를 변조시키기 위해서는 Burp Suite를 이용해야 한다고 한다.

(Burp Suite 도구를 사용해서 HTTPS 요청 패킷을 캡처할 수 있기 때문)

 

Burp Suite 사용법을 익힌 후 다시 작성하겠다,,^^

 

 

[그림 1] 문제
[그림 2] auth를 누를 시 나타나는 창

auth 클릭 시 Access_Denied 문자열이 뜬다.

 

[그림 3] view-source 를 통해 코드 확인

php 코드를 분석해보자.

 

GET 요청과 함께 넘어온 val 인자값을 변수 $go에 저장한다.

$go에 들어온 값이 없다면 처음 페이지로 무조건 가게 되어있다.

그리고 preg_match 함수가 사용되는데, preg_match의 설명은 아래와 같다.

 

<preg_match> 패턴일치
첫 번째 인수 : 정규 표현식 작성
두 번째 인수 : 검색 대상 문자열
세 번째 인수 : 배열 변수 반환
→ 패턴 매치에서 매칭된 값을 배열로 저장
→ 반환값 : 매칭에 성공하면 1, 실패하면 0을 반환

/ 2 | - | \+ | from | _ | = | \\ s | \* | \/ /i

더보기

| (or)를 기준으로 살펴보면

숫자 2

기호 -

\+ ; 1개 이상의 \

from 문자열

기호 _

기호 =

\\s; 공백

\* ; 0개 이상의 \

기호 /

/i ; 대소문자 구별 x

 

여기에 해당되면 다 매칭이 되기 때문에 이를 사용하면 안된다.

 

$go 변수가 이 정규 표현식에 매칭되면 "Access Denied"가 뜨므로 매칭되지 않도록 해야한다.


이 단계를 넘기면 db로 연결되고 ( $db dbconnect(); )

$rand 변수에 1~5 사이의 수가 임의로 결정된다.( $rand=rand(1,5); )

 

$rand 변수의 값에 따라 밑의 조건문 중 하나가 무조건 실행이 된다. 

예를 들어 $rand == 1 이라면 실행되는 문장은 아래와 같다.

 

$result=mysqli_query($db,"select lv from chall7 where lv=($go)") or die("nice try!");

+) mysqli_query(연결객체, 쿼리) 함수는 mysqli_connect 를 통해 연결된 객체를 이용하여 MySQL 쿼리를 실행시키는 함수이다. 

1. chall7 테이블에서 lv가 $go변수값인 레코드의 lv를 보여주는 쿼리를 실행하거나

2. "nice try!"라는 문자열을 띄우면서 종료한다.

 

따라서, 해당 레코드가 없다면 die 되므로

쿼리의 결과가 있도록 $go 변수값을 잘 지정해줘야 한다.


+) mysqli_fetch_array() 함수는 쿼리문을 통해 가져온 결과의 레코드를 배열 형태로 받아오는 함수이다.

 

위 코드를 성공적으로 통과해왔다면

레코드를 배열 형태로 받아서 $data에 저장하게 된다.

 

$data[0] == 1 이면 'Access_Denied' 메시지를 띄우고

$data[0] == 2 이면 'Hello admin' 메시지를 띄운다.


결론적으로, data[0] == 2가 되어야 하고 그러러면 $result가 2라는 값을 가지도록 하면 된다.

 

chall7 테이블의 lv 컬럼에 어떤 형태의 값이 있는지 모르기 때문에

select lv from chall7 where lv=($go) 의 쿼리문을 통해 2라는 값을 가지도록 하려면

chall7 테이블의 lv 컬럼 값을 찾거나, lv 컬럼에 값을 넣어야 한다.

 

select lv from chall7 where lv=(0) union select 2#)

라고 먼저 넣어준다.

 

이렇게 되면 SELECT lv FROM chall7 WHERE lv =(0)

UNION

SELECT (2)

와 같은 쿼리문이 전달되는 것이다.

 

하지만 정규표현식에 숫자 2가 걸리므로

 

0)union(select(char(50)))%23

 

 

 

 

 

 

 

 

[그림 1] 문제화면

 

[그림 2] view source 확인

소스코드를 확인한 결과, encode코드와 decode코드로 나눠져있었다.

base64방식으로 20번 인코딩되고, str_replace로 치환된 후, $var_id, $val_pw에 저장된 값을 쿠키로 지정해주고 있다.

 

 

[그림 3] 쿠키 값 확인

쿠키값을 확인해보니 엄청난 길이의 값이 저장되어 있었다.

 

그리고 decode 하는 부분을 살펴보았다.

[그림 4] Decoding 코드

저장된 'user' 쿠키 값을 $decode_id 에, 저장된 'password' 쿠키 값을 $decode_pw 에 넣고

각각의 변수에 !문자열이 있다면 1로, @가 있다면 2로 ,, 바꿔준다.

그 후 그렇게 바꿔준 값을 base64 방식으로 20번 decoding 해준 후, 웹 페이지에 보여준다.

 

 

그리고 아주 핵심적인 코드!

if($decode_id=="admin" && $decode_pw=="nimda"){  
solve(6);
}

 

admin과 nimda를 base64 방식 암호화 함수로 20번 인코딩해서 쿠키값에 넣어주면 풀릴 것 같다.

 

[그림 5] 인코딩 파이썬 코드
[그림 6] 그 결과

따라서 나온 값을 각각 쿠키값에 넣어줬다.

[그림 7] 쿠키 값 수정

[그림 1] 문제

OEP를 찾고 Stolenbyte까지 찾는 문제이다.

OEP를 찾는 문제이므로 패킹되어있을 가능성이 아주 높다.

 

[그림 2] PEiD로 확인

PEiD로 확인해본 결과, UPX로 패킹이 되어있다. UPX로 패킹되어 있는 실행파일은 간단하게 upx 툴을 이용하여 언패킹이 가능하다.

 

[그림 3] upx -d 옵션으로 언패킹
[그림 4] 언패킹 된 11.exe를 OllyDBG로 실행

언패킹된 실행 파일을 다시 OllyDBG에 올렸더니 

MessageBox 함수에 들어갈 인자가 모자름을 단번에 알 수 있었다.

 

[그림 5] MessageBox

결과는 예상대로 깨진 문자열이 나온다.

이는 unpacking시 문제가 됐을 가능성이 있으므로 unpacking 되지 않은 파일의 POPAD 명령어 부분을 확인해보았다.

 

[그림 6] 패킹되어있는 11.exe의 POPAD 부분

POPAD 이후 스택에 적재되는 세 개의 값을 확인할 수 있었다.

 

OEP로 점프하는 구간에 BP를 걸고 실행시켰더니

MessageBoxA 함수 호출에 필요한 인자들이 스택에 PUSH 됨을 알 수 있고,

이를 통해 PUSH 되는 인자들이 StolenByte 임을 확인했다.

 

 

[그림 7] 코드 수정

Unpacking 했던 실행 파일에서 Ctrl+E를 통해 코드 수정을 하였다.

 

 

[그림 8] 정상적 실행 확인

부족한 인자를 완성 시키고 실행시켰더니 그림8과 같이 정상적으로 실행됨을 확인할 수 있다

'Study > Reversing' 카테고리의 다른 글

CodeEngn Challenge : Basic RCE L14  (0) 2021.05.11
[HackCTF] Reversing Me 풀이  (0) 2021.05.11
CodeEngn Challenge : Basic RCE L10  (0) 2021.05.06
CodeEngn Challenge : Advance RCE L09  (0) 2021.04.28
CodeEngn Challenge : Advance RCE L03  (0) 2021.04.28

[그림 1] 문제
[그림 2] 10.exe

10.exe는 Name에 알맞는 Serial을 적는 실행파일이다.

[그림 3] PEiD로 분석

OEP 관련 문제들은 패킹과 연관이 되어있을 가능성이 높기 때문에 PEiD에 10.exe를 올려보니 Aspack으로 패킹되어있는 것을 알 수 있다.  

Aspack은 UPX와 비슷하게 처음에 PUSHAD를 통해 레지스터의 값을 넣고 디코딩 한 후 POPAD를 한다고 한다.

 

UPX로 패킹되어 있는 실행파일의 OEP 구하는 방법은 아래 글 참고!

https://these-dayss.tistory.com/53?category=957855

 

 

[그림 4] 처음 실행코드

처음에 예상대로 PUSHAD 명령어가 있다. 

 

[그림 5] Hardware BP

PUSH 명령어 실행 후 스택에 Break Point를 건다.

 

 

[그림 6] [F9] 실행

BreakPoint를 건 곳까지 실행하면 [그림 6]과 같이 POPAD 후, JNZ 구문에 의해 분기가 일어난 후 특정주소(00445834)를 PUSH 하는것을 알 수 있다. 이 주소가 바로 OEP 주소라고 할 수 있다.

 

 

[그림 7] 00445834로 이동

OEP 주소로 이동해보니 디버거가 OPCODE를 제대로 인식하지 못하고 있다. 이런 경우, 덤프를 해줘야한다고 한다.

 

 

[그림 8] OllyDump

OllyDump 툴을 사용하여 OEP를 수정한 후 덤프를 떠준다.

 

(Immunity Debugger는 따로 덤프 툴이 없어서 OllyDBG로 변경)

 

[그림 9] 덤프 뜬 실행 파일
[그림 10] text string 으로 찾은 well done 문자열
[그림 11] well done으로 가는 분기문

해당 주소를 찾아가보면 OPCODE는 75 55 임을 확인할 수 있다.

'Study > Reversing' 카테고리의 다른 글

[HackCTF] Reversing Me 풀이  (0) 2021.05.11
CodeEngn Challenge : Basic RCE L11  (0) 2021.05.06
CodeEngn Challenge : Advance RCE L09  (0) 2021.04.28
CodeEngn Challenge : Advance RCE L03  (0) 2021.04.28
CodeEngn Challenge : Basic RCE L09  (0) 2021.04.07

문제화면

까만 배경에 Login 버튼과 Join 버튼이 보인다.

 

Join 눌렀을 때

Join 버튼을 누르면 Access_Denied 라는 문자열을 담은 알림창이 뜬다.

 

 

login.php

Login 버튼을 눌렀을 때 나타나는 첫 화면이다.

아이디와 비밀번호를 입력하는 화면이 뜬다.

 

 

주소창 확인

주소창을 보니 항상 뜨던 webhacking.kr/challenge/web-05/ 주소 뒤에 mem이라는 디렉토리가 있는 것을 발견했다.

 

 

webhacking.kr/challenge/web-05/mem 페이지

들어갈 수 없었던 Join 페이지를 확인하기 위해 join.php를 눌렀다.

 

 

join.php 누른 결과

그 결과 bye라는 문자열을 띄워줬고

 

join.php

join.php에 성공적으로 들어오게 되었다.

하지만 검은 배경에 아무런 글자가 적혀있지 않았다.

 

페이지 소스 코드 확인

<html>
<title>Challenge 5</title></head><body bgcolor=black><center>
<script>
l='a';ll='b';lll='c';llll='d';lllll='e';llllll='f';lllllll='g';llllllll='h';lllllllll='i';llllllllll='j';lllllllllll='k';llllllllllll='l';lllllllllllll='m';llllllllllllll='n';lllllllllllllll='o';llllllllllllllll='p';lllllllllllllllll='q';llllllllllllllllll='r';lllllllllllllllllll='s';llllllllllllllllllll='t';lllllllllllllllllllll='u';llllllllllllllllllllll='v';lllllllllllllllllllllll='w';llllllllllllllllllllllll='x';lllllllllllllllllllllllll='y';llllllllllllllllllllllllll='z';I='1';II='2';III='3';IIII='4';IIIII='5';IIIIII='6';IIIIIII='7';IIIIIIII='8';IIIIIIIII='9';IIIIIIIIII='0';li='.';ii='<';iii='>';lIllIllIllIllIllIllIllIllIllIl=lllllllllllllll+llllllllllll+llll+llllllllllllllllllllllllll+lllllllllllllll+lllllllllllll+ll+lllllllll+lllll;
lIIIIIIIIIIIIIIIIIIl=llll+lllllllllllllll+lll+lllllllllllllllllllll+lllllllllllll+lllll+llllllllllllll+llllllllllllllllllll+li+lll+lllllllllllllll+lllllllllllllll+lllllllllll+lllllllll+lllll;if(eval(lIIIIIIIIIIIIIIIIIIl).indexOf(lIllIllIllIllIllIllIllIllIllIl)==-1) {alert('bye');throw "stop";}if(eval(llll+lllllllllllllll+lll+lllllllllllllllllllll+lllllllllllll+lllll+llllllllllllll+llllllllllllllllllll+li+'U'+'R'+'L').indexOf(lllllllllllll+lllllllllllllll+llll+lllll+'='+I)==-1){alert('access_denied');throw "stop";}else{document.write('<font size=2 color=white>Join</font><p>');document.write('.<p>.<p>.<p>.<p>.<p>');document.write('<form method=post action='+llllllllll+lllllllllllllll+lllllllll+llllllllllllll+li+llllllllllllllll+llllllll+llllllllllllllll
+'>');document.write('<table border=1><tr><td><font color=gray>id</font></td><td><input type=text name='+lllllllll+llll+' maxlength=20></td></tr>');document.write('<tr><td><font color=gray>pass</font></td><td><input type=text name='+llllllllllllllll+lllllllllllllllllllllll+'></td></tr>');document.write('<tr align=center><td colspan=2><input type=submit></td></tr></form></table>');}
</script>
</body>
</html>

페이지 소스보기를 통해 소스코드를 확인한 결과,

알파벳과 숫자를 나타내는 표현 방식이 있다는 것을 알 수 있었고

 

콘솔 창 이용

콘솔 창을 통해 해석해본 결과,

대략 document.cookie에 oldzombie라는 문자열이 없거나, document.URL에 mode=1라는 문자열이 없으면 bye 된다는 뜻이다.

 

쿠키 추가

oldzombie 라는 내용을 가진 쿠키(new)를 생성해 준 후, url에 ?mode=1을 추가하여 해당 페이지로 접속해본다.

 

제대로 뜬 join.php

안 보이던 join.php 화면이 보이기 시작했다.

 

join 완료

아이디 패스워드 모두 test로 가입한 후 

 

login.php

로그인을 해보니 .. admin으로 로그인을 해야한다고 뜬다.

admin으로 join한 결과

아이디를 admin으로 한 결과, 중복되는 결과라고 뜬다.

 

으잉?

따라서 앞에 공백을 5개 준 후 join 해보니 성공적으로 가입이 되었다.

 

그리고 나서 다시 login

문제

알 수 없는 초록색의 문자열과 함께 Password를 입력하는 창이 떠있다.

 

Password에 아무 문자를 넣고 [제출]을 해보니

계속해서 초록색 문자열은 바뀌었다.

 

 

source 코드

혼자 계속 저 문자열을 유추해보다가 view source 가 있는 것을 깨닫고,,^^

view source로 들어가 소스코드를 확인했다.

 

우리가 입력한 값은 key라는 이름으로 POST 방식으로 전달이 되며

$hash 변수에는 10000000,99999999  사이 중 아무 숫자가 지정되고 그 숫자에 salt_for_you라는 문자열을 붙인다.

sha1 방식으로 $hash를 500번 암호화하여 $_SESSION['chall4'] 

페이지에 보여준다.

 

 

이 문제는 레인보우 테이블을 이용하는 문제로, 시중에 나와있는 레인보우 테이블 만들기 코드를 참고하였다.

(레인보우 테이블(rainbow table)은 해시함수(MD-5, SHA-1, SHA-2 등)를 사용하여 만들어낼 수 있는 값들을 대량으로 저장한 표이다. 보통 해시함수를 이용하여 저장된 비밀번호로부터 원래의 비밀번호를 추출해 내는데 사용된다.)

 

레인보우 테이블 코드

hello에 해당 문자열을 넣고

이런 식으로 모든 경우를 고려하여 답을 찾아낸다.

 

답을 찾아내는데 정말 오래걸렸고, 그래서 일부러 10000000-10050000 사이에서 답이 나오도록 문자열을 계속 바꿔줌으로서 문제를 해결할 수 있었다.

 

문제
클릭 시 검은색으로 변한다

 

 

스도쿠인 것 같아서 풀어봤더니

 

다음 화면

clear! enter your name for log : 라는 문자열이 뜨면서 진짜 문제 해결을 위한 창이 떴다.

알맞은 이름을 적어야 하는 것 같다.

 

 

아무 문자열을 계속 적어보았다.

내가 적은 문자열은 name의 값으로 들어가고,

answer는 101010000011100101011111로 고정되어 있는 것 같고

ip에는 내가 사용하는 공인 IP주소가 들어가는 것을 확인할 수 있었다.

 

URL 주소

URL을 확인해보니 answer값 10101000...과 _1=1&_2=0... 이 부분이 맞아 떨어지는 것을 확인할 수 있었고 

아마 스도쿠 칸에서 검은색으로 칠했던 부분을 나타내는 것 같았다.

 

 

URL의 answer=1로 변경 시 뜨는 화면

URL의 answer 값을 1로 변경하였더니 

no라는 화면이 뜨면서 처음 화면으로 돌아갔다.

 

즉, answer에 따라서 결과가 달라지는 것 같다는 생각이 들었다.

answer 자리에 SQL Injection을 시도해본다.

대표적인 SQL 인젝션 구문 ' or 1=1-- 과 ' or 1=1#을 사용해보았다.

(-- 는 MS SQL Server의 주석이다. MySQL의 경우 #을 사용해야 한다.)

 

hidden으로 전해지는 value 값을 항상 참으로 바꾼 후 양식 다시 제출

빈 칸에는 아무 문자열을 썼고, answer로 전해지는 value 값을 항상 참으로 뜨도록 바꾼 후에 다시 submit 했다.

 

 

그 결과

query error!가 뜬다. Injection 공격이 맞는 것 같다.

 

 

이번에는 answer로 전달되는 value 값에 ' or 1=1#를 추가해주었다.

 

그랬더니 바로 해결되었다. 

아무래도 SQL Injection이 뚫리는 곳을 찾고, 참이 되게 하는 적절한 쿼리를 만드는 연습을 하는 문제인 듯 싶다.

+ Recent posts