이번 문제는 소스코드가 주어져서 쉽게 해결할 수 있었다.
소스코드가 없었다면, 이 취약점을 찾기 쉽지 않았을 것 같다.

소스코드의 select_menu() 함수가 문제였다.


(문제 코드)



(문제코드)


문제가 되는 함수의 부분이 위의 빨간 부분이다.
select_menu() 함수는 안에서 recursive하게 자기 자신을 호출한다.

화면에 다시 메뉴를 출력하게 할 때, while 문이나 다른 코드로 처리를 하는게 보통인데
참 특이하게 자기자신을 다시 호출하므로써 메뉴를 다시 출력하였다.

재귀적 코드는 깔끔해 보이는 장점이 있지만,
재귀가 많이 반복될 수록 스택 메모리를 많이 잡아먹는 현상이 일어난다.

그래도 함수 마지막에 호출하게하여 Tail Call(Tail Recursion)을 유도했지만,
실제로 gdb로 분석해본 결과 스택메모리를 낭비하며 동작하고 있었다.

그렇다면 스택을 쭉 낮출 수 있다는 것이다.
그리고 랜덤하게 주소를 받는데 그 주소가 스택 주소가 아니란 법도 없다.
더군다나, 여기서 만든 메모리할당 함수를 보면 실행권한까지 떡하니 줘버린다.
쉘코드를 써달라고 애원하는 듯 하다.

내가 exploit을 짜기 전 계획이다.
1. 스택영역의 주소를 얻는다.(계속 반복하여서)
2. 스택을 차곡차곡 쌓아서 주소를 팍 낮춘다. 우리가 구한 스택영역의 주소보다 낮게.
3. 그 다음 메모리할당을 한번 더 받아서 그 안에 쉘코드를 넣어둔다.(이 때 받은 쉘코드의 주소를 기억)
4. 아까 받은 스택영역의 주소에다가 쉘코드주소를 쫙 넣어둔다. 4096바이트만큼 쓸수 있으므로 1024개의 주소로 덮어쓸 수 있다.
5. exit 을 실행하면 스택이 recursive하게 리턴되면서 그 중간에 덮어쓰여진 쉘코드 주소로 뛰게 되고 쉘코드 실행!

exploit을 짜면서 바뀐 계획인데

2번의 과정은 없어도 되었다. 스택영역의 주소를 얻기위해 반복하는 과정에서 자연스레 엄청 낮아졌다.



(스택 영역 주소 구하기)


반복은 받은 주소가 현재 ebp 주소보다 커진 경우까지 반복한다.

그렇게 되면 ret,leave 하면서 쭉 내려갈 테니까!



(clear 함수)


반복하다보면 255개의 배열을 다 쓴다..
그런 경우 255개를 다시 다 지워주는 함수를 만들었다.
(계속 반복 할 수 있도록)

쉘코드 올리기


(쉘코드 올리기)


쉘코드를 올리는 부분이다.



(스택 영역 주소 덮기)


그 후 쉘코드 주소를 스택영역 주소에 덮어쓴다.

그리고 

exploit!



(스택 주소 받기)


엄청난 양을 받는다.

옆에 Stacking 옆의 값은 현재 ebp 값이다.


(clear 중)


clear 도 몇번 실행되었다.



(exploit)


끝내 만족하는 스택영역 주소를 받고
ret 주소를 덮어썼다.

여기서 5를 눌러 exit를 실행하면 최종적으로 shell을 획득할 수 있다.


(쉘 획득)


문제 풀이 끝!

'WarGame > 500 Project' 카테고리의 다른 글

(66/500) pwnable.kr - rsa_calculator  (1) 2018.03.10
(65/500) pwnable.kr - echo2  (0) 2018.03.08
(64/500) backdoor - Enter the matrix  (0) 2017.10.31
(63/500) pwnable.kr - syscall  (2) 2017.10.22
(62/500) NOE.systems - BURYBURY  (0) 2017.10.01


이 문제도 하루 정도 걸려서 풀게 되었다..

취약점을 쉽게 발견했지만 RSA 관련해 이해해야할 부분이 있었고
그 부분에 있어서 시간이 좀 걸렸다.

RSA 암호기법은 정보보안기사 공부할 때도 했고, 암호 공부할 때도 했었는데
실제로 암호화 과정, 복호화 과정을 대강 이렇다~ 하고 넘어갔기 때문에
문제를 풀 때 다시 공부를 해야했다.

이 문제를 풀면서 암호화, 복호화 과정을 철저하게 되집은 점이 가장 기분 좋았다.

먼저, 문제 프로그램의 보호기법을 확인하였다.


(보호기법)


오랜만에 pwnable.kr 의 문제를 풀어서 그런지 조금 새로웠다.
이유는 다른 대회문제나 다른 사이트의 포너블같은 경우
아주 당연하게 PIE, NX 기본적인 보호기법이 설정 되어있는데
pwnable.kr의 루키 문제라서 그런지 보호기법이 거의 없었다. 여기서는 카나리 하나 적용되었다.
(그리고 바이너리파일에 C코드 심볼이 첨부되어있어서 읽기도 편했다.)

바이너리 파일을 직접 핸드레이를 하고 전체적인 과정을 파악해 보았다.
크게 첫번째로 RSA 암호화의 p,q e, d를 설정하는 과정이 있다.

그리고 암호화를 위해 선택지2번을 입력하면 아래와 같이 나온다.


(암호화)


읽을 데이터 길이를 입력받는다.
그리고 데이터를 입력받는다.

여기서 조금 특이한 과정이 있었는데, 그 이유는 모르겠다.
의도적으로 이상하게 만든건지 이유가 있어서 그런건지.
특이했던건
입력을 fread로 받는데, 1바이트씩 받는다.

그래서 스택의 어느 변수에 하나씩 받아 온 후, 한 자씩 어느 글로벌 배열변수에 저장한다.



(평문 버퍼)


바로 이곳에 한자씩 저장을 한다.

그리고 이 버퍼에서 한 자씩 다시 가져와서 암호화 과정을 걸 친 후 글로벌 배열변수(암호화버퍼)에 저장한다.



(암호문 버퍼)


위 암호문 버퍼에 저장하게 된다.

