[모각코] #12 드림핵강의 리버스 엔지니어링(6~7강)
문제
2개의 정수를 입력받아 값이 특정 조건을 만족하면 correct!를 출력하고 틀리면 wrong!을 출력
문제풀이 - 0
input 에 숫자 2개를 입력하면 wrong!
이라는 문자열을 출력하는 것을 확인할 수 있음
- 추측할 수 있는 사실
-
프로그램은
input:
을 출력하고, 숫자 2개를 입력받고, 무언가 받은 숫자를 처리하는 코드를 실행한 다음, 그 결과에 따라correct!
나wrong!
을 출력하는 순서로 실행된다 -
main
함수에서 사용할 법한 함수로는printf
,puts
등의 출력 함수 및scanf
같이 입력을 받는 함수가 포함되었을 것,wrong!
이 출력되기 전 부분에 입력받은 숫자 2개를 처리하는 부분이 있을 것이다
문제 풀이 - 1, 메인함수 찾기
모듈 간 호출 찾기
기능
-> 호출한 함수들의 리스트 볼 수 있음
-
메인 함수 확인
-> 간단하게 참조하는 문자열만 보면 처음 출력되는
input:
문자열부터 입력받을 문자열의 형식을 지정하는 것으로 보이는%d %d
, 정답 여부를 출력할 때 쓰는 것으로 보이는correct!
와wrong!
도 보임
문제 풀이 - 2, 메인함수 분석
1|140001200 | sub rsp,38 |
2|140001204 | lea rcx,qword ptr ds:[140002230] | 140002230:"input: "
2|14000120B | call <easy-crackme1.sub_140001070> |
3|140001210 | lea r8,qword ptr ss:[rsp+20] |
3|140001215 | lea rdx,qword ptr ss:[rsp+24] | rdx:EntryPoint
3|14000121A | lea rcx,qword ptr ds:[140002238] | 140002238:"%d %d"
3|140001221 | call <easy-crackme1.sub_140001120> |
4|140001226 | mov edx,dword ptr ss:[rsp+20] |
4|14000122A | mov ecx,dword ptr ss:[rsp+24] |
4|14000122E | call <easy-crackme1.sub_140001180> |
5|140001233 | test eax,eax |
5|140001235 | je easy-crackme1.140001246 |
5|140001237 | lea rcx,qword ptr ds:[140002240] | 140002240:"correct!"
5|14000123E | call qword ptr ds:[<&puts>] |
5|140001244 | jmp easy-crackme1.140001253 |
5|140001246 | lea rcx,qword ptr ds:[14000224C] | 14000224C:"wrong!"
5|14000124D | call qword ptr ds:[<&puts>] |
6|140001253 | xor eax,eax |
6|140001255 | add rsp,38 |
6|140001259 | ret |
- 스택을 확장하는 코드입니다. 이 함수에서는 0x38만큼 스택을 사용한다는 걸 알 수 있습니다.
- 첫 번째 인자에
input:
문자열의 주소를 넣고sub_140001070
함수를 호출합니다.sub_140001070
내부로 들어가서 분석할 수도 있지만, 여기서는 프로그램의 동작과 인자만으로도printf
이거나printf
와 비슷한 함수라는 것을 충분히 알 수 있기 때문에 내부 함수 분석은 넘어가도 됩니다. 이와 같이 내가 분석할 대상의 중요한 부분이 아니라면 적당히 추측하고 넘어가는 것이 분석 시간을 단축하는 데 있어 중요한 요소 중 하나입니다. - 첫 번째 인자에
%d %d
문자열의 주소를 넣고, 두 번째 인자에rsp+0x24
, 세 번째 인자에rsp+0x20
을 넣고sub_140001120
를 호출합니다. 첫 번째 인자와 함수 호출 시점을 미뤄봤을 때sub_140001120
가scanf
라는 것을 추측할 수 있습니다. 그리고rsp+0x24
와rsp+0x20
에는 각각 입력한 첫번째 숫자와 두번째 숫자가 4바이트 정수형으로 들어간다는 것도 알 수 있습니다. - 첫 번째 인자에
rsp+0x24
, 두 번째 인자에rsp+0x20
을 넣고sub_140001180
를 호출합니다. 즉 입력받은 두 숫자를 인자로 받습니다. sub_140001180
함수의 리턴값인eax
를 확인해 0이면 점프를 뛰어wrong!
이 출력되고 1이면 점프를 안 뛰어correct!
을 출력합니다. 이를 통해sub_140001180
함수가 입력받은 숫자를 검사하는 함수라는 것을 확실하게 알 수 있습니다.main
함수의 리턴값을 0으로 설정하고 확장한 스택을 정리한 후 리턴합니다.
문제 풀이 - 3, sub_140001180
분석
분기문이 많은 함수
-
x64dbg
는 그래프로 보기 기능-> 그래프로 보고 싶은 함수를 골라 오른쪽 클릭을 눌렀을 때 나오는 메뉴에서 그래프를 선택하거나, 혹은 간단하게 g를 누르면 그래프 창이 뜸
- 그래프: 노드(코드 부분) + 엣지(선)
- 엣지 색
- 초록색:
jcc
명령어에서 분기를 취했을 때 가는 노드 - 빨간색:
jcc
명령어에서 분기를 취하지 않았을 때 가는 노드 - 파란색: 항상 분기를 취하는 노드
- 초록색:
- 엣지 색
- 그래프: 노드(코드 부분) + 엣지(선)
문제 풀이 - 4, sub_140001180
분석
-
1번 노드(시작 부분)
인자로 받은
ecx
(첫 번째 인자)와edx
(두 번째 인자)를 각각rsp+0x8
과rsp+0x10
에 저장합니다. 하지만 이후sub rsp, 0x18
명령어 때문에 이후rsp
를 통해 저장된 인자에 접근할 때는rsp+0x8
이 아닌rsp+0x20
,rsp+0x10
이 아닌rsp+0x28
로 접근하게 됩니다. -
9번 노드
함수의 끝 노드입니다. 확장한 스택을 정리하고
ret
하는 코드밖에 없습니다. -
6, 7, 8번 노드
9번 노드(함수의 끝)와 연결된 노드들입니다. 자세히 보시면 노드들이 함수의 리턴값인
eax
를 설정한다는 것을 볼 수 있습니다. 6번과 8번 노드는eax
를 0으로, 7번 노드는eax
를 1로 설정합니다.앞서 메인함수 분석에서
sub_140001180
가 1을 리턴했을때correct!
가 출력된다는 사실을 생각해 봤을 때, 6번 노드와 8번 노드를 지나가면 안 되고 무조건 7번 노드를 지나가야만 된다는 사실을 알 수 있습니다. 이를 생각했을 때correct!
를 출력하는 함수의 흐름은 다음과 같습니다.1→2→3→4→5→7→9
이와 같은 흐름으로 실행되어야 1이 리턴되며 메인함수에서
correct!
가 출력되게 만들 수 있습니다
문제 풀이 - 7, sub_140001180
분석
-
4번 노드 → 5번 노드
네 번째 분기문은 4번 노드입니다.
cmp dword ptr ss:[rsp+4],4 ; [rsp+4]와 4를 비교한다jne easy-crackme1.1400011F1 ; Jump near if not equal
3번 노드에서 설정한 [rsp+4]
가 4인지 비교하고 점프합니다. 5번 노드로 가려면 명령어가 jne
이고 빨간 선이 니 [rsp+4]
가 4여야 합니다.
-
5번 노드 → 7번 노드
다섯 번째 분기문은 5번 노드입니다.
cmp dword ptr ss:[rsp+8],12FC ; [rsp+8]과 0x12fc를 비교한다jne easy-crackme1.1400011F1 ; Jump near if not equal
3번 노드에서 설정한 [rsp+8]
가 0x12fc인지 비교하고 점프합니다. 7번 노드로 가려면 명령어가 jne
이고 빨간 선이니 [rsp+8]
이 0x12fc이여야 합니다.
문제 풀이 - 8, solve.py 작성
-
구한 조건
-
첫 번째 인자가 0x2000보다 작거나 같아야 한다
-
두 번째 인자가 0x2000보다 작거나 같아야 한다
-
첫 번째 인자 * 두 번째 인자 가 0x6ae9bc여야 한다.
-
첫 번째 인자 / 두 번째 인자 가 4여야 한다.
-
첫 번째 인자 ^ 두 번째 인자 가 0x12fc여야 한다.
=> 위를 바탕으로 모든 경우의 수를 탐색하는 코드 작성
for x in range(0x2000 + 1): for y in range(0x2000 + 1): if x * y != 0x6ae9bc: continue if x // y != 4: continue if x ^ y != 0x12fc: continue print('answer:', x, y)
출처: https://dreamhack.io/
-