objdump

  • 라이브러리, 컴파일된 오브젝트 모듈, 공유 오브젝트 파일, 독립 실행파일등의 바이너리 파일들의 정보를 보여주는 프로그램.
  • ELF 파일을 어셈블리어로 보여주는 역어셈블러로 사용될 수 있다.

objdump usage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-a, --archive-headers    Display archive header information
-f, --file-headers Display the contents of the overall file header
-p, --private-headers Display object format specific file header contents
-P, --private=OPT,OPT... Display object format specific contents
-h, --[section-]headers Display the contents of the section headers
-x, --all-headers Display the contents of all headers
-d, --disassemble Display assembler contents of executable sections
-D, --disassemble-all Display assembler contents of all sections
-S, --source Intermix source code with disassembly
-s, --full-contents Display the full contents of all sections requested
-g, --debugging Display debug information in object file
-e, --debugging-tags Display debug information using ctags style
-G, --stabs Display (in raw form) any STABS info in the file
-W[lLiaprmfFsoRt] or
--dwarf[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
=frames-interp,=str,=loc,=Ranges,=pubtypes,
=gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
=addr,=cu_index]
Display DWARF info in the file
-t, --syms Display the contents of the symbol table(s)
-T, --dynamic-syms Display the contents of the dynamic symbol table
-r, --reloc Display the relocation entries in the file
-R, --dynamic-reloc Display the dynamic relocation entries in the file
@<file> Read options from <file>
-v, --version Display this program's version number
-i, --info List object formats and architectures supported
-H, --help Display this information

objdump examples

-> 바이너리에서 코드 가젯을 찾고자 할 때

1
2
objdump -d [바이너리 파일] | grep "[함수 이름]" -A10
ex) objdump -d ./test_file | grep "read" -A10

readelf

  • elf 파일의 정보를 보여준다.

readelf usage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Display information about the contents of ELF format files
Options are:
-a --all Equivalent to: -h -l -S -s -r -d -V -A -I
-h --file-header Display the ELF file header
-l --program-headers Display the program headers
--segments An alias for --program-headers
-S --section-headers Display the sections header
--sections An alias for --section-headers
-g --section-groups Display the section groups
-t --section-details Display the section details
-e --headers Equivalent to: -h -l -S
-s --syms Display the symbol table
--symbols An alias for --syms
--dyn-syms Display the dynamic symbol table
-n --notes Display the core notes (if present)
-r --relocs Display the relocations (if present)
-u --unwind Display the unwind info (if present)
-d --dynamic Display the dynamic section (if present)
-V --version-info Display the version sections (if present)
-A --arch-specific Display architecture specific information (if any)
-c --archive-index Display the symbol/file index in an archive
-D --use-dynamic Use the dynamic section info when displaying symbols
-x --hex-dump=<number|name>
Dump the contents of section <number|name> as bytes
-p --string-dump=<number|name>
Dump the contents of section <number|name> as strings
-R --relocated-dump=<number|name>
Dump the contents of section <number|name> as relocated bytes
-z --decompress Decompress section before dumping it
-w[lLiaprmfFsoRt] or
--debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
=frames-interp,=str,=loc,=Ranges,=pubtypes,
=gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
=addr,=cu_index]
Display the contents of DWARF2 debug sections
--dwarf-depth=N Do not display DIEs at depth N or greater
--dwarf-start=N Display DIEs starting with N, at the same depth
or deeper
-I --histogram Display histogram of bucket list lengths
-W --wide Allow output width to exceed 80 characters
@<file> Read options from <file>
-H --help Display this information
-v --version Display the version number of readelf

readelf examples

-> 특정 함수의 offset 값을 알고 싶을 때

1
2
readelf -a [ELF 파일 이름] | grep "[함수 이름]"
readelf -a /lib/x86_64-linux-gnu/libc.so.6 | grep "write"

pwntools

모듈 포함

1
from pwn import *

문제에 접속