그리고 이 버퍼에 있는 것을 다시 한자씩 출력용으로 데이터를 가져와서 스택에 저장한 후
그 스택에 있는 것을 출력한다.

위의 과정을 걸쳐 암호화가 진행된다.
여기서 취약한 점은 바로 버퍼 크기이다.
평문 버퍼와 암호문 버퍼 같은 사이즈로 설정해놓았다. 그리고는 평문에서 한자씩 가져와서 RSA 과정을 걸쳐서 암호문 버퍼에 저장한다.
그런데 평문버퍼의 1바이트를 가져와서 RSA 암호화를 거치면 4바이트가 되는데 이 것을 암호문 버퍼에 저장을 하기 때문에 암호문 버퍼는 당연히 넘치게 된다.

(overflow)


바로 아래에 있는 func 배열 변수까지 넘쳐버렸다.

암호문 버퍼에 있는 것을 스택으로 옮기는 과정에서도 마찬가지다. 그렇기에 스택도 overflow 낼 수 있다. 하지만 스택 카나리가 있기 때문에 메모리 leak할 방법을 찾아내어야한다.
그렇기에 나는 암호문 버퍼를 overflow하여 func 배열변수를 덮어쓸 생각을 했다.
func 배열 변수에는 함수 주소들이 있다. func[0] 에는 set_key 함수 주소가 들어있었는데,
이 주소를 덮어쓴 후, set key를 실행시키면 우리가 덮어쓴 주소가 시작되게 할 것이다.

나는 평문버퍼 앞부분에 쉘코드를 넣고, func[0] 에다가 평문버퍼의 주소를 덮어쓸 생각을 했다.
왜 자신있게 했느냐 하면, NX가 설정되어있지 않기 때문에!! 이럴때 쉘코드를 마구마구 맘껏 써야겠다는 생각이 들었다.

그렇다면 이제 문제는 어떻게하여 평문버퍼의 주소 0x602560으로 설정할 것인가 였다.
(*평문버퍼의 주소를 확정할 수 있는 이유는 바로 PIE가 설정되어있지 않기 때문에 고정적이기 때문이다.)
한 글자, 즉 0~255의 숫자를 RSA하여 0x602560으로 만들어야한다.
여기까지는 금방 파악했으나 평문버퍼의 주소를 만들기 위해 시간이 꾀 걸렸다.

RSA 암호화 과정
pow(m,e) % N = c
m은 평문이고 c는 암호문이다.

우리는 c가 0x602560이기만 하면 된다.

이것저것 시도하다가 생각이 트인 때가 있었다.


(p,q 설정과정)


p,q,d,e를 설정하는 부분인데
실제 RSA 에서는 P와 Q는 굉장히 큰 소수여야한다.
소수!!

하지만 이 프로그램에서는 소수인지 검사하지 않는다. (아마 그렇게 되면 프로그램이 무거워져서 그랬나?)
무튼, 여기 프로그램에서는 p와 q가 소수가 아니어도 된다는 점!
그렇기에 값을 마구마구 설정해서 원하는 c(암호문)값만 만들 수 있으면 된다는 거였다.

pow(m, e) % N = 0x602560
10진수로 쓰면
pow(m, e) % N = 6301024 이고
다시 정리하면
N * a + 6301024 = pow(m, e)
여기서 m은 14, e는 7이라고 설정했다.
그래야 6301024의 근처 숫자가 되기 때문이다.
그렇게 되면 
N * a = 99112480
이 되고, N은 6301024 보다 큰 조건을 만족하고, 작은 N의 값을 12389060 으로 설정했다.

그러면 p,q 값을 찾아야하는데
이 값은 잘 정해야한다. 이유는 p,q값이 char로 설정되어있기 때문에 0x0000~부터 0x7FFF 까지의 숫자여야한다.(64비트이기 때문)


N이 12389060 이기 때문에 p,q를 1217, 10180으로 설정했다.

그리고 우리가 원하는 평문버퍼 주소(0x602560)으로 나오는지 확인해보았다.



(확인)



(확인 결과)


확인해보니
딱 나왔다!

이제 끝났다!

func[0]부분까지만 덮어쓰려면 265만큼 넣으면 된다.



(265 바이트 입력)



(결과모습)


결과 딱 func[0] 까지만 덮어쓰여진 것을 볼 수 있다.

이제
페이로드 앞부분에는 쉘코드를 올리고
나머지 241바이트
그 후 0x0e  값을 보낼 것이다.
마지막 바이트가 RSA 과정을 거쳐 평문버퍼의 주소가 된다.

exploit을 작성했다.



(쉘코드 및 변수 설정)



(exploit)



(exploit 결과)


문제 풀이 끝

'WarGame > 500 Project' 카테고리의 다른 글

(67/500) pwnable.kr - note  (0) 2018.03.29
(65/500) pwnable.kr - echo2  (0) 2018.03.08
(64/500) backdoor - Enter the matrix  (0) 2017.10.31
(63/500) pwnable.kr - syscall  (2) 2017.10.22
(62/500) NOE.systems - BURYBURY  (0) 2017.10.01
오랜만에 포너블 문제를 풀었다.
그간 다른 것들을 공부하느라 워게임을 못풀었는데

감떨어질까봐 하나 풀어봤는데, 역시 감이 떨어진 느낌이 들었다.
틈틈히 다시 풀어봐야겠다.!

pwnable.kr 사이트의 echo2 문제이다.

이 문제는 echo 서비스를 실행하는 프로그램을 연결해두었다.


(echo2 문제)


파일을 주기 때문에 직접 다운받아서 분석해볼 수 있다.



(보호기법)


보호기법을 보면 되있는게 없다.
그래서 50점 짜리였나보다. (오래걸리진 않았지만 그닥 쉽게 풀진 않은것으로 보아 감이 많이 떨어진 느낌이다.)

echo2 프로그램에서는 2가지 서비스를 제공한다.
FSB 라는 항목인데 여기서는 스택에 입력을 받았다가, 입력한 문자열을 그대로 다시 화면에 보여주는 기능
UAF 라는 항목에서는 힙영역에 입력을 받아 저장했다가 다시 화면에 보여주는 기능을 한다.

대놓고 어떤 취약점이 있는지 알려주었다.

그러면 FSB 취약점부터 확인해본다!



