본문 바로가기

CTF_Write_UP/HackCTF

[HackCTF] Unexploitable_3, 4

시작

안녕하세요 :D

시험도 끝나서 HackCTF만 열심히 풀고 있습니다 ㅎㅎ

웹도 건드려보고 싶은데 사지방에서 했다가 잡혀갈 것 같아서.. 포너블이나 하려구요 ㅠㅠ

오늘은 Unexploitable 시리즈만 2개 풀어봤습니다!! 풀다보니깐 하루가 다 갔네요.

구름ide에서 복사 기능이 여전히 안 돼서.. 캡처 도구로 계속 쓸게요.

시작해보죠!!

 

Write UP - Unexploitable_3

 

Unexploitable_3 문제입니다.

main()을 보니 fwrite()로 출력 후 fgets()로 사용자의 입력을 받네요. 버퍼는 16 bytes인데 256 bytes를 입력받아 BOF가 발생합니다.

함수가 두 개밖에 없어서 쉘을 따려면 fwrite()를 이용한 메모리 leak이 필요할 것 같아요.

fwrite() 함수는 4개의 인자를 받습니다.

fwrite()

첫 번째 인자 : 스트림에 쓰여질 배열
두 번째 인자 : 그 배열의 각각 원소의 크기
세 번째 인자 : 배열의 원소 수
네 번째 인자 : 스트림을 가리키는 포인터

main()함수에선 이 녀석을 fwrite(0x400780, 0x1, 0x24, stdout) 형태로 썼어요.

이를 참고해서, fgets@got를 leak 하려면 fwrite(fgets@got, 0x1, 0x6, stdout) 처럼 쓰면 되겠죠?

따라서 24 bytes의 Dummy를 채워주고 가젯을 이용하려 했으나..

