UART 에 대해 간단히 알아보았으므로 다음은 SWD 포트를 이용한 완전한 동적 디버깅을 실습해보겠다.

재미있는 예제를 위해 Login 과정을 직접 만들어서 보드에 올리고 gdb 를 통해 인증을 우회하는 방법을 실습해보려한다.

가장 유명하고 실제로 자주 사용되는 ST사의 STM32F103C8T6 (Black Pill) 보드를 대상으로 한다.

인증과정을 직접 만들어보아야 원리를 더 잘 알수있는 것이기때문에 실제와 가깝게 해보기위해 IDE 를 사용하였다.

(개발과정은 아두이노IDE 에 STM32보드 를 추가 하는방법도 있으니 아두이노 IDE 가 편하다면 그 방법으로 하여도 된다.)

 

실습을 요약해보면 다음과 같은 구조로 진행된다.

 

준비물

  • STM32F103C8T6 (BLACK PILL) 개발보드, bread board
  • ST-LINK V2 (STM32보드의 SWD포트 연결)
  • FT232RL 호환 USB-TO-TTL
  • PC에 STM32CubeIDE, OpenOCD, gdbmultiarch 설치
  • 지식 : STM32CubeIDE 기본적인 사용법

기본 연결 구조

  • 입출력 (UART) : STM32 보드의 SWD <-> FT232RL <-> PC usb허브
  • 디버깅 (SWD) : STM32 보드의 SWD <-> ST-LINK V2 <-> PC usb허브 -> OpenOCD (on-chip-debugger), GDBmultiarch
  • LOGIN 인증 프로그래밍 : STM32CUBEIDE ( Arduino 로 하는 방법도 가능 )

 

과정

  • 개발의 과정) STM32CubeIDE 로 Login 구현하기
  • 해킹의 과정) OpenOCD 설치, 펌웨어 다운로드, gdb 로 로그인 우회

 먼저 UART 입출력을 위한 FT232RL과 디버깅지원을 위한 ST사의 ST-LINK 를 연결하였다.

SWD의 핀은 (3.3V GND IO CLK) 4가지를 각각 같은곳으로 연결하면 되므로 어렵지않다.

STM32F103C8T8 - ST-LINK V2 / FT232RL 을 PC 에 연결한 모습

다음 STM32CubeIDE 를 설치하고 그림과 같이 핀을 세팅한다. 코딩을 하는 부분 외에 칩셋을 세팅하는 화면을 Device Control Tool 이라고 하는데, 나는 아래 목록을 세팅하였다. 이부분은 개발과정에 해당되므로 자세한 사항은 유투브나 다른블로그를 참조하면 좋을것이다. 

  • Pinout View 에서 PB12 -> GPIO_OUTPUT ( LED 점등을 위한 핀 )
  • RCC 에서 HSE -> Crystal/Ceramic ... ( 클럭을 위한것, 옵션 )
  • SYS 에서 Debug -> Serial Wire ( 디버깅 지원을 위해 필수 )
  • USART1 에서 Mode -> Asynchronous ( UART 를 위한 필수, Flow Control 은 하지않음 )
  • Project Manager 에서 Generate peripheral initialization 체크 ( 소스코드를 위한것, 옵션 )

Device Control Tool 세팅한 화면

저장하면 MX_GPIO_Init(); MX_USART1_UART_Init(); 와 같은 초기화 코드들이 자동으로 추가되고 프로그래밍을 하기위한 준비가 될것이다. main.c 내부에 while 구문을 다음과 같이 프로그래밍 하였다.

UART 통신을 위한 함수는 HAL_UART_Transmit / HAL_UART_Receive 이고

LED 점등을 위한 함수는 HAL_GPIO_TogglePin 이다.

while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  uint8_t rc = 0;
	  static uint8_t i = 0;

	  // 처음 실행할 경우 Login 메세지 출력.
	  if (login_msg == 1) {
		  HAL_UART_Transmit(&huart1, (uint8_t*)"\rLogin : ", 9, 1000);
		  login_msg = 0;
	  }

	  // UART 포트로 한문자씩 RX 전송받아 TX 로 출력하며 LED 를 깜빡인다.
	  if(HAL_UART_Receive(&huart1, (uint8_t *)&rc, 1, 1000)==HAL_OK) {
		  HAL_UART_Transmit(&huart1, (uint8_t *)&rc, 1, 1000);
		  HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
		  HAL_Delay(100);
		  HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
	  }

	  // 전송한 문자열에 리턴값을 포함할때까지 버퍼에 문자열을 저장한다.
	  // 리턴값이 들어오면 널문자를 추가하고 문자열을 패스워드와 비교한다.
	  if (rc != 0) {
		  if(rc != '\r' && rc != '\n') {
			  buf_passwd[i++] = rc;
			  if(i >= 32) i = 32 - 1;
		  }
		  else {
			  HAL_UART_Transmit(&huart1, (uint8_t *)"\n", 1, 1000);
			  buf_passwd[i] = '\0';

			  if (strcmp((const char *)buf_passwd, "hardwarehacker") == 0) {
				  HAL_UART_Transmit(&huart1, (uint8_t *)"Access Granted!\n", 16, 1000);
				  break;
			  } else {
				  HAL_UART_Transmit(&huart1, (uint8_t *)"Access Denied.\n", 15, 1000);
			  }

			  i = 0;
			  login_msg = 1;
		  }
	  }
  /* USER CODE END 3 */
  }