(fsb 취약점)


printf 에다가 인자를 문자열 버퍼 주소를 그대로 넣는 것을 볼 수 있다.
이렇게 되면 fsb 취약점이 생기게 된다.

그런데 실제로 문제를 풀 때 여기서 조금 헤메었다.
그 이유는
실제 내 컴퓨터에서는 메모리 노출 정보가 달랐다.

아래 사진을 보면서 설명하겠다.



(내 컴퓨터)


내 컴퓨터에서는
64비트 컴퓨터의 함수 전달 방식처럼 레지스터의 값들을 출력해주었다.
rdi, rsi rdx, rcx, r8, r9 이 순서대로 출력이 된다.
실제 위 사진처럼 해당 레지스터 정보가 출력된다.

그런데 실제 서버에 연결을 하였을 때는 달랐다.



(실제 연결시)


실제 연결시 레지스터의 정보들이 나오는 것이 아니라 바로 스택의 정보가 나왔다.
위 사진을 보면 문자열이 나오고 있는 것을 확인 할 수 있다.
이유는 잘 모르겠지만...
이 것 때문에 처음에 만든 exploit을 뜯어고쳤다.
처음에 만든 exploit은 내 컴퓨터에서는 돌아갔는데 서버에서는 동작하지 않았다.
그 이유가 stack 주소를 잘 찾지 못했기 때문이었다.

실제로 연결하였을 때 문자열이 나오는 것을 보고 다시 메모리 정보를 leak하였다.



(눈에 띄는 주소)


해보았을 때 문자열이 다 끝나고 다음의 값들이 나오는 것을 볼 수 있었는데

400acb라는 값이 눈에 띄었다.
나는 이 값이 무엇인지 알고 있었다. 이 주소는 main 함수의 주소였다.

그렇다! ret 주소였다.



(ret 주소)


그렇다면?

그 앞에 있는 것은 rbp 주소! 스택주소를 알아내었다.



(rbp 주소)


그 다음 UAF 취약점이다.
나는 그 동안 heap 영역에 대해서 정리를 해왔는데
이 부분에 있어서 너무 쉽게 응용할 수 있게 되었다.

main에서 함수가 호출 된 후 마지막에 cleanup함수가 호출된다.



(cleanup)


cleanup 함수에서는 o객체를 free해준다.
o객체 안에는 greeting함수와 byebye함수 주소가 있고
실제로 greeting과 byebye함수를 호출할 때 o객체에 접근하여 호출한다.

여기서 4를 입력으로 주어 종료를 시도하면 cleanup 함수가 실행된다.
문제는 그 다음에 다시 정말 종료할 지 묻는다.
이 때 n을 눌러 종료하지 않게 되면
다시 main의 루프로 돌아가게 되는데 이 때는 이미 o객체가 free 된 후에서 프로그램이 진행된다.

echo2함수 안에서 보면



(greeting 함수 호출)


greeting 함수 호출 할 때 o객체에 접근하여



(greeting 함수)


greeting함수를 호출하는 것을 볼 수 있다.


그렇다는 건 이 값을 덮어쓸 수 있다는것! 왜냐!
바로 UAF (3번)을 입력하면 malloc을 다시 주는데 이 주소를 다시 할당해주기 때문이다.


(재할당)


실제로 다시 해보면 malloc을 통해서 받는 주소는 아까 free된 o객체의 주소를 넘겨주었다.
이제 이 주소에다가 0x20 만큼 적을 수 있으므로 greeting 함수위치에 다른 주소를 적을 수 있다는 것이다.

우리는 쉘코드를 스택에 올리고 그 쉘코드 주소를 여기다 적어줄 것이다.

쉘코드를 스택에 올릴 것인데, 바로 맨 처음 이름을 입력받는 부분이다.

입력을 받는 부분은 아래와 같다.



(이름 영역)


이 곳에 쉘코드를 올릴 것이고 그 위에 값을 보면 아까 우리가 확인한 rbp 값을 확인할 수 있다.
0x20 차이가 난다.

exploit 코드를 보면서 정리하겠다.



(shellcode 업로드)


쉘코드를 업로드하는 부분이다. 이름입력할 때 쉘코드를 업로드하고
이 부분은 스택영역이다.

그 후 스택 주소를 찾아서 정확한 쉘코드 주소를 찾아야한다.



(쉘코드 주소 찾기)


그 후 UAF 취약점 공략이다.



(UAF 공략)


UAF를 통해 o객체에 다시 접근하여 greeting 주소 부분에다가 shellcode 주소를 적어준다.

그리고 아무 기능이나 다시 시작하면 greeting 함수가 시작 될때 shellcode가 실행되어 쉘을 얻을 수 있다.



(쉘 획득)


문제 풀이 끝!

'WarGame > 500 Project' 카테고리의 다른 글

(67/500) pwnable.kr - note  (0) 2018.03.29
(66/500) pwnable.kr - rsa_calculator  (1) 2018.03.10
(64/500) backdoor - Enter the matrix  (0) 2017.10.31
(63/500) pwnable.kr - syscall  (2) 2017.10.22
(62/500) NOE.systems - BURYBURY  (0) 2017.10.01



(matrix 문제)



Backdoor 워게임 사이트의 matrix문제이다다른 엉뚱한 곳 파다가 쉽게 풀수 있었던 문제를 돌아가서 풀게된 문제이다.

먼저 바이너리를 받아서 handray로 직접 c로 바꾸었다.




(main 중 구문)


위와 같이 main문에서 choice를 입력받고 이름을 입력받은 후해당 choice에 따라 함수가 분기되는 구문이 있었다.



(zion 함수)


zion이라는 함수가 system 함수를 사용하고 있고 공략할 만한 부분이었다. Snprintf를 이용해서 buffer에다가 name %s에 치환시켜 3번째 인자를 저장한다그리고 그 버퍼를 system 함수로 실행한다.



(command injection 구상)


나는 이 구문에서 sql injection과 같이  ;를 이용해서 중간에 내가 원하는 /bin/sh을 실행시킬 생각이었다즉 이름에는 나의 payload가 들어가야하고 zion이라는 함수가 실행되어야한다.
 