csu로 넘겨서 세 번째 인자까진 채운다 해도 네 번째 인자인 rcx를 넣을 방법이 없네요 :(

 

하지만 친절하게도 gift() 함수에서 rcx 레지스터를 건드려줍니다.

rdi 안에 있는 값을 rcx로 넘겨주고 바로 ret 하는 코드를 볼 수 있어요.

main()에서 rcx에 인자를 넣을 때 어떻게 했는지 다시 한 번 볼까요?
mov rax, QWORD PTR [0x601060 (stdout)] 이후 mov rcx, rax 형태로 넣어줬습니다.

0x601060 내에 있는 값을 rax로 넘겨주고, 그 친구를 rcx에 주는 거에요.

그렇다면 pop rdi; ret; 가젯을 통해 rdi에 0x601060을 넣어주면 mov rcx, QWORD PTR [rdi] 에 의해 rcx에 올바른 stdout이 들어가겠네요.

그 이후는 csu 영역에서 fwrite(fgets@got, 0x1, 0x6, stdout) 를 맞춰주고 main()으로 복귀, 메모리 leak을 토대로 libc-database에서 검색 후 system("/bin/sh") 하면 끝입니다.

코드에요.

 

마크다운 플러그인까지 받아서 쓰다가 밍밍한 vi 파이썬 하이라이팅을 보니 좀.. 그렇네요..

 

Exploit!!

Write UP - Unexploitable_4

진짜 이 문제.. 사람을 이렇게까지 괴롭혀야 하나 생각했던 문제에요 ㅋㅋㅋㅋㅋ

 

600점짜리 문젠데 보호기법이 하나도 안 걸려 있습니다.
단순 쉘코드 문제같은데 오히려 이런 게 어렵더라구요ㅠㅠ

 

stripped binary이기 때문에 objdump로 입력받는 부분을 찾았어요.

버퍼는 16 bytes인데 44 bytes를 받아서 BOF가 발생합니다.

일단 버퍼를 고정된 곳으로 돌리는게 나중에 리턴할 때 편하기 때문에 RBP에 0x601110을 주고 0x4006db로 돌아오겠습니다.

이렇게 되면 lea rax, [rbp-0x10] 이 진행될 때 rbp가 0x601110 이므로 0x601100에 입력이 들어가게 돼요.

이제 여기에 쉘코드를 넣기만 하면 되는데..

24 bytes 쉘코드를 넣고 리턴을 이 위치로 돌렸더니 rip와 rsp가 충돌해서 쉘코드가 깨지더라구요.

리턴 후의 rsp는 0x601120이고 rip는 버퍼의 시작인 0x601100으로 옮겨지는데 쉘코드 내부에서 push 연산을 하다보면 rip와 rsp가 겹쳐서 쉘코드 내부에 push가 되는 일이 발생해요.

다른 방법으로, rsp 조작을 위해 특정 위치에 ret을 써놓고 쉘코드 + ppppr 가젯으로 접근하려 했더니 fgets() 연산 중에 스택이 무너집니다.

구글링을 통해 얻은 결론은..

Dummy + SFP + RET 뒤에 쉘코드 중 8 bytes만 입력하고, 다음 입력 때 이어서 입력받는 방법입니다.

 

첫 번째 입력에 Dummy + 0x601110 + 0x4006db를 주면 rbp가 0x601110으로 이동한 상태에서 입력을 다시 받습니다.

두 번째 입력에 Dummy + 0x601138 + 0x4006db + 쉘코드 중 8 bytes를 주면 위와 같이 들어가요.

rbp를 0x601138로 줬으니깐 다음 입력 땐 0x601128 위치부터 값이 들어갑니다. 바로 나머지 쉘코드를 붙여주면 되겠죠?

 

세 번째 입력으로 나머지 쉘코드 + \x90 + 쉘코드 시작주소 (0x601120)을 주었습니다.

아까 fgets()에 의해 스택이 무너진단 말을 했었는데, 0x601100에 있었던 dummy값이 사라진 것을 확인할 수 있어요.

이것도 rsp값이 조작되어 있기 때문에 발생하는 것을 알아냈습니다.

이제 0x601120으로 리턴하게 되면 rip가 0x601120으로 가고, rsp는 0x601148이 됩니다.

만약 쉘코드에 push 연산이 많다면 충돌이 나겠죠??

이 문제 풀면서 진짜 잘 만들었다고 느낀게, 우리가 24 bytes 쉘코드를 쓰잖아요.

쉘코드의 크기를 줄이려면 push pop 연산으로 레지스터에 집어넣게 돼요. mov를 쓴 것보다 크기가 훨씬 작아지거든요.

그래서 인터넷에 퍼져있는 쉘코드를 어셈블리어로 바꿔보면 push pop 연산이 생각보다 많습니다.

8 + 24 = 32 bytes 내에서 push pop을 거의 쓰지 않은 쉘코드를 만들어야 해요.

저는 이렇게 만들었습니다.

 

원래는 push rax 이후 “/bin//sh”를 넣어 문자열의 끝을 알려주는데
push를 최대한 안 쓰기 위해 rbx에 넣을 때 “/bin/sh”만 주고 뒤에 1 byte를 더미로 채웠어요.

그 후 더미인 [rsp + 7]를 지워주기 위해 1 byte al을 대입했습니다.

이렇게 되면 코드 내에 \x00 없이 /bin/sh\x00이 들어가게 돼요.

이걸 컴파일해서 objdump로 추출, 쉘코드로 뽑아냈습니다.

 

정리하겠습니다.

1. Dummy + 0x601110 + main_fgets 를 줘서 rbp를 0x601110으로 셋
2. Dummy + 0x601138 + main_fgets + shellcode[:8] 을 줘서 rbp를 0x601138로 셋
3. shellcode[8:] + NOP + 0x601120 을 줘서 쉘코드 완성, 시작 위치로 리턴

 

코드에요.

 

rsp 레지스터 때매 오래 삽질한 문제였습니다.

Exploit!!

마무리

HackCTF 내에 있는 Unexploitable 시리즈를 다 풀어봤습니다.

rbp 레지스터를 조작하는 것부터 시작하는 재밌는 문제들이었어요 ㅎㅎ

얻은 게 많은 문제들입니다!! 포너블 하시는 분들은 꼭 풀어보면 도움될 것 같네요.

감사합니다 :D

'CTF_Write_UP > HackCTF' 카테고리의 다른 글

[HackCTF] j0n9hyun's secret  (0) 2019.11.22
[HackCTF] babyfsb  (0) 2019.11.13
[HackCTF] You are silver  (0) 2019.11.02
[HackCTF] World Best Encryption Tool  (0) 2019.10.22
[HackCTF] Register  (0) 2019.10.15