맆케알의 easy_elf 문제를 풀어보기로 했다.
실행시 첫 줄이 출력되고 입력을 받는다.
1234를 입력했더니 Wrong를 출력하고 꺼진다.
radare2로 프로그램을 열어보았다.
함수 목록을 띄워주는 afl명령어를 쳐 보았더니 엔트리와 메인이 나뉘어져 있다.
메인 함수부터 디스어셈된 데이터를 구경해보자.
메인 함수에서 Easy_ELF출력을 위해 write 함수 사용 전에 인자를 넣는 모습을 볼 수 있다.
write 함수를 call 후 알 수 없는 함수 2개를 call, 그 이후에 eax와 1을 비교해 eax가 1이 아니면 jmp를 하여 Wrong을 출력하는 것을
메인 함수에서 알 수가 있었다.
그럼 맞췄을 땐 뭐라고 하는지 찾아봐야겠다.
strings를 이용해 문자열이 뭐가 있는지 찾아봤다.
Correct!가 있는것을 알게 되었다.
일단 메인에서 write함수를 call하고 난 뒤에 호출하는 두 개의 함수를 분석해보자.
첫 번째 함수는 인자를 넣고 scanf를 호출한다.
0x804a020은 문자열을 입력받을 주소같고, 0x8048650은 무슨 주소인지 모르겠으나 인자 순서상 "$s" 일 것 같다.
확인을 해 보자. s 명령어로 0x8048650을 따라가 hex로 출력해보았다.
실제로 저 주소에는 %s가 들어가 있는 것을 볼 수 있다. 보려는 걸 의도하진 않았지만 옆에 Correct도 byte 데이터로 들어가있다.
아무튼 scanf 인자들을 넣고 호출하는 것을 볼 수 있고, 문자열이 들어가는 주소도 알게 되었다.
그 다음 함수를 살펴보자.
함수가 좀 길었는데 리턴하는 부분만 따 보았다.
위에서 뭔갈 자꾸 비교하고 결과가 다르면 점프시키는데, 메인 함수를 분석할 때 알아낸 것이
이 함수가 끝나면 eax의 값이 1인지 아닌지 비교하여 값이 맞았는지 틀렸는지 알아낸다.
그러므로 점프하는 구간을 잘 봐야한다.
위의 사진에서 je구간을 보면 어떤 값이 맞았을 때 다음 분기로 점프하고, 틀리면 eax에 0을 넣고 리턴 앞으로 점프시킨다.
그렇다면 je구간의 루틴을 분석해 값이 다 맞게 하면 될 것 같다.
어디서 자주 본 주소를 사용한다. scanf때 인자로 사용했던, 입력된 문자열이 들어있는 주소를 사용한다.
아래로 더 내려도 문자열의 주소를 0x~20부터 0x~25까지만 사용하는 것을 알 수 있다.
알아야 할 구간을 표시해보자면 ? ? ? ? ? ? [0x804a020 ~ 0x804a025]
0x~20 - 0x~25 사이의 문자가 어떻게 들어가야 루틴을 뚫을 수 있는지 분석해보자.
처음에 20을 비교하는것도 아니고 21을 비교한다. 일단 [1]번째의 문자가 0x31인지 확인하고 맞으면 점프하여 다음 루틴으로 넘어간다.
벌써 하나는 알아내버렸다.
? 0x31 ? ? ? ? [0x804a020 ~ 0x804a025]
다음은 20을 eax에 가져오고, eax의 값에 0x34를 xor을 시킨다. 그리고 그 값의 byte사이즈만큼 (al) 다시 20에 넣는다 ?
20의 값을 xor한 값을 다시 20에 넣는다. 그리고 따로 비교는 하지 않고 다음 줄로 넘어간다.
22, 23도 똑같은 방법을 사용했다. 각 값에 원하는 값과 xor시킨 뒤 다시 각 값의 원래 주소에 넣는다.
그리고 그 아래는 다시 그냥 단순 비교하는 식이 나온다.
xor루틴의 핵심 비교라고 생각해 보았으나 그것은 아니었고, 24를 단순 비교해서 점프시킨다.
24는 0x58이어야 하는 것 같다.
? 0x31 ? ? 0x58 ? [0x804a020 ~ 0x804a025]
그 다음 루틴은 25를 비교하는 것이었다.
25를 eax에 넣고, test al, al을 하는데
test al, al은 al의 값이 0이면 zero flag를 1로 세팅한다고 한다.
한마디로 25(문자)의 값이 0이면 점프한다는 것이다.
25는 NULL이 들어가야 하는 것이 확인되었다.
? 0x31 ? ? 0x58 0x00 [0x804a020 ~ 0x804a025]
그 다음줄부턴 xor계산 루틴의 핵심이 되는 것 같았다.
각 루틴이 모양새가 같아서 한 번에 따왔다.
22, 20, 23을 각자 가져와 원하는 값과 비교해서 점프한다.
그런데 22, 20, 23은 처음에 문자열을 입력받은 값이 아니라, 의도된 X 값과 xor 계산한 값이다.
? xor X = Y라는 건데
xor은 신기하게도 ?를 찾을 수 있게 된다.
A와 B를 xor했을 때 C가 나오면
B와 결과값인 C를 알면 B와 C를 xor하면 A를 알 수 있게 된다.
인코딩 / 디코딩처럼 말이다.
xor된 값과 원하는 값을 비교해서 같은 값이 되어야 하기 때문에,
우리가 알아야 할 값은 xor 되기 전의 값이어야 한다.
그럼 찬찬히 찾아보자
먼저, 20이다.
? 와 0x34를 xor한 값이 0x78이어야 한다.
파이썬으로 값을 계산해보았다.
0x4c가 나온다. 20은 0x4c가 답인 걸 알 수 있다.
0x4c 0x31 ? ? 0x58 0x00 [0x804a020 ~ 0x804a025]
0x22는 ? 값과 0x32를 xor한 값이 0x7c이어야 한다.
마찬가지로 답을 구해내었다.
0x4c 0x31 0x4e ? 0x58 0x00 [0x804a020 ~ 0x804a025]
다음은 0x23인데, 괴상한 숫자와 xor을 한다.
? xor 0xffffff88 = 0xdd인데,
ffffffff가 -1인 것을 생각하면 음수인 것 같다.
일단 0xdd와 0xffffff88을 xor 해 보았다.
0xffffff55가 나오는데, 입력할 때 byte 사이즈로 0xffffff55를 입력할 수는 없을 것이다.
하지만 계산하는 루틴에서 계산 후 al 크기로만 다시 값을 집어넣는 것을 볼 수 있다.
?가 0x??????55라고 해도 al의 값만 가져가기 때문에 0x00000055 (0x55)가 될 수 있는 것이다.
al의 사이즈는 eax가 32, ax가 16, ah가 상위 8비트, al이 하위 8비트이다.
그렇다면 답이 다 나왔다. 23은 0x55가 되어야 한다.
0x4c 0x31 0x4e 0x55 0x58 0x00 [0x804a020 ~ 0x804a025]
이것을 파이썬으로 출력해보았다.
얼추 프로그램의 정답같아보인다. 파이썬으로 입력해보자.
정답이 맞는 것을 볼 수 있다.
공부할 때 적었던 텍스트
1
[1] == 1
[4] == X
eax=32
ax=16
ah=8
al=8
0x804a020~0x804a027
0x4c 0x31 0x4e 0x55 0x58 null
Mov eax,[3]
Xor eax, 0xffffff88
Mov [0], al
Cmp [0], 0xdd
Je clear
ffffff88랑 xor한 값이 dd이 나와야 한다
ffffff88랑 dd이랑 xor하면 0xffffff55
하지만 movzx(작은 사이즈대로 잘림)이기 때문에 al사이즈인 0x55
Mov eax,[3]
Cop [4], 0x58
Je clear
[5]는 복사해서
Test al, al 하니까 0인지 검사해서
0이면 점프(null) : 클리어
그럼 총 문자 길이는 NULL을 뺀 5를 입력받는다
'리버스 엔지니어링' 카테고리의 다른 글
[리버싱] 윈도우에서 아무것도 설치하지 않고 Hex Data 수정 (0) | 2020.07.01 |
---|---|
[리버싱] 메모장 시간/날짜 표시 순서 변경 (1) | 2020.07.01 |
[리버싱] 과연 이게 될까? Ch.1 (0) | 2020.07.01 |
인라인 패치 실습 - Code Cave patch (0) | 2020.07.01 |
crackmes.one crackme 001 by disip 풀이 + 크래킹 프로그램 제작 (0) | 2020.07.01 |