Zion을 실행시키기위해서는 choice 0이 되어야하는데 0이 되면 입력을 다시 받으라고 한다정상적으로 쓰지 못한다. Zion을 실행시키기 위해 프로그램을 더 분석하던 중 zion으로 넘어갈 수 있을 만한 껀덕지를 발견했다.



(blue_pill 함수 중)


Blue_pill 함수안에 있는 내용이다여기서 zion실행 문장까지 내려가려면 조건이 이름이 Neo여야하고 입력받는 배열을 7사이즈로 해야하고 A*B의 결과가 특정 값이어야한다
Neo에다가 뒤에 null을 붙이고 나의 명령 payload를 붙여서 보내야지하고 생각했다.(잘못된 생각이었고 방향을 바꾸었어야했다.)



(기대값)



A*B의 값이 위와같이 나와야한다.




(기대값 수치)



Gdb 메모리 값을 파일로 정리했다그 값을 파이썬을 이용하여 10진수로 바꿀 것이다.



(matric 출력코드)


값을 해당 배열의 위치를 고려하여 출력하였다.



(exploit)



그렇게 해서 태어난 exploit코드
A의 값에는 해당값과 B에는 곱셈의 항등원으로 7x7 단위행렬을 넣어주어 곱셈의 결과가 기대되는 행렬이 나오도록 하였다.



(실패)



Zion을 실행까지 왔다그러나… 
 
놓치고 있었던 것….
%s 또한 문자열을 인식하는거니까 name에 null까지만 넣는다

, Neo뒤에 null을 추가하고 뒤에 payload를 넣는다는 생각은 너무 짧았었다..



(메모리 상황)



생각을 바꾸어 choice값을 0으로 만들 방법을 생각했다.

마침 name바로 위에 있으니 덮어 쓸 수 있지 않을 까 생각했다.



(입력값 검증 헛점)



입력값을 name배열만큼 검열하는 듯했지만 자세히 보니 아주 작은 헛점이 있었다.
맨 마지막의 개행문자를 null로 바꾸어준다그런데 딱 해당하는 길이가 넘어오면 걸리지 않았다
즉 비교문에 =이 빠져 한바이트를 null로 덮어쓸수 있었던 것이고 zion을 실행시킬 수 있었다.



(exploit)


다시 작성한 exploit 코드



(쉘 획득)



문제 해결!

*저건 플래그가 아니고 저 파일을 읽어야합니다 :)

'WarGame > 500 Project' 카테고리의 다른 글

(66/500) pwnable.kr - rsa_calculator  (1) 2018.03.10
(65/500) pwnable.kr - echo2  (0) 2018.03.08
(63/500) pwnable.kr - syscall  (2) 2017.10.22
(62/500) NOE.systems - BURYBURY  (0) 2017.10.01
(61/500) NOE.systems - double_input  (0) 2017.09.17

System call을 이용해 커널모드로 전환되었을 때 root권한으로 상승시켜야 하는 문제이다.
커널 exploit을 맛볼 수 있는 기회가 되었다.



(문제화면)


커널 exploit에서는 시원하게 uid 0으로 만들어버리는 방법을 사용한다그 후 shell을 실행시키면 root권한의 쉘을 얻을 수 있는 것이다.



(시스템 확인)



접속해보았더니 시스템이 arm 시스템이었다.




(커널모듈)


시스템을 확인해보니 m.ko 커널 오브젝트 파일이 있었다m.ko 모듈을 insmod 했을 거라는 예상을 해본다
 
커널 모듈 소스코드를 문제에서 주었다.



(커널 모듈 소스코드)


확인해보면 system call을 하나 만들어서 등록해두었다입력을 통해 out으로 문자열을 소문자면 대문자로 바꾸어주어 보내는 함수이다. printk를 통해 함수가 등록된 것을 알려준다.



(dmesg 확인)



dmesg를 통해서 확인해보니 모듈이 등록되어있는 것을 확인했다.
 
위에서 정의한 system_call은 입력포인터출력포인터에 대한 범위 검사가 없다그렇기에 원하는 곳에 원하는 값을 적을 수 있다는 이야기이다.
시스템콜이 동작할 때는 커널모드로 전환되므로 메모리에서 커널영역 또한 참조하고 수정할 수 있다위 시스템콜의 취약점을 이용하면 커널영역을 마음대로 수정할 수 있다는 이야기가 된다.
나는 system call table의 주소를 내가 원하는 함수로 덮어 쓰므로써 원하는 함수를 실행하기로 계획했다
 
커널 exploit 할 때는 uid 0으로 만들어버린다고 했었는데 방법은 이렇다.
commit_creds(prepare_kernel_cred (0));
 
위 주소는 cat /proc/kallsyms | grep @@ 을 통해 찾을 수 있다
그 후 table의 원하는 주소를 찾아 그 주소를 위 함수가 실행될 수 있도록 덮어쓰고 실행하면 된다.
 

나는 getuid geteuid를 덮어썼다.



(내 exploit코드)



Exploit 코드이다.
 

여기서 주의할 점은 영어 소문자의 범위가 되면 -0x20이 된다는 점이다그렇기에 60까지 쓸 수 있는데 그러면 geteuid를 실행하게 되면 0x8003f560으로 넘어 가게 될텐데, 6c 까지 슬라이딩해서 넘어가야한다. nop으로 깔아주려 했으나 arm 계열에서 nop의 코드는 0x00이다.




(nop 코드)



그렇기에 nop의 기능과 같게 의미없는 코드로 채워야했다. 12바이트를!



(mov r1, r1)


이 코드를 컴파일 한 후 실행해보면



(권한상승)


권한이 상승된 것을 확인할 수 있고 flag까지 안전하게 확인할 수 있었다.

'WarGame > 500 Project' 카테고리의 다른 글

(65/500) pwnable.kr - echo2  (0) 2018.03.08
(64/500) backdoor - Enter the matrix  (0) 2017.10.31
(62/500) NOE.systems - BURYBURY  (0) 2017.10.01
(61/500) NOE.systems - double_input  (0) 2017.09.17
(60/500) pwnable.kr - crypto1  (0) 2017.08.27


학교 수업듣느라 틈틈이 푼 문제이다.
오랜만이다.!



(문제 화면)


부리부리

이름을 봤을 때 힙영역 취약점이라고 예상했다그리고 확인해보니 힙영역의 취약점을 이용한 문제였다.



