system 함수의 주소 계산

강의를 공부하면서 system 함수의 주소를 계산할 때 read함수의 got를 읽음으로서 read함수의 주소를 알아내고 이 값과 libc.symbols["read"]의 차이를 구함으로서 라이브러리의 시작주소를 알아내는 것으로 이해했는데요 여기서 몇가지 궁금증이생겨 질문드립니다.

1.강의에서의 "/lib/x86_64-linux-gnu/libc-2.27.so"와 같은 라이브러리 주소나 버전은 어떻게 알아내는 건가요?
2.ELF.symbols 메소드는 라이브러리의 시작주소로 부터 함수까지의 오프셋을 알려주는 메소드인가요?
3.read함수의 got를 읽을때 puts함수가 갑자기 나오는 이유를 모르겠습니다ㅠ read함수의 got에는 read함수의 주소가 들어있는 것으로 이해했는데 puts함수에 read함수의 got를 넣어주면 read함수의 주소가 출력되는 건가요?

#pwnable
작성자 정보
답변 2
2dedce
워게임 고인물

1.강의에서의 "/lib/x86_64-linux-gnu/libc-2.27.so"와 같은 라이브러리 주소나 버전은 어떻게 알아내는 건가요?
라이브러리 버전은 문제마다 다를 수 있어서 문제에서 dockerfile을 통해 알려주거나 문제에서 libc파일을 같이 주거나, @Return Oriented Programming처럼 함께실습 워게임 문제는 강의에서 버전을 알려줍니다. libc_base(libc의 시작주소)의 특징은 계산했을 때 하위 12비트가 0입니다. 다시말하자면 16진수로 표현했을 때 마지막 3자리가 0입니다. 그래서 libc 몇개 후보를 가지고 libc_base를 계산했는데 0x00007f3758958000처럼 마지막 3자리가 000으로 깔끔하게 나오면 맞는 버전을 찾은 것입니다.
라이브러리파일의 주소는 ubuntu18.04에서 gdb에서 프로그램이 실행된 상태에서 info proc mappings 또는 i proc m을 치거나 gdb-peda에서 vmmap 또는 vm을 치면 다음처럼 뜹니다.

gdb-peda$ vmmap
Start              End                Perm      Name
0x00400000         0x00401000         r-xp      /mnt/c/files/rop
0x00600000         0x00601000         r--p      /mnt/c/files/rop
0x00601000         0x00602000         rw-p      /mnt/c/files/rop
0x00007f97da4c7000 0x00007f97da6ae000 r-xp      /lib/x86_64-linux-gnu/libc-2.27.so
0x00007f97da6ae000 0x00007f97da8ae000 ---p      /lib/x86_64-linux-gnu/libc-2.27.so
0x00007f97da8ae000 0x00007f97da8b2000 r--p      /lib/x86_64-linux-gnu/libc-2.27.so
0x00007f97da8b2000 0x00007f97da8b4000 rw-p      /lib/x86_64-linux-gnu/libc-2.27.so
0x00007f97da8b4000 0x00007f97da8b8000 rw-p      mapped
0x00007f97da8b8000 0x00007f97da8e1000 r-xp      /lib/x86_64-linux-gnu/ld-2.27.so
0x00007f97daad7000 0x00007f97daad9000 rw-p      mapped
0x00007f97daae1000 0x00007f97daae2000 r--p      /lib/x86_64-linux-gnu/ld-2.27.so
0x00007f97daae2000 0x00007f97daae3000 rw-p      /lib/x86_64-linux-gnu/ld-2.27.so
0x00007f97daae3000 0x00007f97daae4000 rw-p      mapped
0x00007ffccf15a000 0x00007ffccf17b000 rw-p      [stack]
0x00007ffccf1e2000 0x00007ffccf1e5000 r--p      [vvar]
0x00007ffccf1e5000 0x00007ffccf1e6000 r-xp      [vdso]

보면 로드된 라이브러리 주소가 /lib/x86_64-linux-gnu/libc-2.27.so인 것을 알 수 있지요. 아니면 다운로드 받아서 같은 디렉터리에 넣고 libc = ELF('./libc-2.27.so')처럼 로드해도 상관 없습니다.

2.ELF.symbols 메소드는 라이브러리의 시작주소로 부터 함수까지의 오프셋을 알려주는 메소드인가요?
시작주소로부터 함수까지의 오프셋을 알려주는 것이 맞습니다. pwntools을 다운받아 from pwn import *을 한 후 libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") 처럼 로드합니다. 그다음 libc.sym['system'] 또는 libc.symbols['system']처럼 접근하면 시작주소로부터 오프셋을 알려줍니다. 만약 libc.address = 0x00007ffff7a81000처럼 라이브러리 시작주소를 설정해주면 libc.sym['system']처럼 접근하면 오프셋이 아니라 절대주소로 알려줍니다.