-> process

  • 로컬 프로세스를 이용해 통신하기 때문에 익스플로잇을 로컬 바이너리를 대상으로 할 때 사용한다.
  • 보통 익스플로잇을 테스트하고 디버깅을 하기 위해서 사용한다.
  • 환경 변수를 직접 설정할 수 있고, 인자를 전달해야 하는 경우 인자를 전달할 수 있다.
1
2
3
4
from pwn import *

p = process('[바이너리 파일 경로 및 이름]')
ex) p = process('./test_file')

-> remote

  • 원격 서버를 대상으로 할 때 사용한다.
  • 대상 서버를 실제로 공격하기 위해 사용한다.
1
2
3
4
from pwn import *

p = remote('[host]', [port 번호])
ex) p = remote('testsite.com', 12412)

-> ssh

  • 원격 서버에 ssh로 접속한다.
1
2
3
4
from pwn import *

p = ssh(user='[user 이름]', host='[host]', port='[port 번호]', password='[패스워드]')
ex) p = ssh(user='slay', host='testsite.com', port=22, password='1234')

데이터 전송 및 수신

-> send

  • 데이터를 프로세스에 전송하기 위해 사용한다.
1
2
3
4
5
6
7
8
from pwn import *

p = process('./test')

p.send('a') # ./test에 'a'를 입력한다.
p.sendline('A') # ./test에 'A'+'\n'을 입력
p.sendafter('hello','A') # ./test가 'hello'를 출력하면, 'A'를 입력
p.sendlineafter('hello','A') # ./test가 'hello'를 출력하면, 'A' + '\n'을 입력

-> recv

  • 프로세스로부터 데이터를 받기 위해 사용한다.
  • recv는 최대 n byte까지 받는다는 의미로, 최대치만큼 데이터를 받지 못해도 된다.
  • recvn은 정확히 n byte의 데이터를 받는다는 의미로, n byte만큼 받지 못하면 계속 기다린다.
1
2
3
4
5
6
7
8
9
from pwn import *

p = process('./test')

data = p.recv(1024) # p가 출력하는 데이터를 최대 1024바이트까지 받아서 data에 저장
data = p.recvline() # p가 출력하는 데이터를 개행문자를 만날 때까지 받아서 data에 저장
data = p.recvn(5) # p가 출력하는 데이터를 5바이트만 받아서 data에 저장
data = p.recvuntil('hello') # p가 출력하는 데이터를 'hello'가 출력될 때까지 받아서 data에 저장
data = p.recvall() # p가 출력하는 데이터를 프로세스가 종료될 받아서 data에 저장

패킹과 언패킹

  • 패킹 : 어떠한 숫자로 된 정수 값을 리틀 엔디언 방식의 byte 배열인 문자열로 변경한다.
  • 언패킹 : 리틀 엔디언 방식의 byte 배열인 문자여을 어떠한 숫자로 된 정수 값으로 변경한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/python3
from pwn import *

# 16진수를 리틀엔디언으로 변환
s32 = 0x41424344
s64 = 0x4142434445464748
# p8, p16, p32, p64 : 4byte, 8byte 값을 정수값 -> 바이트 배열 문자열로 바꾼다.
# ex) 0x41 -> 'a'
print(p32(s32))
print(p64(s64))

# u8, u16, u32, u64 : 4byte, 8byte 값을 바이트 배열 문자열 -> 정수값으로 바꾼다.
# ex) 'a' -> 0x41
s32 = "ABCD"
s64 = "ABCDEFGH"
print(hex(u32(s32)))
print(hex(u64(s64)))
1
2
3
4
b'DCBA'
b'HGFEDCBA'
0x44434241
0x4847464544434241

interactive

  • 셸을 획득했거나,익스플로잇의 특정 상황에 직접 입력을 주면서 출력을 확인하고 싶을 때 사용하는 함수이다.
  • 호출하고 나면 터미널로 프로세스에 데이터를 입력하고, 프로세스의 출력을 확인할 수 있다.
1
2
3
4
from pwn import *

p = process('./test')
p.interactive()

ELF

  • ELF 헤더에는 익스플로잇에 사용될 수 있는 각종 정보들이 기록되어 있다.
  • 익스플로잇 코드를 작성할 때 함수 주소나 문자열 주소 등과 같은 정보들을 가져올 수 있다.
  • ex) plt, got, ELF 파일에 있는 문자열의 주소, section의 주소 등
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *

