본문 바로가기

4-7. 2021-2 심화 스터디/CTF

[2021.11.13] dreamhack, HackCTF 풀이

dreamhack

[ Pwnable ]

  • off_by_one_000

압축 해제 후 문제 확인

주어진 파일을 실행하니 이름을 입력하라는 메시지가 출력됨, 무작위 값을 입력하니 입력한 내용을 다시 출력하고 프로그램이 자동으로 종료됨

 

off_by_one_000.c 파일을 살펴보니 아래와 같은 코드로 작성됨을 확인함

 

 

 

문제 해결을 위해서는 get_shell 함수를 실행시켜야 한다고 했음, 이를 위해 gdb에서 우선 get_shell 함수의 주소를 info function을 통해 확인함 (0x080485db)

 

다음으로 main 함수에서 cpy 함수를 호출하는 부분에 BP를 걸고, 프로그램을 실행시킴

 

dummy값을 256개를 주니 EIP까지 침범해 ret 주소가 입력한 dummy값으로 변경된 것을 확인함

 

cpy함수의 스택은 ‘dummy + 스택 베이스 값(sfp, 4 byte) + ret 주소(4 byte)’가 저장됨을 예측할 수 있음, 이 때문에 바로 위에서 dummy 값을 주었을 때 스택 베이스 값이 aaaa가 되어 함수가 정상적으로 종료될 수 없었음

 

따라서 공격을 위해서는 get_shell의 주소 4 byte64번 반복한 256 크기의 문자열을 입력 값으로 입력하면 스택 베이스 값이 get_shell의 주소로 변조될 것으로 예상함, 이에 아래와 같은 코드를 작성 후 실행함

 

flag: DH{fef043d0dbe030d01756c23b78a660ae}

 

  • off_by_one_001

Age의 값이 0이되면 get_shell이 실행될 수 있음을 확인.

이때 지역변수가 name[20], age 순서로 자원이 할당되어있다

 

따라서 스택오버플로우가 되기 위해 name에 임의의 문자열 20개를 채우고 21번째에서 버퍼가 넘치는데 이때 넘쳐 침범하는 공간이 age의 공간이다. 여기에 0을 쓰면 get_shell()으로 이동할 수 있다.

flag: DH{343bab3ef81db6f26ee5f1362942cd79}

 

  • out_of_bound

문제에서 주어진 C코드를 확인해보았다.

command에 있는 명령어들 중에서 (우리가 입력하는 인덱스)번째에 있는 문자열을 system() 함수의 인자로 전달하여 실행한다.

문제를 풀기 위해서는 name 입력란에 "/bin/sh" 문자열과 name의 시작주소를 넣고, command 인덱스로 name의 시작주소를 담고 있는 저장공간을 가리키도록 해야한다.

우선 *command 의 주소값을 알아야 한다. idx 변수에 0을 넣게 되면 0x804a060 으로 변수가 들어간다. main 함수 어셈블리어 중 0x08048737 <+108>: mov eax,DWORD PTR [eax*4+0x804a060]를 보면 인덱스가 1 증가할 때마다 주소값이 4만큼 증가함을 알 수 있다. 이를 이용해 idx 에 5 라는 숫자를 넣어 보았다.

 

0x804a074 주소값을 참조함을 알 수 있다. 0x804a074 - 0x804a060 = 24(4*4)

즉, 

0x804a060 *command[0]

0x804a064 *command[1]

0x804a068 *command[2]

0x804a06c *command[3]

0x804a070 *command[4]

0x804a074 *command[5]

... 이런식으로 주소값이 나아감을 알 수 있다. 

name변수가 들어있는 0x804a0ac 주소값을 참조해야 한다.

0xac - 0x60 = 76

76 / 4 = 19 

최종적으로 문제의 페이로드는 다음과 같이 작성했다.

 

from pwn import *

 

p = remote('host1.dreamhack.games', 14658)

# p = process('./out_of_bound')

 

name = 0x0804A0AC

p.sendlineafter("name: ", b"/bin/sh\x00" + p32(name))

p.sendlineafter("want?: ", "21")

 

p.interactive()

다음 페이로드를 실행하면 다음과 같이 FLAG 값을 얻을 수 있다.

flag: DH{2524e20ddeee45f11c8eb91804d57296}

 

  • sint

if 문을 보면 사이즈 값을 사용자로부터 받아오고  if문을 통해 크기를 제한시켜 버퍼오버플로우를 방지하고 있다. 하지만 read문을 보면 read(0,buf,size-1)이다

size가 0일때 세번째 인자는 -1 이되고 세번째인자는 unsigned를 인자로 받아들이기에 -1값은 2의 보수법으로 엄청큰값을 가져 size값이 0이 되면 bof를 실행할 수 있다.

 

 

사이즈값을 넘어서 원하는 ret 에 데이터로 덮는다.

그 후 실행하면 flag 값 획득

flag: DH{d66e84c453b960cfe37780e8ed9d70ab}

 

HackCTF

[ Pwnable ]

  • x64 Buffer Overflow

Local_118로 문자열을 읽어오는데, 여기서 버퍼오버플로우를 할 수 있는 것을 확인했다.

이때 loocal_118이 [ebp-110] 만큼 떨어져 있고 ret주소는 8바이트(SFP) 만큼 추가로 떨어져있다.

따라서 272byte+8byte 리턴주소에 다른 주소를 삽입하면 원하는 버퍼오버플로우를 일으킬 수 있다.

 

여기서 bin/bash 명령어를 일으킬 수 있는 callmeMaybe 함수를 찾았고 여기의 주소를 return 주소에 버퍼오버플로우 하면 된다.

0x00400606