(프로그램 실행)



문제를 실행해보면 위와 같이 나온다.
기능은 ADD Chunk를 이용해 데이터를 저장할 수 있는데 3개까지 만들 수 있다.
그리고 unlink를 통해 unlink함수를 구현해 놓았구, modify에서는 
기존의 힙의 데이터를 수정할 수 있게 해놓았다.



(전역변수)



취약하다 생각한 것은 A,B,C라는 전역변수를 만들어 두었고, malloc으로 할당받을 때 
각각 A,B,C에 주소를 저장해 놓는 것이었다

그리고 수정할 때 이 A,B,C에서 값을 가져와서 그 주소에 있는 것을 수정했다.



(unlink 취약점)



바로 이부분인데힙영역의 취약점을 일부러 unlink라는 함수를 만들어놔서 구현해 놓았다현재 버전의 시스템에서는 취약점이 막혔기 때문인 것 같다.


(heap overflow)



일단 기본적으로 힙영역 취약점을 이용하려면 heap overflow가 가능해야한다. ADD 함수에서는 전혀 힙오버플로우가 일어나지 않았고 좌절할 뻔했지만 modify가 있었다.
바로 여기서 수정을 하는데 입력을 0x100 바이트 받는다.
나는 GOT를 오버라이트 해서, A,B,C라는 전역변수에 GOT주소를 적고 싶었다그렇게 되면 수정할 때마다 GOT영역을 0x100바이트 내 맘대로 조작할 수 있기 때문이다.
위에서 말했듯 A,B,C라는 전역변수에 malloc을 저장해 두었고, A,B,C 주소 중 가장 끝의 것 C 주소를 선택했다.
 
이제 3번 청크를 수정하면 GOT영역의 데이터에 나의 데이터가 올라간다.
이제 중요한건 system 함수에 /bin/sh 이라는 문자열을 어떻게 전달할 것인가 이다.
 

나는 아주 기맥힌 방법을 생각해냈다.



(공략)



이 부분에서 idx라는 전역변수에서 값을 꺼내오고 확인한 후 puts를 실행하는 과정인데,
여기서 보면 rax를 초기화 하지 않는다.!
 

여기서puts함수를



(덮어쓸 주소)


위 주소로 덮어쓰고, free함수를 system 함수로 덮어쓰면 바로 rax에 있던 값이 system 함수 인자값으로 들어간다.
그렇다면 나는 idx값에 첫번째 청크 주소값 + 0x10 을 넣을 것이다수정할 때 0x10부터 데이터가 들어가고 나는 첫번째 청크에 /bin/sh + 0x00 을 넣을 것이기 때문이다그렇게 되면 /bin/sh 무자열 주소가 system 함수에 전달되는 것이다.
 
그런데 문제가 있었다. Modify 후 가장 먼저 실행되는 것이 puts함수였다즉 puts 함수를 위 주소로 덮어쓰면 제일 먼저 rax값을 설정하는 그 주소를 실행하지 못한다는 것이다.
, puts 함수가 실행되기 전 다른 함수가 실행되어야하고그 함수에 rax값을 설정하는 주소로 덮어써야한다.
 

나는 modify의 카나리 체크함수를 이용하기로 했다일부러 카나리값을 망치게 하는 주소를 free함수에 넣는다그리고 unlink를 실행해 주어 이 주소로 넘어가고이 때 modify에서 puts 함수와 기타 함수를 덮어쓰면 원하는대로 rax값 설정 후 puts 함수로 넘어간다.



(카나리 망치기)


바로 이 주소를 free 함수로!
 

나의 계획을 정리하면 이렇다.



(공략 정리)




(문제 해결)



Exploit 작성 후 공격 실행 성공한 모습


'WarGame > 500 Project' 카테고리의 다른 글

(64/500) backdoor - Enter the matrix  (0) 2017.10.31
(63/500) pwnable.kr - syscall  (2) 2017.10.22
(61/500) NOE.systems - double_input  (0) 2017.09.17
(60/500) pwnable.kr - crypto1  (0) 2017.08.27
(59/500) pwnable.kr - dragon  (0) 2017.08.17



주정도 걸려서 푼 문제이다.
이 문제를 풀기위한 고비가 크게 3번 있었다그 고비를 중심으로 설명하겠다.
 

먼저 문제 프로그램을 살펴보자



(프로그램)


이 프로그램은 입력을 2가지 방식으로 받는다. First Input에서는 스택에 입력을 저장하고, Second Input에서는 heap영역에 값을 저장한다그리고 3번 Delete로는 값을 삭제하는데 스택에 저장한 값은0으로 채우고힙영역은 free로 메모리 할당을 해제하여 삭제한다. Print는 저장했던 값을 출력하고 Exit는 프로그램을 종료한다.
 
1번째 고비
프로그램이 단순한 executable 파일이 아니다??...

이 프로그램은 shared object 파일이었다.



(파일 정보)



So 파일은 동적으로 프로그램 실행할 때 메모리에 로딩하는 파일로만 알고 있었다물론 그런 파일이지만 이 프로그램 단독으로 실행되는 줄은 몰랐다.
 
왜 이게 고비였냐 하면메인함수로 찾아가기가 힘들었다.
게다가 심볼이 없다. Start address를 찾아서 브레이크를 걸고 실행하여 일일이 뒤져가 main 함수를 찾아내었다.
 
*  Main 함수까지 가는법.
먼저 gdb로 열고 한번 그냥 실행해 주어야한다아까 말했든 이 파일은 so 파일이어서 처음에는 메모리에 존재하지 않는다… 한번 실행해주고 중단 시킨 후 재실행할 때 break를 잡아준다.

Break 잡아주는 위치는 _dl_start_user 여기로 잡아준다이건 내가 찾아낸 부분인데 이 부분 전에 starter 어쩌구 함수에서 실행이 되고 이 함수로 넘어오는데 여기에 break 잡아준다.




(main 찾아가기(1))




main 찾아가기(2)



그 후 위 사진처럼 저 위치에서 si로 함수 안으로 들어가준다.



main 찾아가기(3)



여기 call rax에서 si로 함수 안으로 들어가주면 여기서부터 main 함수이다.



main 함수



Main 함수드디어찾았다.! (여기서 첫번째 고비를 넘겼다.)