p = process('./test')
elf = ELF('./test')
libc = ELF('./libc.so.6')

print(p32(elf.plt['read'])) # read 함수의 plt 주소를 출력

print(p32(elf.got['read'])) # read 함수의 got 주소를 출력

print(libc.symbols['read'])) # 메모리에 올려진 라이브러리 파일의 시작 주소로부터 떨어진 read() 함수의 offset 값을 출력

print(hex(next(elf.search("/bin/sh")))) # test 바이너리 파일 안에 있는 "/bin/sh" 문자열의 주소를 출력

print(hex(elf.get_section_by_name('.bss').header.sh_addr)) # test 바이너리 파일에 존재하는 .bss 섹션의 주소를 출력

print `elf.read(elf.symbols['main'], 4)` # 원하는 바이너리 주소에서 데이터를 읽어온다.

# 원하는 바이너리 주소에 데이터를 쓰고, 읽어온다.
elf.write(0x400000, "!!!")
print `elf.read(0x400000, 4)`

context.log

  • 익스플로잇에 버그가 발생하면 익스플로잇도 디버깅을 해야 하는데, pwntools에는 디버그의 편의를 돕는 로깅 기능이 있으며, 로그 레벨은 context.log_level 변수로 조절할 수 있다.
1
2
3
4
5
6
7
from pwn import *

context.log_level = 'error' # 에러만 출력
context.log_level = 'debug' # 대상 프로세스와 익스플로잇간에 오가는 모든 데이터를 화면에 출력
context.log_level = 'info' # 비교적 중요한 정보들만 출력

log.info()

context.arch

  • pwntools는 셸코드를 생성하거나, 코드를 어셈블, 디스어셈블하는 기능 등을 가지고 있는데, 이들은 공격 대상의 아키텍처에 영향을 받기 때문에 pwntools는 다키텍처 정보를 프로그래머가 지정할 수 있게 하며, 이 값에 따라 몇몇 함수들의 동작이 달라진다.
1
2
3
4
5
from pwn import *

context.arch = "amd64" # x86-64 아키텍처
context.arch = "i386" # x86 아키텍처
context.arch = "arm" # arm 아키텍처

shellcraft

  • x86-64를 대상으로 생성할 수 있는 여러 종류의 셸 코드 : https://docs.pwntools.com/en/stable/shellcraft/amd64.html
    1
    pwntools에는 자주 사용되는 셸 코드들이 저장되어 있어 공격에 필요한 셸 코드를 쉽게 꺼내 쓸 수 있게 해주는데, 정적으로 생성된 셰이라 셸 코드가 실행될 때의 메모리 상태를 반영하지 못하고, 프로그램에 따라 입력할 수 있는 셸 코드의 길이나, 구성 가능한 문자의 종류에 제한이 있을 수 있는데 이런 조건들도 반영하기가 어렵기 때문에 직접 셸 코드를 작성하는 것이 좋다.
1
2
3
4
5
6
#!/usr/bin/python3
#Name: shellcraft.py
from pwn import *
context.arch = 'amd64' # 대상 아키텍처 x86-64
code = shellcraft.sh() # 셸을 실행하는 셸 코드
print(code)
1
2
3
4
5
6
/* execve(path='/bin///sh', argv=['sh'], envp=0) */
/* push b'/bin///sh\x00' */
push 0x68
mov rax, 0x732f2f2f6e69622f
...
syscall

asm

  • pwntools는 어셈블 기능을 제공하는데, 이 기능도 대상 아키텍처가 중요하므로 미리 아키텍처를 지정해야 한다.
    1
    2
    3
    4
    5
    6
    7
    #!/usr/bin/python3
    #Name: asm.py
    from pwn import *
    context.arch = 'amd64' # 익스플로잇 대상 아키텍처 'x86-64'
    code = shellcraft.sh() # 셸을 실행하는 셸 코드
    code = asm(code) # 셸 코드를 기계어로 어셈블
    print(code)
    1
    b'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05'