[1] 코드분석
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | int __cdecl main(int argc, const char **argv, const char **envp) { _BYTE *v4; // [esp+18h] [ebp-28h] char s; // [esp+1Eh] [ebp-22h] unsigned int v6; // [esp+3Ch] [ebp-4h] memset(&s, 0, 30u); setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 1, 0); printf("Authenticate : "); _isoc99_scanf("%30s", &s); memset(&input, 0, 12u); v4 = 0; v6 = Base64Decode((int)&s, &v4); if ( v6 > 12 ) { puts("Wrong Length"); } else { memcpy(&input, v4, v6); if ( auth(v6) == 1 ) correct(); } return 0; } | cs |
IDA로 뜯어본 main함수의 코드이다.
분석을 시작해보자.
1. 값을 입력받고, 그 값을 Base64Decode함수에서 base64 디코딩해준다.
2. Base64Decode부분에서 반환값은 디코딩한 문자열의 길이이다. 따라서 v6에는 문자열의 길이가 저장된다.
3. if문으로 디코딩한 문자열이 12글자 이상인지, 아닌지를 검사하여 12글자보다 크면 오류를 출력한다. 문자열이 12글자 이하이면 memcpy를 실행한 후, 또다른 if문으로 들어가서 auth(v6) == 1 인지 조건검사를 하여, 맞으면 correct함수를 실행하여 쉘을 딸수있다.
4. auth 함수 안에서는 memcpy(&v4, &input, a1)을 실행하고, s2에 calc_m5 함수를 실행해준다. 그리고나서 s2의 해쉬값을 출력해준다. 그 후에 strcmp로 "f87cd601aa ... "와 s2를 비교한다. 맞으면 correct함수가실행되는 구조인듯 하다.
5. correct함수안에서는 input값이 0xDEADBEEF 인지 검사한다. 맞으면 쉘이 실행!
[2] 취약점 분석
일단, Base64Decode함수에서 base64로 디코딩 해주므로, 우리는 입력값을 base64인코딩 해서 넣어주어야 한다.
참고로, base64는 2진수를 64비트단위로 끈어서 암호화 하는 기법이라고 한다.
하지만, 어떤 값을 인코딩해서 넣어봐도 출력되는 해시값은 계속변하고, 어떻게해도 strcmp조건을 만족시켜서 correct함수를 실행시키기에는 힘들듯 하다..
그리고 뭔가 이상하게 인코딩해서 계속 값을 입력해도 세그먼트 폴트가 뜬다.
=> 이부분이 원래 정석 문제풀이의 핵심부분인것같다. auth함수에서 memcpy가 취약점이 있다고 하는데, 아직 난 이해하지 못했다... 이해하고 나서 다시 수정해서 올릴 예정이다. 나는 야매로 풀었다.
이해했다. 인코딩해서 12만큼 입력해서 오류가 나는것의 이유는 이부분에 있었다.
auth함수의 내부이다. 보면 a1을 인자로 전달받고, v4를 선언하여 input을 v4에 복사하는것을 볼 수 있다.
메인함수에서 auth를 사용할때 auth(v6)이라고 했는데, v6은 위에서 말했던대로 문자열의 길이이다.
따라서 복사할 메모리크기는 12이이다.
근데 v4의 위치는 ebp-8부터이다. 그래서 12만큼 입력받으면 나머지 4바이트가 오버플로우되어 ebp에 변조되었던것이다!
그래서 코어파일을 분석해보려고 AAAABBBBCCCC를 인코딩한 QUFBQUJCQkJDQ0ND 라는 값을 입력했다.
역시나 세그먼트 폴트 오류를 출력하면서 프로그램이 죽어버렸다.
이제 그 이유를 찾을 차례다.
gdb로 분석을 열심히 해봤다..
위와같이 ebp값이 CCCC로 변조되어 있었다.
여기서 우리는 ebp값을 CCCC말고 다른 값으로 변경하여 ebp를 변조하여 esp의 흐름을 바꿀수있다는 생각을 해볼필요가 있다.
어떻게 esp의 값을 변경하여 프로그램의 흐름을 바꿀수있을까?
답은 바로 함수 에필로그에있다.
함수 에필로그는 leave, ret 두부분으로 이루어지고, 두부분은 각각 다음과 같다.
Leave = mov esp, ebp ret = pop eip
pop ebp jmp eip
mov esp, ebp로 esp와 ebp를 같게 만든다. 하지만 여기서 ebp값이 변조된 값이라면, 프로그램 흐름을 바꿀수도 있다.
이것을 Fake EBP 기법이라고 하는데, 블로그에 글을 작성해서 추가할 예정이다.
[3] 페이로드 작성
AAAA BBBB CCCC를 입력하였을때 ebp가 CCCC가 되었으므로, CCCC부분은 sfp 부분이다.
ret는 sfp부분 4byte 뒤에있으므로 dummy(12) + &correct(4)
이런식으로 공격을 해보려고 했으나, 생각해보니 디코딩한 문자열의 길이를 검사해주는 부분이 있어서 이런식으로는 안될것이다.
그럼 우리가 활용할수있는 부분은 sfp부분까지이므로, 위에서 말했던대로 sfp를 변조하여 Fake EBP 기법을 활용해야 한다.
일단 페이로드는 다음과 같다.
dummy(4) + &correct함수에서 /bin/sh 부분(4) + &input(4) = 12
자, 그러면 페이로드의 흐름을 분석해보자.
페이로드의 흐름은 크게 두가지로 나뉘어진다.
auth함수의 에필로그 ( leave , ret ) , main함수의 에필로그 ( leave , ret )
일단 auth함수의 에필로그에서 leave, ret을 살펴보자.
위의 페이로드대로, sfp는 &input 으로 변조되었고, 따라서 ebp또한 &input으로 변조되었다.
leave는 어셈블리 명령어로 mov esp,ebp / pop ebp 이다.
따라서 esp는 ebp가 있는 위치에 있을것이다.
그리고 pop ebp를하면 sfp로 이동하므로 ebp는 &input인 상태이고, esp는 ret으로 간다.
그 후 ret과정을 거치고 나면 일단 main함수로 돌아온다.
돌아오고나서 흐름상 바로 main함수의 leave가 실행된다.
현재 ebp = &input 인 상태라는걸 다시한번 엄두해두고...
main함수의 leave가 실행되면 mov esp,ebp로 인해 esp는 ebp가있는 위치에 있을것인데, auth함수의 에필로그에서 우리는 ebp를 &input으로 변조시켜둔 상황이다.
자, 이렇게되면 esp = ebp = &input 이 되고, 이상태로 pop ebp를 하게되면 esp는 esp+4 ( &input + 4 ) 가 된다.
그 후 leave가 끝나 ret으로 가면, esp+4가 eip가 되고, jmp eip를 하면, esp+4 부분으로 갈것이다.
근데 esp+4부분은 /bin/sh 주소이다. 따라서 쉘이 실행될것이다.
자, 이제 값들을 찾아볼 차례이다.
correct함수에서 쉘을 시켜주는 부분의 주소 = 0x08049284
input의 주소 = 0x0811eb40
=> AAAA + 0x08049284 + 0x0811eb40
입력할때는 인코딩해줘야 하므로 인코딩하면!!
'QUFBQXiSBAhA6xEI' 라는 값이 나온다.
클리어!