3.read함수의 got를 읽을때 puts함수가 갑자기 나오는 이유를 모르겠습니다ㅠ read함수의 got에는 read함수의 주소가 들어있는 것으로 이해했는데 puts함수에 read함수의 got를 넣어주면 read함수의 주소가 출력되는 건가요?

puts함수에 인자로 read함수의 got를 넣어주면 read함수의 주소가 출력됩니다. gdb-peda에서 got를 쳐보겠습니다.

gdb-peda$ got

/rop:     file format elf64-x86-64

DYNAMIC RELOCATION RECORDS
OFFSET           TYPE              VALUE
0000000000600ff0 R_X86_64_GLOB_DAT  __libc_start_main@GLIBC_2.2.5
0000000000600ff8 R_X86_64_GLOB_DAT  __gmon_start__
0000000000601050 R_X86_64_COPY     stdout@@GLIBC_2.2.5
0000000000601060 R_X86_64_COPY     stdin@@GLIBC_2.2.5
0000000000601018 R_X86_64_JUMP_SLOT  puts@GLIBC_2.2.5
0000000000601020 R_X86_64_JUMP_SLOT  __stack_chk_fail@GLIBC_2.4
0000000000601028 R_X86_64_JUMP_SLOT  printf@GLIBC_2.2.5
0000000000601030 R_X86_64_JUMP_SLOT  read@GLIBC_2.2.5
0000000000601038 R_X86_64_JUMP_SLOT  setvbuf@GLIBC_2.2.5

read의 got는 0x0000000000601030라고 하네요. 여기에 무슨 값이 있나 봅시다.

gdb-peda$ x/gx 0x0000000000601030
0x601030:       0x00007f97da5d7020
# x/gx에서 첫 번째 x는 어떤 주소값의 내용을 보고싶다는 의미이고요
# g는 8바이트 값으로 보고싶다는 것이고, w:4바이트, h:2바이트, b:1바이트, i:명령어, s:문자열 등으로 바꿀 수 있습니다.
# 마지막 x는 값을 16진수로 출력시켜 보여줍니다

0x00007f97da5d7020가 들어있네요. 이것이 libc 안의 read함수의 주소입니다. vmmap명령어로 확인해도

gdb-peda$ vmmap 0x00007f97da5d7020
Start              End                Perm      Name
0x00007f97da4c7000 0x00007f97da6ae000 r-xp      /lib/x86_64-linux-gnu/libc-2.27.so

이 주소가 libc 안에 있는 것이라고 합니다. 무슨 명령어가 있는지 직접 봐도

gdb-peda$ x/5i 0x00007f97da5d7020
   0x7f97da5d7020 <__GI___libc_read>:   lea    rax,[rip+0x2e09b1]        # 0x7f97da8b79d8 <__libc_multiple_threads>
   0x7f97da5d7027 <__GI___libc_read+7>: mov    eax,DWORD PTR [rax]
   0x7f97da5d7029 <__GI___libc_read+9>: test   eax,eax
   0x7f97da5d702b <__GI___libc_read+11>:        jne    0x7f97da5d7040 <__GI___libc_read+32>
   0x7f97da5d702d <__GI___libc_read+13>:        xor    eax,eax

read함수의 시작부분이 맞네요. 그러므로

gdb-peda$ x/gx 0x601030
0x601030:       0x00007f97da5d7020

여기서 rdi에 0x601030를 넣고 puts 함수르 호출하면 첫번째 인자가 0x601030이므로 0x00007f97da5d7020를 출력시키겠지요. 근데 어떻게 출력되냐면요 이 값을 1바이트 단위로 보면 알 수 있습니다.

gdb-peda$ x/8bx 0x601030
0x601030:       0x20    0x70    0x5d    0xda    0x97    0x7f    0x00    0x00
gdb-peda$ x/s 0x601030
0x601030:       " p]ڗ\177"

0x20, 0x70, 0x5d, 0xda, 0x97, 0x7f를 출력한 후 널문자를 만나 출력이 끝날 겁니다. 즉 " p]ڗ\177"처럼 무의미한 문자열을 출력할텐데 이것을 읽어서 read의 주소를 알고 libc의 시작주소를 계산하는 것이지요.

2022.03.27. 12:07
리버싱하는 범재
대표 업적 없음

진짜 딱 제가 궁금해하는 부분들만 딱딱 짚어서 질문해주셨네요 답변해주신분도 정말 감사합니다!

2022.05.12. 01:10
질문에 대한 답을 알고 계신가요?
지식을 나누고 포인트를 획득해보세요.
답변하고 포인트 받기