이 코드가 하는일은 간단하다. login_msg 가 1일때 "Login : " 을 띄운후,

rc 에 한 문자열씩 입력될 때마다 LED 를 깜빡이고, 그 문자열이 \n 또는 \r 이 아닐때 버퍼에 저장한다.

엔터를 입력하면 그 값을 strcmp 로 패스워드와 비교하여 일치하는지에따라 결과를 출력하는 것이다 :)

인증프로그램을 올린후 실행한 화면

컴파일하여 올린후 Workspace 에 가보면 다음과같은 bin, elf 바이너리가 생성되어있다.

Workspace 디렉토리

OpenOCD 를 설치하고 ( 이부분은 생략하였으나 다른 자료를 참고 ) OpenOCD 서버를 실행하면 telnet 과 gdb 를 사용할 수 있는 상태가 된다. 먼저 telnet 을 실행하여 보드의 초기화와 halt 하고, 펌웨어를 덤프 할 수 있다.

디버깅중 실수하여 원하는 breakpoint 지점으로 다시 새롭게 시작하려면 다시 reset init - halt 이후 다시 continue 하면 될 것이다.

지금은 예제이므로 실행파일을 SYMBOL 을 가져다 쓰기위해 .ELF 파일을 GDB 에 로드할 것이지만 실제로는 여러 함수의 심볼을 보기는 어려울 것이다.

보드 HALT 명령어와 펌웨어 덤프

gdb 서버를 통해 halt 되어있는 보드에 디버거를 연결한다.

workspace dir $ gdb-multiarch -q --eval-command="target remote localhost 3333: simple-login.elf

main() 에 breakpoint 를 걸고 10개의 명령어씩을 확인하도록 display 설정을 해주었다.

이후 함수들의 심볼을 확인해보았다. 대부분의 HAL 관련함수, strcmp, standard C 함수들도 보인다.

main() 과 HAL 함수들
strcmp() 가 호출됨을 확인

 

 문자열을 비교하는것이므로 strcmp 를 타겟으로 하면 좋을것이다. continue 한후 UART 가 연결된 터미널에 test 를 입력한 후 continue 해보았더니 strcmp 에 breakpoint 가 걸렸고, 함수 인자를 확인해 보기위해 r0, r1 을 확인해보았다. (arm 프로세서는 인자들을 보통 r1-r4 레지스터에 저장한다. )

간단한 리버싱 실습과정

 의외로 쉽게 패스워드를 확인 할 수 있었고, finish 를 통해 빠져나와보면 r0 와 0 을 비교하여 점프하는 구문이 있는데, reversing 에 조금만 익숙하다면 MOV, CMP, BNE 이 세가지 조합은 위에서 코딩한 if (strcmp(arg1, arg2) ... 와 비슷한 점프 구문임을 예상할 수 있을 것이다.

임의로 입력한 패스워드로 우회한 모습

 

strcmp 는 두 문자열이 같을때 0을 리턴하므로 r0 레지스터에 0을 넣고 continue 하면 성공! 틀린패스워드이지만 통과되었다.

 


직접 만들어보니 많은 공부가 된 것 같다. 개인적인 기록이고 문자로 모든부분을 남겨둘 수 없어 OpenOCD 설치나 보드의 여러 환경 세팅과정의 많은 부분을 생략하였다. 직접 똑같이 따라하기 어려울 수 있으나, 디테일한 내용은 쉽게 검색할 수 있으니 참고하면 될 것이다. 세팅과정은 다음에 따로 정리 하려고 한다.

 


참고목록)

http://slemi.info/2018/08/17/black-pill-blinky/

https://blog.naver.com/crucian2k3/222105552572

https://www.youtube.com/playlist?list=PLUaCOzp6U-RqMo-QEJQOkVOl1Us8BNgXk

https://www.amazon.com/Practical-IoT-Hacking-Fotios-Chantzis/dp/1718500904/ref=sr_1_1?dchild=1&keywords=iot+hacking&qid=1627158209&sr=8-1 

 

+ Recent posts