다음과 같이 280의 더미값을 채우고 return 주소를 callmeMaybe의 주소로 변환.

 

flag: HackCTF{64b17_b0f_15_51mpl3_700}

 

  • x64 Simple_size_BOF

첨부되어 있는 파일을 다운로드 받으면 Simple_size_BOF파일이 생성된다.

 

리눅스에서도 다운로드 받았다.

 

 

IDA를 통해 코드를 분석해보았다. Buf:%p로 주소값을 받고 gets 함수를 이용하여 문자열을 입력받는다. gets함수는 따로 문자열의 제한이 없는 것을 볼 수 있다. 문제 제목에도 나타나있듯 BOF에 취약하다.

 

파일을 실행해보았다. Permission deny 오류가 발생하여 권한을 수정해주었다.

파일을 실행할 때마다 주소값이 변경되는 것을 확인할 수 있다. 

gets함수는 문자열 제한이 없고 char s의 버퍼가 6d30(27952)이므로 64비트 쉘코드+버퍼(더미)+ret(주소고정) 의 내용을 담아 페이로드를 작성했다. 쉘코드는 구글링을 통해 찾았다. 페이로드 내용은 다음과 같다.

 

from pwn import*

p=remote(‘ctf.j0n9hyun.xyz’, 3005)

context.log_level = ‘debug’

shell_code=\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05

p.recvuntill(‘buf: ‘)

bufadd = int(p.recvline(),16)

payload = shell_code

payload += ‘A’*(0x6D30+0x8-len(shell_code))

payload += p64(bufadd)

p.sendline(pay_load)

p.interactive()

 

위 페이로드를 실행시켜보면 다음과 같이 flag를 얻을 수 있다.

 

flag: HackCTF{s000000_5m4ll_4nd_5m4ll_51z3_b0f}

 

  • Offset

권한 변경 후 문제 확인

주어진 파일을 실행하니 어떤 함수를 실행할 것인가를 묻는 질문이 출력됨, 무작위 값을 입력하니 프로그램이 자동으로 종료됨

 

다음으로 프로그램에 걸려있는 보호기법을 gdbchecksec 명령을 통해 확인함

 

Offset 파일에는 스택 카나리 보호기법만 적용되지 않았으며, 나머지 보호기법은 적용된 것을 확인할 수 있었음

 

이후 info function 명령어를 통해 프로그램 내 함수를 살펴봄

이때, 0x00006d8에서 ‘print_flag’ 이름을 가진 함수를 참을 수 있었음, 이에 입력 값으로 해당 함수 이름을 입력했지만, 이후 인터랙션은 할 수 없었음

 

이에 ghidra를 이용해 대략적인 코드를 살펴봄

main 함수를 disassemble 하니 위와 같이 31 크기의 char형 배열을 선언하고 그 공간에 gets 함수를 이용해 사용자의 입력 값을 입력받고 있었음, 이후 select_func 함수의 인자값으로 전달함

 

다음으로 select_func 함수를 disassemble 하니 전달받은 인자값을 param_1에 저장하고 local_2e 배열에 복사한 뒤, one 문자열과 비교하여 서로 같은 문자열이라면 one 함수 포인터를 리턴하고, 아니라면 two 함수 포인터를 리턴하고 있었음

 

 

이에 함수 포인터가 리턴되는 곳에 실행하고자 하는 print_flag의 함수 포인터 값을 넣으면 해당 함수가 실행될 것으로 예상함

이를 위해서는 dummy 30개로 char형 배열을 BOF시키고, print_flag 함수의 포인터 값을 추가하기로 함

 

이를 위해 우선 dummy 값이 30개 이상 들어가면 함수 포인터에 변화가 있는지 살펴보기로 함

select_funcBP를 걸고, 프로세스 실행 후 si를 이용해 내부로 들어감

 

call eax 명령어에 실행흐름이 위치할 때 EAX 레지스터 값을 살펴보고, 해당 값이 esp 레지스터의 어느 부분에 위치하는지 확인함

 

dummy30개 이후에 eax의 값 0x56555600이 위치한 것을 알 수 있음

 

해당 부분에 print_flag의 함수 포인터를 위치시키기 위해서는 print_flag 함수의 위치도 알아내야 함, 해당 위치는 처음 함수를 확인하는 작업을 통해 그 값이 ‘0x06d8’임을 알 수 있었음

 

공격을 위해 아래와 같은 파이썬 코드를 작성하고 실행함

위와 같이 flag가 출력된 것을 확인함

 

flag: HackCTF{76155655017129668567067265451379677609132507783606}

 

  • BOF_PIE

checksec을 통해 메모리 기법을 확인하면 NX와 PIE가 걸려있는 것을 확인

→ 바이너리 주소가 상대적인 주소로 랜덤하게 매핑시키는 기법

base 주소만 바뀌므로 offset을 이용하여 푸는 것을 예상

 

파일을 실행시키면 주소가 나오고 사용자의 입력을 받은 후 nah...가 출력되는 것을 확인

 

코드를 살펴보면

 

main함수에는 welcom함수를 실행하고 nah...를 출력

welcome함수를 보면hello~~~~라는 문자와 함께 welcome주소를 출력하고 scanf를 통해 입력을 받는다.( 파일을 실행하면 welcome의 주소가 계속해서 바뀌는것을 확인, scanf는 입력값을 검증받지 않아 bof가 발생)

j0n9hyun함수에는 flag값이 있는것을 확인 함수를 실행시켜 플래그를 확인하는 것이 목표

welcome과 j0n9hyun 의 오프셋 확인

두 차이는 0x79

j0n9hyun의 주소는 welcome 함수 - 0x79 이다.

작성 후 실행

flag: HackCTF{243699563792879976364976468837}