reversing

Layer7 리버싱 4차시 과제(2) - prob2 풀이

leesu0605 2022. 7. 27. 17:35

목차

1. prob2 분석 및 페이로드 제출


1. prob2 분석 및 페이로드 제출


main함수 어셈블리 코드

분석을 시작해보자.

함수 프롤로그

일단 처음에 아까도 봤던 함수 프롤로그(스택 프레임 생성)이 보인다.
rsp에서 0x20을 뺐으므로 지역변수 크기는 대략 32바이트 정도 될 것이다.

스택 카나리 복사

아까 prob1을 분석할 때도 봤던 스택 카나리가 또 나왔다.
스택 카나리를 사용하므로 아마 main함수의 맨 아래쪽에는 스택 카나리값이 ebp-0x8에 잘 들어있는지 확인하는 코드가 있을 것이다.

스택 카나리 확인

존재한다.
그럼 이 두 부분을 제외하면 아마 모두 사용자가 직접 작성한 코드가 될 것이다.

분석을 시작하려했는데, main이 시작하자마자 srand@plt라는 함수를 호출한다.

srand 호출 코드

srand는 rand함수를 사용하기 전 랜덤값을 만들어낼 시드를 정해주는 함수이다.
그런데 0xfeedc0de라는 시드를 사용하고 있는 것으로 보아 C프로그램 내에서 시드값을 16진수 숫자로 전해주었는데,
이는 프로그램 실행마다 똑같은 시드에서 랜덤값을 추출해오므로 프로그램을 실행할 때마다 랜덤값이 변하지 않는다.
(랜덤값이 계속해서 프로그램 실행마다 변하게 하고 싶다면, time.h를 선언해 시드를 현재 시간으로 전해주면 된다.)

어쨌든, 분석을 계속해서 해보자.

main+111로 점프

그런데 srand함수 호출이 끝나자마자 뜬금없이 main+111 주소로 점프한다.
main+111에 어떤 코드가 들어있는지 확인해보자.

main+42 ~ main+111

main+111 코드를 보면 아까 0을 저장했던 주소에 들어있는 값과 0x13(19)를 비교해서 작거나 같으면 main+42로 옮기는 일을 하고 있다.
그런데 이 전개 뭔가 익숙하다.
아까 prob1을 풀 때도 갑자기 비교하는 부분으로 점프했는데, 바로 for문에서였다.
main+111 바로 위 코드, main+107을 보면, 아까 0을 저장했던 주소에 들어있는 값을 1씩 증가시키는 것을 볼 수 있다.
추측상으론, for([rbp-0x10]=0;[rbp-0x10]<=19;[rbp-0x10]++) 이런 for문 코드인 것 같다.

그럼 main+42부터 분석을 시작해보자.

main+42 분석

main+42를 보면 edi에 0x4008d4를 넣고, puts를 호출하는 것을 보고 있다.
이 전개는 prob1에서도 봤었다.
puts([0x4008d4]에 들어있는 문자열)이다.
한 번 저 주소에 어떤 문자열이 들어있는지 확인해보자.

이런 값이 들어있다.
매 반복문이 시작될 때마다 이 문자열을 출력하고 시작하는 것 같다.
(puts는 뒤에 개행문자가 자동으로 들어간다.)

다음 코드를 보면, rand함수를 호출하고 그 반환값을 [rbp-0xc]에 저장하고 있다.

scanf 함수를 호출하고 있다.
rsi에 [rbp-0x14]를 넣고 있는데, scanf 함수로 미뤄봤을 때, 포맷스트링으로 입력받은 값을 저장할 주소값으로 볼 수 있다.
edi는 이제 말하지 않아도 scanf에 들어갈 문자열로 볼 수 있을 것이고, eax를 0으로 만드는 것으로 보아 scanf의 반환값을 받으려하는 것 같다.

정수 포맷 인자가 들어있다.


(scanf의 반환값은 입력받은 포맷 인자의 갯수이다.)

그 다음 코드에선 입력받은 값과 추출해낸 랜덤값을 비교해 같으면 main+107로 점프하고 같지 않으면 원래 흐름대로 수행한다.
물론 같을 때가 정답이겠지만, 분석이 목적이므로 같지 않을 때부터 어떤 흐름을 타는지 확인해보겠다.