(여기 main함수 까지 찾아오는데 정말 오래걸렸다.)
 
두번째 고비
스택과 힙힙은 취약점이 없어보이는데??...
 
두번째 고비는 힙 영역에 취약점이 없어 보인다는 점이었다스택 영역은 스택 카나리가 존재해서 bof를 생각도 안했다그리고 heap영역에는 다양한 취약점이 존재한다는 걸 알고 있었기에 노려보려 했지만 쉽지 않았다.
 
그 이유는 heap영역에 취약점은 기본적으로 heap overflow에 기반했다하지만 여기서는 입력할 데이터 수를 받고 그 데이터길이만큼 malloc으로 할당해준다그리고 그 영역에 read함수를 통해서 해당 데이터 길이만 입력을 받기 때문에 overflow가 일어나기 힘들었다.
 
여기서 한참 방황한뒤 취약한 틈새를 발견하게 되었다바로 print하는 부분이다.

바로 입력한 문자열을 출력할 때 null 값까지 쭉 출력한다는 점을 이용하여 스택영역에 값들을 노출 시킬수 있겠다는 생각이 들었다.




(스택 모양)


위 빨간 영역에 바로 버퍼의 주소이다스택영역에 입력할 수 있는 길이가 130까지 되므로 충분히 카나리를 노출 시킬 수 있고 ret 주소 까지 덮어쓸 수 있다.
 

카나리를 노출시킬 수 있으면 카나리 위치에 카나리를 넣을 수 있고 그렇다면 stack overflow를 할 수 있다는 생각이 들었다.



(카나리 구하기 함수)



바로 여기서 이 부분을 파고 들기 시작했다.
 
3번째 고비
쉘을 따기 위해서는 system함수를 불러야하고 인자를 넘겨야하는데 어떻게
 
3번째 고비는 인자 문제였다.
Ret 까지 system 함수를 호출하는건 문제가 없었다.
하지만 인자를 넘겨주는데 문제가 있었다리눅스 64비트 환경에서 인자를 넘겨줄 때는 첫번째 인자는 rdi 레지스터를 이용해 넘겨준다그렇다는건 rdi 레지스터에 /bin/sh 이라는 문자열 주소를 넣고 ret으로 system을 실행시켜야하는데….
 
** 맞다.! 나는 stack smashed가 카나리 변조만으로 이루어지는 줄 알았다하지만 카나리 값을 제대로 넣어도 stack smased가 나오는 경우가 있었다바로 rsp의 위치였다리눅스 64비트에서 디버깅을 해보니 rsp 가 아무역할을 안하는거 같길래 무시했더니, rsp가 rbp보다 큰주소(아래)주소에서 놀고 있을 때 stack smashed가 일어났다함수 시작 앞에서 rsp를 sub로 조정하는 부분이 불필요한 부분은 아니었던 것이다
이 부분으로 또 한참이 걸렸다.
이 과정에서 나는 ROP를 생각했다이 때 fake rbp를 이용해 스택 포인터를 마음대로 움직여야겠다는 생각도 하였다그러면서 ROP를 해야겠다는 생각이 들었다
그래서 ROP 가젯을 찾기 위해 새로운 프로그램들을 받으면서 경험했다.
 

문제 해결에 도움을 준 프로그램은 ROPgadget이라는 프로그램이었다여기서 엄청 핵심인 가젯을 찾아주었다.



(퍼펙트 가젯)



아니…! Pop rdi !! 라니!!!!!! 게임 끝이지 뭐

Fake rbp를 이용해 스택 포인터를 버퍼쪽으로 가져간 후 여기서 pop rdi 할 때 스택 위치에 /bin/sh 문자열을 넣을 주소를 넣어주고 ret 에 system 주소를 넣어준다그리고 /bin/sh 문자열은 버퍼의 어딘가 위치에 넣고 그 offset만 맞추어서 pop rdi에 들어가게 하면 된다.


(exploit 코드)



exploit 해주면



(문제 해결)

'WarGame > 500 Project' 카테고리의 다른 글

(63/500) pwnable.kr - syscall  (2) 2017.10.22
(62/500) NOE.systems - BURYBURY  (0) 2017.10.01
(60/500) pwnable.kr - crypto1  (0) 2017.08.27
(59/500) pwnable.kr - dragon  (0) 2017.08.17
(58/500) pwnable.kr - fsb  (0) 2017.08.12


오랜만에 해결된 문제이다.

crypto1 문제로 암호학 문제이다.



(crypto1 문제 화면)



암호방식은 AES-CBC 방식으로 암호화된 내용을 주고받는 프로그램인데 결론적으로 admin으로 로그인 하는 것이다기사공부때 공부했던 AES 암호 방식 알고리즘을 차근차근 다시 뜯어보게 된 계기가 된 문제이다

누가 AES 암호화 특징을 이용해서 풀었다고 했다...
그래서 AES 암호방식을 공부하고 키를 찾으려했지만 AES 특징이라기보다는 블럭암호의 특징이라고 하는게 맞을것 같다...
덕분에 AES 암호 알고리즘 과정을 뒤져보는 시간을 갖게 된거 같다...ㅜ

 

해당 프로그램은 클라이언트와 서버 두개로 이루어져있다클라이언트 프로그램을 먼저 분석해보았다.



(클라이언트 프로그램)



(클라이언트 프로그램)



클라이언트 프로그램은 아이디와 비밀번호를 입력받아서 특정형태의 문자열을 만든 후 그 문자열을 AES암호화 과정을 한 패킷을 전송한다여기서 이 패킷을 보내기 전에 출력해준다.
특정 형태란 ID-PW-Cookie 이런식으로 사이에 대쉬(-) 를 입력하여 문자열을 구성해준다.
 
서버로 입력을 보낸 결과가 admin이 아니라면 프로그램이 중간에 종료된다.
admin으로 로그인해야 된다고 생각이 들었다.
 

이제 서버 프로그램을 분석해보았다



(서버 프로그램)



서버 프로그램에서는 전달받은 내용을 복호화한 후 아이디와 쿠키값을 연결하여 SHA-256으로 해쉬한 값이 pw와 일치하는지 확인한다
여기서 알 수 있는 것은 SHA-256(id+cookie) = pw 라는 것이다.
비밀번호를 알기 위해 우리는 cookie 값만 알면 된다.
 
