목차
1. prob1 정적 분석
2. prob1 동적 분석
1. prob1 정적 분석
prob1을 gdb로 열고, disass 명령어를 통해 main함수를 출력해보았다.
이걸 하나씩 해석해보자.
일단 main+0부터 main+4까지는 저저번 시간에 배운 스택 프레임을 형성하는 코드이다.
rbp(스택의 바닥을 가리킴)를 스택에 push하고 rsp(스택의 천장을 가리킴)값을 rbp값으로 옮겨 함수가 끝났을 때를 대비해 현재 스택의 바닥을 임시로 저장하고, 새로운 스택프레임의 바닥을 가리키게 된다.
그 다음 rsp에서 0x10, 즉, 16을 빼주었고, 이것은 main함수의 지역변수를 담기 위한 공간이다.
main+8에서는 fs라는 공간에서 값을 가져오는데, 이는 스택 카나리라고 하는 보호기법 때문에 사용한다.
[rbp-0x8]주소에 카나리값을 넣고, 나중에 함수가 리턴하기 전, 그 값이 바뀌지 않았는지 체크한다.
만일 바뀌었다면 __stack_chk_fail@plt를 호출해 프로세스를 강제로 종료한다.
이 보호기법은 스택 버퍼 오버플로우라는 꽤 비중이 높은 해킹 기법을 막기 위해 쓰는 보호기법인데, 간단히 설명하자면 문자열을 입력받을 때 문자열의 버퍼 크기보다 훨씬 많은 문자를 입력해 리턴 주소를 원하는 곳으로 오염시키는 것이다.
그러나 문자열 버퍼와 리턴 주소 사이에는 스택 카나리 값을 저장해둔 rbp-0x8 주소가 있기 때문에 이 값이 바뀌었는지
안 바뀌었는지 확인하면 오버플로우가 났는지 안 났는지 확인할 수 있고, 오버플로우가 났으면 리턴하기 전 프로세스를 강제로 종료시켜 해킹으로부터 보호하는 것이다.
main+8 다음으로 눈여겨볼 코드는 puts@plt를 호출하는 코드이다.
xor eax, eax를 쓰면 eax는 반드시 0이 되는데, 아마 함수의 리턴값을 받기 위해 쓴 것 같다.
그 다음 edi에 0x4007d4값을 넣는데, edi는 문자열 연산에 사용되고, puts바로 위에 있으므로 이것이 puts에서 출력할 문자열이 저장되어있을 확률이 높다.
그래서 x/s 0x4007d4 명령어로 저 주소에 있는 값을 문자열 형태로 출력해보았다.
이런 값이 나온 것으로 보아 main함수 처음에 input any number라는 문자열을 출력하고 나머지 동작을 수행하는 것 같다.
puts함수가 끝난 다음에는, scanf를 호출하는 코드가 보이는데, 한 번 인자를 해석해보자.
일단 lea함수 호출을 통해 rax에 rbp-0xc의 주소값을 넣고 있다.
mov는 값을 넣는 것이고, lea는 주소를 넣는 것이다.
그 다음, rsi에 rax를 넣는 것으로 보아 rbp-0xc에 입력받은 데이터를 넣는 것 같고, edi에 0x4007e5를 넣는다.
edi에 어떤 주소값을 넣는다...
어디선가 한 번 봤던 것 같다.
아까 puts를 호출할 때도 edi에 문자열 주소값을 넣고 함수를 호출했다.
이번에도 맞는지 확인해보자.
맞다!
이로써 뭔가 규칙을 찾을 수 있었는데,
puts, printf, scanf같이 문자열을 쓰는 함수들은 edi에 문자열의 주소값을 넣고, 그 문자열을 출력, 입력 함수 내에서 따로 처리해준다는 사실을 알게 되었다.
분석할 때 도움이 많이 될 지식 같아 보인다.
그리고 다음 코드를 보면,
그렇다, 아까 scanf로 rbp-0xc에 입력을 받는 것 같다고 했는데, scanf 호출 다음 코드에서 바로 사용하고 있다.
그 사실을 아니 이 코드 해석도 어렵지 않을 것이다.
일단 eax에 rbp-0xc(사용자가 입력한 값)을 넣는다.
그러고 나서 eax와 0xdeadbeef와 비교를 해 흐름을 정하는 것 같은데, 0xdeadbeef를 입력하지 않았을 때의 코드 실행 결과부터 살펴보자.
eax와 0xdeadbeef가 같지 않으니 jne를 통해 main+93번째 주소로 eip를 이동시켰다.
그랬더니 아까 스택 카나리를 설명할 때 봤던 코드가 나왔다.
일단 스택 카나리가 바뀌었는지 체크하고, main+118번째 주소로 eip를 옮기고 있다.
아마 0xdeadbeef가 아닌 값을 입력하면 그냥 프로세스를 종료하는 것 같다.
그럼, 만일 0xdeadbeef를 입력하면 어떤 코드가 수행될까?
일단 0xdeadbeef와 cmp가 같으니 jne 명령어는 타지 않을 것이고, 원래 흐름대로 명령어를 수행할 것이다.
이 다음 명령어는 이렇다.
또 어디선가 봤던 코드가 나왔다.
edi에 어떤 주소값을 넣고 있는데, 이젠 "puts에서 출력할 문자열을 edi에 넣고 있어요", 라고 확실히 말할 수 있을 것이다.
그럼 0x4007e8 주소값에 어떤 문자열이 들어있는지 확인해보자.
correct!라는 문자열을 출력하는 것으로 보아, 0xdeadbeef를 입력하면 성공하는 문제인 것 같다.
어쨌든, 계속 실행시켜보자.
flag_generator라는 왠지 플래그를 만들 것 같은 함수를 지나서, 또 rdi에 어떤 주소값을 넣어서 puts로 출력해주고 있다.
그런데 이번에는 rax값을 rdi에 넣어주고 있는데, 아마 flag_generator에서 만든 문자열의 주소값을 rdi에 넣는 것 같다.
이렇게 끝내면 아쉬우니 flag_generator도 분석해보자.
하나씩 분석해보자.
아까 main 함수에서도 봤던 함수 프롤로그가 보인다.
이번에도 지역변수 크기는 16이다.
그 다음 0x21을 인자로 주고, malloc@plt 함수를 호출하는데, malloc함수는 아시다시피 동적할당이고, 0x21을 준 것은 문자열을 담을 배열 크기를 33으로 설정해 호출한 것 같다.
그 다음 코드에선 malloc으로 받은 배열 주소를 rbp-0x8에 넣고 있다.
그렇다.
아까 추측했던 대로 문자를 한번에 8바이트씩 4번에 걸쳐 복사하고 있다.
즉, movabs 명령어를 통해 아까 malloc으로 받은 배열에 문자를 8개씩 복사하고 있는 것이다.
그 다음 flag_generator+132주소로 eip를 이동시키는데, 한번 어떤 코드가 있는지 확인해보자.
flag_generator+132에선 0x1f(31)과 rbp-0xc를 비교해서 작거나 같으면 flag_generator+94로 이동시키고, 아니면 문자열 주소를 리턴한다.
그런데 cmp명령어 바로 위에 add가 있는 것으로 보아 rbp-0xc 주소에 반복 횟수를 저장하는 for문인 것 같다.
그런데 movsxd라는 처음 보는 명령어가 나와 당황했다.
그러나 침착하게 구글링을 시도했고, 이 코드와 똑같은 코드가 stack overflow 사이트에도 있어 답변을 보았다.
https://stackoverflow.com/questions/56565510/x86-what-does-movsxd-rdx-edx-instruction-mean
여기에서는 movsxd rdx, eax를 써 부호 확장(32bit -> 64bit)을 시킨다고 한다.
뒤엔 movzx라는 명령어도 나왔는데, 이는 제로 확장이란 개념이다.
지난 블로그에서 썼던 내용을 가져왔다.
이를 통해 알 수 있는 사실은 반복 횟수는 int 형이었지만 연산을 하기 전 long long형으로 형변환 해 계산한다는 것이다.
그리고 이 코드를 제외한 나머지 부분은 (배열[(rbp-0x8)에 들어있는 값])과 0x6f를 xor연산해 그 중 8비트만 다시 (배열[(rbp-0x8)에 들어있는 값])에 넣는 코드이다.
그리고 이 과정을 31번 반복하면 배열 안의 모든 문자에 대해 연산을 마치게 되고, 이 문자열의 주소값을 eax로 리턴하게 된다.
다시 main함수로 돌아가자.
flag_generator 바로 밑에서 rdi에 flag_generator 함수에서 리턴한 문자열 주소를 복사하고 puts의 인자로 넘겨줘 출력하고 있다.
그리고 이후엔 0xdeadbeef를 입력하지 않았을 때와 같은 코드를 수행해 스택 카나리 체크를 하고, 프로세스를 종료한다.
2. prob1 동적 분석
이젠 이 코드가 어떤 역할을 하는지 알았으니 동적 분석을 통해 진짜 플래그값을 알아내자.
b main을 통해 main함수 시작점에 브레이크포인트를 걸고, r을 통해 실행시켰다.
지역변수 공간을 만드는 sub rsp, 0x10부터 시작했다.
ni를 눌러 rsp값이 어떻게 변하는지 확인해보자.
rsp값이 0x10만큼 줄어들었다.
그 다음 puts@plt까지 ni로 실행시켜보자.
아까 추측했던대로 "input any number"라는 값을 출력하고 있다.
그 다음 0xdeadbeef라는 값을 입력하면 플래그가 진짜 나오는지 확인하기 위해 scanf까지 실행시켰다.
아까 봤던 %d를 인자로 호출했고, 여기서 ni 명령을 실행하면 입력을 값을 입력할 수 있다.
우리는 여기서 입력받은 값과 0xdeadbeef라는 값을 비교해 같아야 한다는 사실을 알기 때문에 0xdeadbeef를 10진수로 변환한 값을 넣어보겠다.
이걸 입력하고 나서, jne까지 실행하면 0xdeadbeef와 값이 같기 때문에 jne 명령어를 수행하지 않는다는 사실을
알 수 있다.
그 다음, 마지막 puts까지 수행해 플래그가 나오는지 확인해보자.
플래그가 성공적으로 나왔다.
flag : "Layer7{y0u_are_g00d_at_a55emb1y}"
느낀 점
prob1을 분석해보면서 함수에 문자열을 전달할 때 edi에 주소값을 복사해 전달한다는 사실을 알게 되었고,
for문이 어떻게 작동하는지도 알게 되었다.
또, movabs, movsxd, movzx 명령어가 뭔지도 알 수 있었다.
이는 어셈블리어 코드에서 볼 수 밖에 없는 중요한 규칙, 명령어로 언젠가 리버싱 문제를 풀 때 코드 해석 속도가 굉장히 빨라질 수 있다.
'reversing' 카테고리의 다른 글
layer7 리버싱 5차시 과제(2) - prob3 분석 (0) | 2022.08.01 |
---|---|
Layer7 리버싱 5차시 과제(1) - prob5 풀이 (0) | 2022.08.01 |
Layer7 리버싱 4차시 과제(2) - prob2 풀이 (0) | 2022.07.27 |
Layer7 리버싱 3차시 과제 (0) | 2022.07.25 |
layer7 리버싱 2차시 과제 (0) | 2022.07.20 |