0x4008ef에 들어있는 문자열을 출력하고, main+209로 점프한다.

0x4008ef에 들어있는 문자열

아까 봤던 스택 카나리가 변조됐는지 확인하는 코드가 보이고, 카나리 변조 확인이 끝나면 main함수를 리턴하고 있다.

그럼 이제 rand값과 입력한 값이 같으면 어떻게 되는지 확인해보자.

아까 봤던 반복문 코드가 나온다.
즉, rand값을 생성하고 입력값과 비교하는 과정을 총 19+1=20번 반복하는 것이다.
그럼 이 과정이 끝나면 어떻게 되는지 살펴보자.

0x4008f8에 들어있는 문자열을 출력한다.
여기에 무슨 문자열이 들어있을까.

input correct number라는 문자열이 나온다.
우린 이미 모든 랜덤값을 맞췄다.
그런데 이런 문자열이 나온다는 건 마지막으로 입력해야할 숫자가 하나 더 있다는 뜻이다.

실제로, 바로 다음 코드에선 scanf를 호출해 rbp-0x14에 정수를 입력받고 있다.

입력을 받은 후에는,
입력받은 값이랑 0xdeadface를 비교해 다르면 main+194로 점프시키고 있다.

아까처럼 바로 main을 리턴시킨다는 코드라고 봐도 되지만, 0x400916주소에 들어있는 무언가를 출력하고 프로그램을 종료한다.

wrong!!을 출력하고 프로그램을 종료한다.

그럼 만일 0xdeadface를 입력한다면 어떤 값이 나오게 될까.

0x40090d주소에 들어있는 문자열을 출력한다.

correct!라는 문자열이 들어있는 것으로 보아 0xdeadface를 입력하면 플래그가 출력될 것 같다.

실제로 flag_generator에서 리턴값으로 받은 문자열 주소를 puts로 출력하고 있다.

지금까지의 내용을 정리하면, 0xfeedc0de 시드에서 나온 랜덤값 20개 모두 추측, 마지막으로 0xdeadface 입력 -> 플래그!!
이렇게 된다.

참고로 이건 브루트포스로 풀었을 때, 랜덤값이 바뀌지 않는다고 해도
(9,223,372,036,854,775,807)^20 가지의 경우의 수가 나온다.
그러므로 우린 같은 시드에서 나온 랜덤값은 같다는 것을 이용해 랜덤값을 미리 구하고, 프로그램을 실행해 넣어볼 것이다.

이런 식으로 20개의 랜덤값을 얻어오는 프로그램을 짜고, 실행시키면 이 시드에서 추출한 랜덤값 20개가 나온다.

위에 짠 프로그램을 계속 실행시켜보면, 출력되는 랜덤값은 변하지 않는다는 사실을 알 수 있을 것이다.
어쨌든, 우린 이 랜덤값20개와 0xdeadface를 10진수로 변환한 값을 넣으면 플래그를 얻을 수 있다.
실제로 프로그램을 실행시켜보고 저 값들을 넣어보자.

한 번도 wrong!!이 출력되지 않고 넘어갔다.
(숫자가 깨진 것은 복사&붙여넣기를 써서 그렇다)
어쨌든 랜덤값 맞추기는 성공했으니 0xdeadface를 넣어보자.

플래그가 나왔다!!
백준에서도 못 찍은 마스터를 여기서라도 찍어보니 기분이 좋다.
"flag : Layer7{y0u_are_m45ster_0f_A55EMB1Y!}"


2. flag_generator 분석


flag_generator 로직은 prob1이랑 비슷비슷했다.

일단 문자를 저장할 배열을 malloc으로 만들어준다.
문자열 크기는 35이다.

문자열을 만들면 movabs로 8바이트씩 문자를 4번 복사하고, 남은 3개의 문자는 mov로 직접 넣어줬다.

그 다음 35번 반복하면서 문자열의 i번째 숫자에 2의 보수를 취한 값과 0xffffffcd를 비트xor연산해줘 각 자리의 문자를 생성한다.
이 작업이 끝나면 플래그가 나오고, rax에 문자열의 주소를 넘겨줘 main함수에 문자열을 리턴한다.