여기서 많은 삽질이 있었다쿠키 값을 찾기 위해 AES 키를 찾으려고 했다그 과정에서 암호화 알고리즘을 분석하게 되었고 알게 된 것이 키를 알아내기 쉽지 않다는 것이었다다른 방법으로 접근 해보게 되었다.
바이트 단위로 노출하기!
보면 id-pw-cookie 이렇게 문자열이 구성된 후 암호화가 이루어진다.
만약 여기서 id에 –  13개를 입력하고 비밀번호를 입력하지 않는다면
Cookie 앞에 ‘-‘  15개 입력이 된다. AES 16바이트씩 블록으로 암호화가 이루어지므로 첫번째 블록은 쿠키의 한글자가 포함되어 “-“*15 + “쿠키 첫글자” 이런식으로 암호화가 된다.
그렇게 되면 여기서 우리는 가능한 글자를 대입해 보아 첫글자를 맞출 수 있다.
 
 

이런식으로 한글자씩 노출시키는 것이다.



(exploit 코드 일부)



쿠키의 앞 14자리를  찾는 코드이다이런식으로 한자리씩 맞추어 찾을 수 있다

(전체 exploit 코드는 공개하지 않겠습니다. 궁금하시면 글 주세요.)



(쿠키값)



Exploit 코드를 이용해 찾은 쿠키값을 찾았다.




(pw를 생성하기 위한 코드)



이제 쿠키와 id를 이어 SHA 해쉬해준다.

그 값을 pw로 admin으로 로그인 하면 플래그가 나온다.



(문제 해결)

'WarGame > 500 Project' 카테고리의 다른 글

(62/500) NOE.systems - BURYBURY  (0) 2017.10.01
(61/500) NOE.systems - double_input  (0) 2017.09.17
(59/500) pwnable.kr - dragon  (0) 2017.08.17
(58/500) pwnable.kr - fsb  (0) 2017.08.12
(57/500) pwnable.kr - ascii_easy  (2) 2017.08.09


정말 오래(?) 걸렸던 문제이다.

하루종일 문제푼것은 아니지만 조금씩 풀었는데 그래서그런지 오래걸린 문제이다.



(dragon 문제)




(문제화면)


문제 화면을 보면 게임을 준다.

게임을 다운로드해서 실행해본다.




(dragon 게임 실행)



직업을 고르고 용과 싸우는 게임이다.
이길 수 없다...

gdb로 분석을 시작했다.
main 함수에는 입출력 버퍼 설정하고 PlayGame 함수로 넘어간다.

그러므로 PlayGame 함수부터 분석해보겠다.



(PlayGame 함수)



함수를 보니 SecretLevel 함수?가 있다.
직업을 고를 때 3을 입력하면 이 함수로 넘어갈 수 있다.

그러면 SecretLevel에서는 뭘 할까?


(SecretLevel 함수)


비밀번호?를 입력 받고 문자열과 비교해서 일치하면 쉘을 던져준다...!

그래서 입력 문자열포멧과 비밀번호를 확인해보니



(입력 포멧, 입력해야할 문자열)



scanf 로 10글자를 받는데,,,
입력해야할 글자는 10글자가 훨씬 넘는다

즉 불가능.! 이다.
(그러면 저 문자열 10번째 다음 바이트를 00으로 바꿔서 해보자라는 생각이 들었는데, 저 문자열은 코드영역에 있기 때문에 쓰기 권한이 없어 불가능했다..)

그러므로 우리가 쓸수있는 방법은 바로 이 system("/bin/sh") 이 실행되도록 0x08048dbf 이 주소로 실행 흐름을 돌리면 된다.



(우리가 사용할 쉘)



여기서 이제 함수 하나하나 분석을 하는데 오래걸렸다...

FightDragon함수를 보면 malloc과 free를 2번씩 사용하는 모습을 확인하고 uaf 취약점이 아닐까 생각이 들었다.

그런데 uaf 취약점이든 bof든 무슨 취약점이든 입력할 곳이 있어야하는거 아닌가?...
전부 scanf  %d 로 입력을 받고 우리가 뭐 어떤 값을 입력할 곳이 없는 것이다

그러던 중 FightDragon 함수 아래쪽에서 문제 해결의 핵심을 찾았다.



(문제의 핵심)



이 부분의 scanf는 조금 특별했다.

위에서 malloc을 해주고 그 곳에 입력을 받고 게다가!



(16글자)



더구나 16글자를 입력 받는다!!!
이 부분에서 우리의 payload를 넣을 수 있다는 생각이 들었다.

그러면 어떻게 해야하나 위로 올라가보니



(공격함수)



공격함수 끝난 후 eax 값을 ebp-0x18을 넣고



(비교)



ebp-0x18 값이 0이 아니어야 입력할 기회가 주어진다.

즉, 용을 잡아야 기회가 주어진다.

또 여기서 시간을 많이 썼다.
용을 어떻게 잡는담... 인터넷에서 힌트를 좀 받았다. dragon 구조체를 잘 보면 된다고 나왔다.

dragon 구조체를 몇일을 들여다 보다 무릎을 탁 쳤다.




(dragon 구조체)



dragon 구조체를 보면
처음에는 몬스터 정보를 출력하는 함수 주소.
두번째는 몬스터 체력과 그 옆에 5가 체력회복량이다.
여기서 잘 보면 몬스터 체력이 바이트 단위로 설정되어있다. C언어로 표현하면 char자료형 처럼 1바이트 짜리이다.
범위로는 -127~127 이다.

엄마용이 80체력에 공격력이 10이니깐 계속 공격안하고 버티면 127을 넘길수 있을거 같다.
그러면 음수 값이 되고 용이 죽는다?

테스트!


(테스트 성공)



정말 127이 넘으니 용이 죽었다.
그리고 16바이트 입력을 넣을 수 있는 기회가 주어졌다.

그러면 취약점은?
scanf 로 입력받고 아래 코드를 보니 call eax 가 보인다.

보니 dragon 구조체가 free 된걸 까먹은건지 dragon 구조체의 첫번째 인자인 몬스터정보 출력 함수를 호출하고 있다.



(취약점)



