pushrbp brush in RTM

holyshield2025 문제를 풀면서, 얻은 지식을 정리하고자 한다.

일단, 기본적인 문제를 보자면

C
int __fastcall main(int argc, const char **argv, const char **envp)
{
  vuln();
  return 0;
}
C
int vuln()
{
  char buf[16]; // [rsp+0h] [rbp-10h] BYREF

  read(0, buf, 0x19uLL);
  return puts(buf);
}

main → vuln 함수인데 vuln 함수에서 bof가 터지는 문제이다.

이런 형태에 문제면 rbp overwrite 한다음에 스택구조를 맟추는 문제인데 자세히 보면 main함수에 추가적인 명령어가 없어서 그런 접근을 아닌것 같았다.

0x19uLL 를 쓸수 있어서 RA를 1바이트 쓸 수 있다. 따라서, RTM으로 언제든지 main함수를 다시 실행시킬 수 있는데.. 일단 문제는 main함수를 다시 실행시켜도 정보를 leak할려면 vuln의 puts 함수로 가능한데 leak을 하더라도 프로그램이 종료가 되지 않게 할려면 결국 RA 1바이트를 덮어써야해서 그전이 내가 쓴 정보로 다 덮어져서 RA주소말고는 leak을 하기가 어렵다. 이때 필요한게 RTM poprbp brush 스킬이다.

그게 뭐냐면, RTM을 해서 main 함수의 에필로그 부분으로 가면 거의 필수적으로

C
.text:000000000000119A main            proc near               ; DATA XREF: _start+18↑o
.text:000000000000119A ; __unwind {
.text:000000000000119A                 endbr64
.text:000000000000119E                 push    rbp

다음과 같이 push rbp 가 존재하게 된다. 그런데 알겠지만, 이때 rbp 레지스터 값은 우리가 vuln에서 rbp 부분을 덮어쓰면서 우리가 control이 가능하게 된다.

따라서 저 부분으로 우리가 원하는 값을 스택에 집어넣을 수 있게 된다. 그러면 무엇이 가능하냐? 최종적으로는 원래는 main함수의 엔딩 프롤로그가 끝나면

C
.text:00000000000011A7                 call    vuln
.text:00000000000011AC                 mov     eax, 0
.text:00000000000011B1                 pop     rbp
.text:00000000000011B2                 retn

프로그램이 종료가 되어서 우리가 계속 RTM으로 살리고 있었던 것인데,

poprbp로 스택을 만들어놓으면 저 main 함수의 엔딩 프롤로그가 실행이 되어서 다시 RTM을 할 수 있다. ( main 함수 RA 부분에 넣어놓으면 되니깐 ) 따라서 아까 문제였던 puts로 leak이 가능하게 된다.

이런식의 leak이 가능하고 지속적으로 main으로 돌아올 수 있다면? 이제는 사실상 끝이다. stack leak , libc leak을 할 수 있고 , 또한 이 기술로 ROP도 짤 수 있다.