내가 16바이트 입력한 곳이 방금 malloc 으로 받은 곳이니 이 주소가 전에 free 되었던 힙영역 주소이다.
같은 크기의 메모리 할당이니 전에 사용했던 그 주소가 재사용 된다.
확인해보자.! 
이름을 AAAA로 입력하였다. 그러면 처음 4바이트가 0x41414141 가 될것이고 아래 call eax에서

이 주소를 몬스터정보 출력 함수인줄 알고 이 주소를 호출하려 할 것이다.


(테스트)



AAAA 입력!




(확인)


테스트가 확인되었다.

그렇다면? 입력을 해줄 때 아까 우리가 봤던 SecretLevel에서의 쉘을 실행시키는 부분 주소를 입력해주면 된다.

pwn 모듈을 이용해 작성했다.



(exploit)



그리고 공격!



(쉘획득)



쉘을 획득하였다.!

'WarGame > 500 Project' 카테고리의 다른 글

(61/500) NOE.systems - double_input  (0) 2017.09.17
(60/500) pwnable.kr - crypto1  (0) 2017.08.27
(58/500) pwnable.kr - fsb  (0) 2017.08.12
(57/500) pwnable.kr - ascii_easy  (2) 2017.08.09
(56/500) pwnable.kr - asm  (0) 2017.07.14


오늘 해결한 문제는 fsb 문제!



(fsb)



FSB가 뭘까 했는데 검색해보니 Format String Bug의 줄임말이었다.
포멧스트링 공격이라..!
기사 공부하면서 배웠던 내용이지만 직접 해보는 건 처음이라 다시 많이 찾아보면서 공부하였다.

단순히 메모리값을 노출시키는데 그치지않고 값 또한 변조할 수 있다는 사실을 알았다.

문제의 소스코드!



(소스코드)



소스코드를 보니 pw와 key가 같으면 쉘을 던져준다.
pw와 key를 같게 해야하니 key를 변조 시키면 되겠다 라는 생각이 들었다.
key는 /dev/urandom 에서 랜덤한 값을 8바이트 가져온다.

fsb 공격의 기회를 4번 주는데 풀면서 이해했는데 왜 4번을 준지 이해했다.
GOT를 overwrite할 수도 있지만 4번을 준 이유는 pw와 key를 같게 하게 해서 풀으라는 문제 출제자의 의도인듯 했다.

처음에 당황했던 것은 찾아보면서 공부할 때는 버퍼가 모두 스택에 있었던 경우라서 버퍼에 직접 주소를 입력할 수 있고 그 주소를 직접 덮어썻는데 문제에서는 버퍼가 전역변수로 설정되어있어 스택에 존재하지 않는다...

그렇기에 스택에 key값의 버퍼 주소를 직접 적어주고 그 주소를 이용해 key값을 덮어 써야한다.
key의 자료형은 unsigned long long 이기에 8바이트 한번에 4바이트씩 덮어 쓸수 있으므로 2번 해야한다. 따라서 4번의 공격 기회를 준것이다.

스택에서 사용할 수 있는 주소를 보자!



(사용할 수 있는 주소)



위 사진을 보면 우리가 쓸만한 변수 주소가 보인다.

0xffedb0 주소에 먼저 key값의 주소를 적을 것이다.



(key값 주소 적기)



포멧스트링 공격을 하고 메모리를 살펴보면



(fsb공격 후 메모리)



key 주소가 덮여 쓰여진 것을 확인 할 수 있다.




(key주소)


key는 총 8바이트 크기의 자료형이므로 그 다음 4바이트도 덮어써줘야하므로 
key주소+4바이트 주소를 적어준다.



(완성)



완성된 주소를 볼 수 있다.
이제 두 주소를 이용해 원하는 값을 적고 pw입력할 때 그 값을 입력할 것이다.

처음에는 0101010101010101 를 입력하려고 했다.



(key 덮어쓰기)



이렇게 덮어쓰면 key 첫 4바이트에 01010101 이 덮어쓰여지는 것을 볼 수 있다.


(key 변조)



이렇게 그 다음 4바이트도 덮어 쓸 수 있다.

하지만 pw를 입력하는데에 문제가 있었다.
입력할 때 strtoull 함수를 이용한다.
입력한 수를 코드를 보면 인자로 10을 주어 입력된 문자열을 10진수로 인식하여 unsigned long long 자료형에 맞추어 숫자로 바꾸어준다.

그런데 어셈블 코드가 좀 수상했다.



(strtoull 근방 코드)



SAR ? 
31바이트를 오른쪽으로 움직인다. 부호를 신경써서. 즉 0이나 1 밖에 나올 수 없다.

0101010101010101 를 만들기 위해 값을 입력해보겠다.



(테스트)



eax와 edx에 값이 잘 들어가는 것을 볼수 있다.



(잘 들어간 값)



그런데 그 뒤 코드들 때문에 4바이트가 날아간다...




(0이 되버림)



음수를 입력하면 -1이 되고 양수를 입력하면 0 이 된다.

즉,,, key 주소 다음 4바이트에 있는  값이 0이되거나 ffffffff 이 되야한다.

검색을 해보던 중 해결할 만한 방법을 찾아냈다.



(0 입력)



%20$n

20번째 위치 인자 주소에 출력된 바이트 수를 입력한다. 여기서는 출력되는게 없으므로 0바이트!



(입력된 0바이트)



0바이트가 입력된 것을 확인 할 수 있다.

key값을 전부 0으로 만들고 0을 입력할 것이다.!

자! 그럼 공격을 해보자!



(key 주소 입력)



(key+4 주소 입력)



주소를 입력하고 그 주소에 있는 값을 0으로 덮어쓴다.



(key 값)



(key+4 값)



그 후 0을 입력하면 성공!~



(성공!)



쉘을 획득했다.


(문제 해결)

'WarGame > 500 Project' 카테고리의 다른 글

(60/500) pwnable.kr - crypto1  (0) 2017.08.27
(59/500) pwnable.kr - dragon  (0) 2017.08.17
(57/500) pwnable.kr - ascii_easy  (2) 2017.08.09
(56/500) pwnable.kr - asm  (0) 2017.07.14
(55/500) pwnable.kr - uaf  (0) 2017.07.12

+ Recent posts