Lab01-01.exe와 Lab01-01.dll 파일을 분석하여라.



1.    바이러스토탈에 파일을 업로드한 후 보고서를 보자. 기존 안티바이러스 시그니처에 일치하는 파일이 존재하는가?


-       Trojan.Agen.16384SS, Agent5.CDE, Trojan.Win32.Generic!BT 등 여러 파일 존재

 




2.    이 파일은 언제 컴파일 됐는가?


-       PE ViewIMAGE_NT_HEADERS > IMAGE_FILE_HEADER > Time Date Stamp를 확인해본 결과 2010/12/19 16: 16: 19 UTC에 컴파일 되었음을 확인 가능

 




3.    이 파일이 패킹 되거나 난독화 된 징후가 있는가? 그렇다면 무엇으로 판단했는가?


-       PEiD로 확인한 결과 패킹 되거나 난독화 된 징후는 없으며 해당 프로그램은 Microsoft Visual C ++ 6.0에서 컴파일 되었음

 




4.    임포트를 보고 악성코드 행위를 알아낼 수 있는가? 그렇다면 어떤 임포트인가?


-       LAB01-01.EXEKERNEL32.DLLMSVCRT.DLL을 임포트하고 있음. MSVCRT.DLL은 큰 의미가 없으나 KERNEL32.DLLCopyFileA, CreateFileA, CreateFileMappingA, FindFirstFileA, FindNextFileA함수를 사용 하는 것으로 보아 파일을 탐색하여 복사하는 작업을 하며 strings 함수를 통해 확인한 결과 C:\라는 문자열이 존재하는 것으로 보아 탐색 대상은 C:\으로 보임


-       LAB01-01.DLLKERNEL32.DLL, WS2_32.DLL, MSVCRT.DLL을 임포트 하고 있으나 KERNEL32.DLL, MSVCRT.DLL은 큰 의미를 갖지 않음. 다만 WS2_32.DLL은 특정 기능을 하나 Function명이 날라가 있어 Ordinal값을 통해 확인한 결과 closesocket, connect, htons, inet_addr, recv, send, shutdown, socket, WSAStartup, WSACleanup 함수를 사용 하는 것으로 보아 특정 대상과 통신하는 행위를 함을 알 수 있음




 

5.    감염된 시스템에서 검색할 수 있는 다른 파일이나 호스트 기반의 증거가 존재하는가?


-       다음은 strings LAB01-01.EXE의 실행 결과임



Kernel32.dll대신 kerne123.dll이라고 작성된 것으로 보아 정상적인 파일은 아님을 알 수 있음




 

6.    감염된 장비에서 이 악성코드를 발견하기 위해 사용한 네트워크 기반의 증거는 무엇인가?


-       Strings LAB01-01.dll의 실행결과는 다음과 같음



127.26.152.13이라는 문자열을 확인 가능한 것으로 보아 이 프로그램은 네트워크 통신을 하고 있음을 알 수 있음

 




7.    이 파일의 목적은 무엇이라고 판단했는가?


-       C:\ 밑의 파일들을 복사하여 127.26.152.13으로 전송하는 프로그램이라 판단됨

리눅스 명령어

file: 파일의 종류를 조사하는 명령어

readelf: ELF파일의 정보를 표시하는 명령어

strings: 파일 안에 들어있는 문자열을 추출하는 명령어

grep: 파일 안의 내용에서 찾고 싶은 문자열을 찾는 명령어

 

트레이서

strace: 리눅스 프로그램이 호출하는 시스템 콜을 추적한다.

ltrace: 리눅스 프로그램이 호출하는 공유 라이브러리 함수를 추적한다.

 

디버거

GDB: 리눅스의 CLI기반의 디버거

OllyDbg: 윈도우 실행파일인 PE형식의 바이너리를 분석할 수 있는 디버거

Immunity Debugger: OllyDbg와 유사한 부분이 매우 많지만 파이썬 API를 제공하며, 힙 분석에 강한 면모를 보인다.

x64dbg: 64비트 PE파일 분석도 지원하는 디버거

WinDbg: 윈도우용 디버거이다. GUI형태지만 많은 조작을 명령어로 처리한다. 커널단 디버거로 사용가능하다.

 

프로세스 메모리 에디터

CheatEngine: 윈도우에서 사용할 수 있는 프로세스 메모리 에디터 중 하나

 

역어셈블러

IDA: 고성능 역 어셈블러

objdump: 리눅스용 역 어셈블러

 

디컴파일러

Hex-Rays: IDA와 함께 사용하는 디컴파일러로 C언어형태의 의사코드로 변형해준다.

Retargetable: Retargetable 디컴파일러(http://retdec.com/)은 웹기반으로 사용할 수 있는 디컴파일러다. 실행파일을 업로드 해야한다는 단점이 있다.

ILSpy: .NET프레임워크의 CIL을 대상으로 한 오픈소스 디컴파일러다. CILC#코드로 변환할 수 있다.

dex2jar / JD: 안드로이드가 실행되는 가상머신인 달빅 VM의 바이너리 코드를 자바 바이너리 코드인 .class 파일로 변환하는 프로그램이다. 그리고 JD는 자바 바이트코드를 자바 소스코드로 변환하는 디컴파일러다.

 

Radare2

radare2는 바이너리 분석을 할 때 도움이 되는 프로그램 집합으로 다양한 도구가 포함되어 있다.

radiff2: 바이너리 차분 추출도구

rabin2: readelf-like 바이너리의 정보추출 도구

rafind2: 바이트 패턴 검색 도구

rahas2: 해시 계산도구

rarun2: 프로그램의 실행 환경을 지정해서 실행할 수 있는 도구

rasm2: 어셈블러 / 디스어셈블러

rax2: 변수 형태 변환 도구

radare2: 위의 도구를 종합한 것

라이브러리로 구현되어 있어서 도구 개발이 간단하다. GUI는 아직 지원하지 않으나 ASCII기반의 인터페이스와 웹 인터페이스가 잘 구현되어 있다. 그러나 사용법이 쉽지 않다.

 

바이너리 에디터

iBored: GUI형태의 바이너리 편집 프로그램이다. 바이너리 파일 뿐 아니라 디스크 블록 편집까지도 가능하다.

Bz: 윈도우용 바이너리 에디터로 시스템 자원을 많이 차지하지 않는다.

ghex: 리눅스에서 작동하는 GUI기반 바이너리 에디터다.

010 editor: 고성능 텍스트, 바이너리, 디스크이미지, 프로세스 에디터다. 바이너리 파일의 형식을 인식하고 분석해 헤더 필드의 값을 표시하거나 스크립트를 사용해 작업을 자동화하는 등 CTF를 진행하는데 큰 도움을 줄 수 있다.

Vim + xxd: 리눅스환경에서 바이너리를 간단하게 편집할 일이 있을때 추가 프로그램을 설치하지 않고 Vimxxd를 조합해 간단한 바이너리 편집을 할 수 있다. Vim–b옵션을 사용해 바이너리 파일을 불러오고 명령어 모드에서 :%!xxd를 입력해 xxd 명령어를 호출한다. 파일을 편집하고 :%!xxd –r을 입력해 편집한 내용을 반영하면 된다.

Easy_CrackMe

 

Reversing.kr의 첫번째 문제입니다.

파일을 다운 받아 실행하면 다음과 같은 창이 뜹니다.



 

값을 입력해서 맞지 않으면 다음과 같은 창이 뜹니다.



 

이 문제는 Password를 찾는 문제가 될듯 합니다.

 

OllydbgEasy_CrackMe를 열도록 하겠습니다.

 

Excutable modulesEasy_CrackMeEntry Point가 적혀 있네요.

Easy_CrackMeEntry Point0x00401188라고 적혀 있습니다.

 

이곳으로 이동하겠습니다.

Easy_CrackMe.text 섹션에 들어온듯 합니다.

 

우리가 하고자 하는 것은 password를 찾는 것입니다.

입력값이 password가 맞는지 검증을 통해 값이 맞다면 맞다는 출력을,

값이 틀리다면 값이 틀리다는 출력을 하겠죠.

위에서 보았듯이 값이 틀릴때는 “Incorrect Password”가 출력이 됩니다.

 

그럼 Incorrect Password 문자열 출력 전후로 해서 password를 비교하는 부분이 있을겁니다.

 

그럼 먼저 Incorrect Password를 찾아보도록 하겠습니다.

Text strings referencedIncorrect Password가 있네요.

저기로 이동해보도록 하겠습니다.

 

0x00401135부분이 Incorrect Password라는 MessageBox를 띄우는 부분으로 보입니다.

이 부분은 0x004010B5, 0x004010CD, 0x0040110B, 0x00401112에서 Jump되는데요.

즉 네부분에서 조건을 검사하고 그것이 맞지 않다면 이부분으로 넘어 오는 것으로 보입니다.

 

그럼 각 부분에 중단점을 걸고 프로그램을 실행하여 값을 입력해보겠습니다.

중단점에서 멈췄습니다.

이 윗부분에서 조건을 검사하고 조건이 맞지 않기 때문에 메시지 박스 쪽으로 넘어 가게 되겠죠.

 

이 윗부분을 확인해보도록 하겠습니다.

이 부분이 첫번째 조건 검사부분 입니다.

 

트레이스 하면서 동작을 하나씩 분석하도록 하겠습니다.

이쪽 루틴에 들어와 메모리에 공간을 100byte확보하여 줍니다.

그리고 ECX 0x18을 넣고, EAX의 값을 0으로 바꿔 줍니다.

 

그리고 0x00401094에서 스택에 0x64를 넣어주고, 0x00401096의 코드를 통해 EDI부터 EDI+24byte까지를 0으로 초기화 해줍니다.

 

그리고 0x004010AA에서 GetDlgItemText을 호출하여 입력한 값을 Buffer(0x0019F9E8)에 읽어옵니다.

https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms645489(v=vs.85).aspx

 

그리고 읽어온 값의 두번째 글자(입력값: ABCDEFGHIJKLMNOP)a인지 확인 합니다.

 

만약 입력한 값의 두번째 글자가 a가 아니라면 Incorrect Password메시지 출력부로 이동하게 됩니다.

 

입력값의 두번째 글자를 a로 바꾸고 진행하면 두번째 조건 검사부로 넘어가게 됩니다.

 

두번째 조건 검사부 입니다.

스택에 25y라는 문장이 들어있는 주소와 입력값의 세번째 글자가 들어있는 주소를 스택에 넣고 함수를 하나 호출합니다.

 

호출된 함수의 내부는 다음과 같습니다.

 

0x00401156에서 ECX2를 넣고 바로 아래에서 ECX0인지 검사해서 분기를 나눕니다. 0x00401181에서 MOV EAX, ECX하는 것으로 보아 ECX0이면 함수를 종료하는듯 합니다.

 

그리고 EBX2를 넣고, EDIESI에 입력한 문자열의 세번째 글자의 주소를 넣어줍니다.

그리고 EAX0으로 만들어 주고 REPNE SCAS BYTE PTR ES:[EDI]를 수행합니다.

REPNE : 같지 않을 때까지 반복 수행

SCAS BYTE PTR ES:[EDI] : 1ByteEDI의 값과 AX의 값을 비교

즉 같지 않을 때까지 EDI1Byte씩 늘려가며 AX와 값을 비교 진행 합니다.

 

그리고 NEG ECX를 통해 ECX의 부호를 반전시키고 EBX와 값을 더해줍니다.

그리고 EDI에 세번째 입력 글자의 메모리 주소를 넣고, ESI5y의 주소를 넣고 다시 비교합니다.

REPE CMPS BYTE PTR ES:[EDI], BYTE PTR DS:[ESI]이므로 ESIEDI의 값이 같을 때까지 반복 진행합니다.

그리고 AL5y5를 집어넣고, EDI-1과 비교합니다. 이렇게 입력값의 세번째 글자와 5와 비교하게 됩니다.

그리고 AL값이 EDI-1보다 크다면 0x0040117F, 값이 같다면 0x00401181로 이동하게 됩니다.

 

0x0040117F에서는 ECX에 보수를 취하고 0x00401181에서는 ECX의 값은 EAX에 넣어줍니다. 그리고 레지스터의 값을 복구하고 함수를 리턴해 줍니다.

 

현재 넣어준 값은 잘못된 값이므로 EAX1이 들어가고 RETN하게 됩니다.

 

그리고 EAX의 값이 0이 아니라면 다시 Incorrect Password로 넘어가게 됩니다.

 

그럼 5y와 값을 비교한다는 것을 알았으므로 다시 입력값을 수정하여 두번째 조건 검사부로 이동하겠습니다.

사용자 입력값의 세번째 글자와 5y를 비교하는 부분입니다.


0x0040116F에서 5를 비교하고 0x00401176에서 y를 비교합니다.

값이 같으므로 0x0040117B의 코드를 통해 0x00401181로 가서 EAX0을 전달하고 리턴하게 됩니다.

 

EAX의 값이 0이므로 세번째 조건 검사부로 이동하게 됩니다.

 

ESI“R3versing”의 주소값을 넣어주고, EAX에 입력값의 4번째 글자의 주소를 넣어줍니다.

그리고 거기서 한글자씩 뽑아서 비교를 하여 값이 다르다면 0x00401102로 이동합니다.

 

그리고 캐리 플래그를 포함하여 EAX에서 EAX값을 뺍니다.

그리고 EAX의 값이 0아니라면 Incorrect Password로 이동합니다.

그럼 R3versing과 비교한다는 것을 알았으므로 이를 넣어 다시 실행하도록 하겠습니다.

 

R3versing과 사용자 입력값의 세번째부터의 글자가 동일하다면 사용자 입력값의 해당 글자가 NULL인지 확인합니다.

즉 문자열의 끝인지 확인하는 것이죠, 문자열의 끝에 도달했다면 0x004010FE로 이동합니다.


그렇게 두번 검사를 진행하고 다시 0x004010DA로 이동하여 반복합니다.

 

만약 글자가 더 많다면 위의 그림처럼 되어 EAX1이 남고 다시 Incorrect Password 출력부로 이동하게 됩니다.

 

입력값을 다시 맞게 수정하여 실행하도록 하겠습니다.

정상적으로 코드를 빠져나와 XOR EAX,EAX연산을 통해 EAX0으로 만들어 줄것으로 보입니다. 그리고 JMP를 통해 SBB EAX,EAXSBB EAX,-1을 건너 뜁니다.

 

그리고 사용자 입력값의 첫번째 글자의 값이 0x45(E)인지 검사합니다.

현재 첫번째 글자의 값이 A이므로 Incorrect Password부분으로 이동하겠네요.


그럼 다시 입력값을 수정하여 실행하도록 하겠습니다.

성공적으로 문제를 해결하였습니다.

올바른 패스워드는 Ea5yReversing이었습니다.

너무 세세하게 적었나 내용이 길어졌네요

death_knight의 소스를 확인하도록 하겠습니다.



이코드는 port6666으로 설정하고 256byte의 입력을 받아 buffer에 저장합니다.

Buffer의 크기가 40byte이므로 overflow가 발생하죠.

L.O.B.서버의 6666포트로 접속 하니 문장을 하나 출력하고, 입력을 받고 종료하네요.

메모리에 로드되는 위치를 알 수 없으므로 브루트 포싱으로 ret값을 변경하며 페이로드를 전달하도록 하겠습니다.

 

페이로드의 쉘코드는 역으로 쉘을 연결하는 리버스 쉘 코드이며, peda로 얻었습니다.



192.168.0.1222222포트로 접속을 연결하는 쉘코드입니다.

 

리버스 쉘을 netcat을 통해 받아오도록 합니다.

ret를 브루트포싱하여 페이로드를 구성하고 전달하는 파이썬을 작성합니다.

 

이제 공격 하고 기다리면 됩니다.

death_knight의 쉘이 획득되었습니다.

death_knight의 패스워드는 got the life이네요.

death_knight로 접속해 보도록 하겠습니다.

들어가서 있는 파일을 출력하면 다음과 같이 마법진이 나옵니다.

이로서 LOB의 모든 문제가 다 풀렸습니다.

수고하셨습니다.

우선 xavius(자비스?!)의 코드를 확인해 보도록 하겠습니다.



코드를 보면 상당히 절망적입니다.

우선 stack의 영역을 사용할 수 없습니다.

binary의 영역 또한 사용할 수 없습니다.

라이브러리의 함수는 leaveret로 끝나므로 라이브러리의 함수 또한 사용할 수 없습니다.

stack또한 LD부터 끝까지 ret를 제외하고는 모두 초기화 해버립니다.

 

코드를 보고 조건을 확인하고 굉장히 절망적이었습니다. 입력할 수 있는 곳도 없고, 주소도 확인해서 stack이나 binarylibrary도 사용 할수 없으니 말이죠.

 

근데 코드를 보면서 의문이 든점이 있습니다.

1.    왜 갑자기 argv를 안쓰고 gets를 이용해 stdin으로부터 값을 받아올까?

2.    Library함수를 사용하지 못하게 할꺼면 단순이 0x40만 확인하면 되는것 아닌가?

 

하지만 아무리 생각해도 풀이법을 알 수 없었고, 다른사람의 풀이를 참고하고서야 깨달았습니다.

gets함수를 쓴 이유는 stdin을 사용하기 위함이었고, 0x40을 허용한건 stdin의 주소가 0x40번지에 있기 때문입니다.

 

한번 gdb로 확인해 보도록 하겠습니다.

gets의 전달인자중에 stdin에 해당하는 값이 0x08049a3c입니다.

gets가 끝난 다음에 바로 중단점을 걸고 실행해보도록 하겠습니다. Stdin의 내용을 확인해 보도록 하겠습니다.

0x40015000에 입력해준 값이 들어가 있네요.

 

이번엔 main의 끝에 중단점을 걸고 프로그램이 끝날때에도 이값이 남아 있는지 확인해 보도록 하겠습니다.

프로그램의 끝났을 때에도 값이 남아 있음을 확인할 수 있습니다.

 

그럼 eip0x40015000으로 변경하여 이 메모리 영역에 실행 권한이 있는지 확인해 보겠습니다.

segmentation fault가 발생하는 것으로 보아 실행권한 또한 있네요.

 

이제 stdin에 쉘코드를 넣고 ret0x40015000으로 바꿔주면 될듯 합니다.

이에 해당하는 페이로드는 다음과 같습니다.

NOP*19 + 쉘코드 + 0x40015000

오랜만에 보는 간단한 페이로드네요.

 

테스트 해보도록 하겠습니다.

예상대로 쉘이 획득 하였습니다. stdin의 주소는 0x40015000으로 고정인 듯 하여 다른 추가적인 작업은 필요 없네요.

 

이제 원본 파일에 시도해보도록 하겠습니다.

성공적으로 xavius의 쉘을 획득하였습니다.

xavius의 패스워드는 throw me away입니다.

우선 nightmare의 소스를 확인해 보도록 하겠습니다.

ret의 부분은 반드시 strcpy의 주소로 덮어 주어야 하고요.

스택의 ret다음 4byteAAAA로 초기화 해주는 코드가 보이네요.

 

처음에 이 문제를 어떻게 풀어야 하나 많이 고민을 많이 했습니다.

힌트에 PLT라고 적혀 있어서 strcpy를 통해 plt를 수정해서 system을 실행시켜야 하나 고민했었죠.

 

하지만 생각보다 strcpy를 이용한 문제풀이는 간단하였습니다.

 

기본 아이디어는 다음과 같습니다.

메모리에 system’s address + \x11\x11\x11\x11 + “/bin/sh”’s adress로 구성 되어 있는 데이터를 집어 넣는다.

strcpy함수를 통하여 데이터의 내용을 strcpy’s address가 들어있는 ret의 다음 4byte위치에 덮는다.

 

스택을 보며 설명해 드리도록 하겠습니다.

 

Buff’s address

RET’s address+4

AAAA

RET : strcpy’s address

NOP * 32

“/bin/sh”’s address

\x11\x11\x11\x11

System’s address

main함수가 끝나기 전에는 스택의 구성이 위와 같이 됩니다.

 

그리고 main함수가 끝난 후 RETstrcpy’s address로 인하여 strcpy가 실행되게 됩니다.

RET+4에는 RET’s address+4, RET+8에는 Buff’s address를 전달해 주었으니 strcpy(&RET+4, &Buff)가 실행되게 됩니다.

Buff의 내용이 RET+4에 덮어 씌워지게 되는 것이죠.

 

그럼 strcpy가 끝난 후 스택의 모습은 다음과 같이 될것입니다.

“/bin/sh”’s address

\x11\x11\x11\x11

System’s address

RET : strcpy’s address

NOP * 32

“/bin/sh”’s address

\x11\x11\x11\x11

System’s address

RET strcpy함수가 끝났으므로 그 위의 system함수를 실행하고 인자는 /bin/sh가 될것이고, 결과적으로 쉘이 뜰것입니다.

 

이를 위한 페이로드는 다음과 같습니다.

system’s address + 0x11111111 + “/bin/sh”’s address + NOP*32 + strcpy’s address + AAAA + (RET’s address + 4) + Buff’s address

 

그럼 우선 필요한 값들을 얻도록 하겠습니다.

strcpy의 주소는 0x08048410입니다.

system의 주소는 0x40058ae0입니다.


“/bin/sh”의 주소는 0x400fbff9입니다.

 

그리고 RET’s address + 4의 값은 ebp+8로 계산하고, Buff’s addressebp-40으로 구하면 됩니다.

 

현재까지 구한 값을 페이로드에 반영하고 디버깅 하여 ebp를 확인하여 나머지 값들도 얻도록 하겠습니다.

획득한 ebp의 값은 0xbffffab8입니다.

따라서 strcpy에서 dest이 될 주소는 0xbffffac0, src가 될 주소는 0xbffffa90이 될듯 하네요.

 

페이로드에 이를 반영하여 테스트 해보도록 하겠습니다.

역시 세그펄트가 발생했네요. 한번에 될거란 기대도 없었습니다.

 

core dump를 이용해 디버깅 하여 buff의 주소를 알아보도록 하겠습니다.

dest의 주소는 srcbuff의 주소에 0x30만 더해주면 되니깐요.

buff의 시작 주소는 0xbffffaa0이네요. 그럼 dest의 주소는 0xbffffad0입니다.

 

페이로드를 수정하여 테스트 하도록 하겠습니다.

복사본에서 쉘이 획득 되었습니다.

 

메모리의 주소값이 바뀌기 전에 어서 원본파일에 공격하도록 하겠습니다.

성공적으로 nightmare의 쉘이 획득 되었습니다.

nightmare의 패스워드는 beg for me입니다.

succubus의 소스코드를 확인하도록 하겠습니다.


이번 코드는 상당히 기네요.

우선 MO부분에 system(cmd)가 보이네요.

이번 목표는 MO를 실행해서 cmd/bin/sh를 전달하는 것으로 보입니다.

MODO부터 시작해서 GYE, GUL, YUT를 거쳐야만 실행이 되구요.

 

Main내부코드들을 보면 매개변수에 \x40가 있으면 안되구요. 처음 ret에 반드시 DO의 주소값이 들어가야 합니다.

 

그리고 ret 부터 100byte 만큼을 제외한 나머지는 0으로 초기화 해주고요.

 

그럼 우선 스택을 어떻게 구성해야하는지 한번 생각해 봐야겠습니다.

DO의 경우는 스택을 다음과 같이 구성하면 실행할 수 있습니다.

DO`s address

SFP

Buffer

main함수가 종료될때 ret를 통해 DO`s addresspop되어 eip로 들어가게 되고, espDO`s address를 가르키게 됩니다.

 

그리고 DO가 실행되고 프롤로그를 지나면 push ebp를 거쳐 스택의 내용은 다음과 같이 바뀌게 되겠죠.

SFP

SFP

Buffer

그리고 DO함수가 끝날때는 SFP위의 내용을 eip로 들어가게 됩니다.

즉 처음 스택을 구성할때 DO`s address위에 있는 값이 eip에 들어가게 되는 것이죠.


그렇다면 DO, GYE, GUL, YUT, MO를 차례대로 실행시키기 위해서는 스택을 다음과 같이 구성을 하면 됩니다.

MO’s address

YUT’s address

GUL’s address

GYE’s address

DO’s address

SFP

Buffer

각 함수가 시작되면 해당 함수의 주소가 있는 부분이 SFP가 되니 함수가 끝나게 되면 그 위의 값이 EIP에 들어가게 되는 것이죠.

이렇게 스택을 구성하게 되면 DO가 끝나고 차례로 GYE, GUL, YUT, MO가 실행 될것입니다.


그럼 우선 DO, GYE, GUL, YUT, MO의 주소값을 알아내어 입력해 주도록 하겠습니다.

DO의 주소는 0x080487ec

GYE의 주소는 0x080487bc

GUL의 주소는 0x0804878c

YUT의 주소는 0x0804875c

MO의 주소는 0x08048724입니다.

 

이를 이제 인자로 넘겨줘서 테스트 해보도록 하겠습니다.

예상대로 MO까지 실행 시켰습니다.

그럼 이제 남은것은 MO“/bin/sh”를 인자로 전달하는 것만 남았네요.

 

아까 그려본 스택을 생각해 보면 함수의 주소부분이 함수 내부에서는 SFP로 바뀐다고 했습니다. 즉 첫번째 인자를 전달하기 위해서는 함수의 주소가 있는 부분 +8byte부분에다가 데이터를 집어 넣으면 됩니다.


스택으로 구성해보면 다음과 같이 되겠죠.

“/bin/sh”’s address

Dummy(4byte)

MO’s address

YUT’s address

GUL’s address

GYE’s address

DO’s address

SFP

Buffer

그럼 페이로드는 다음과 같이 구성하면 될듯 합니다.

 

`python -c 'print "A"*44 + "\xec\x87\x04\x08" + "\xbc\x87\x04\x08" + "\x8c\x87\x04\x08" + "\x5c\x87\x04\x08" + "\x24\x87\x04\x08" + "AAAA" + /bin/sh’s address + "/bin/sh"'`

 

그럼 /bin/sh의 주소가 어딘지 확인해 보도록 하겠습니다.

0xbffffc70/bin/sh의 값이 들어가 있네요.

이를 페이로드에 반영해 테스트 해보도록 하겠습니다.


세그펄트가 발생했으므로 디버깅해 주소를 확인해 보도록 하겠습니다.


0xbffffaa8/bin/sh이 들어 있네요. 이를 반영해서 다시 테스트 해보도록 하겠습니다.

쉘이 정상적으로 떳네요.


이제 원본 파일에 테스트 해보도록 하겠습니다.

성공적으로 succubus의 쉘이 획득되었습니다.

succubus의 패스워드는 here to stay입니다.

zombile_assassin의 소스를 먼저 확인하도록 하겠습니다.

strcpy대신 strncpy를 사용해서 값을 입력하네요. 다른 방법을 이용해야 할듯 합니다.


그런데 위에 보니 FEBP라는게 적혀있네요. FEBPFake EBP의 약자입니다.

이름에서 알 수 있듯이 EBP를 조작해서 푸는 문제입니다.


Fake EBP의 공격 기법에 대하여 간단히 설명하도록 하겠습니다.

Assassin문제를 풀때 retpop이 있는 곳으로 하는것이 핵심이었다면, 이문제는 retleave가 있는 곳으로 하는것이 핵심입니다.


스택을 보며 설명을 하도록 하겠습니다.

FEBP공격을 할때 스택을 다음과 같이 구성합니다.

ret(쉘코드’s address)

FEBP

ret(leave’s address)

SFP(FEBP’s address)

Buffer

FEBP의 동작순서는 다음과 같습니다.

1.    leave(mov esp, ebp; pop ebp)수행
mov esp, ebp
를 통해 esp, ebp모두 SFP를 가르키게 됩니다.
pop ebp
를 통해 SFP의 데이터가 ebp로 들어갑니다. 이로 인하여 ebpFEBP를 가르키게 됩니다.

2.    ret(pop eip)수행
pop eip
를 통하여 eipleave명령어를 가르키게 됩니다.

3.    다시 leave수행
mov esp, ebp
를 통해 esp, ebp모두 FEBP를 가르키게 됩니다.
pop ebp
를 통해 FEBP의 데이터가 ebp로 들어갑니다. 이로 인하여 espFEBP위의 ret를 가르키게 됩니다.

4.    다시 ret수행
pop eip
를 통하여 eip는 쉘코드를 가르키게 됩니다.

 

즉, leaveret를 두번 반복 수행하여 원하는 위치의 ret를 이용해 쉘코드를 동작 시키는 것입니다.

 

그럼 예상 페이로드는 다음과 같이 됩니다.

`python –c ‘print “A”*44 + FEBP’s address + leave’s address’` `python –c ‘print “AAAA”*4 + 쉘코드’s address + NOP슬라이드 + 쉘코드

 

입력의 편의를 위하여 매개변수를 2개 전달하도록 하겠습니다. 두번째 매개변수의 내용을 첫번째에 이어서 입력해도 되고, 버퍼를 이용해도 됩니다.

 

그럼 필요한 정보들을 획득하도록 하겠습니다.

우선 leave의 주소를 획득하도록 하겠습니다.

leave의 주소는 0x080484df입니다.

 

main+3에 중단점을 걸고 작성한 페이로드를 전달해 FEBP의 주소와, 쉘코드의 주소를 얻어내도록 하겠습니다.


FEBP의 주소는 0xbffffc1c로 잡혔고, 쉘코드의 주소는 0xbffffc30으로 하면 될듯 합니다.

 

이를 반영해 페이로드를 다시 작성하여 시도해보도록 하겠습니다.

세그펄트가 떳으므로 coredump로 디버깅 하도록 하겠습니다.

 

FEBP의 주소는 0xbffffbc9, 쉘코드의 주소는 0xbffffbe0으로 하면 될듯 합니다.

 

페이로드에 이를 반영해 다시 시도하도록 하겠습니다.

쉘이 떳네요.

 

이제 원본 파일을 대상으로 공격하도록 하겠습니다.

성공적으로 zombie_assassin의 쉘이 떳습니다.

Zombie_assassin의 패스워드는 no place to hide입니다.

assassin의 소스를 보도록 하겠습니다.

소스를 보고 알수 있는 정보를 정리해 보겠습니다.

1.    매개변수의 갯수 제한이 없어졌습니다

2.    매개변수의 길이 제한이 없어졌습니다.

3.    ret에 스택 주소를 사용할 수 없습니다.

4.    ret에 라이브러리 주소를 사용할 수 없습니다.

5.    buffersfp0으로 초기화 됩니다.

 

지금까지 풀이는 stack에 쉘을 올리고 eip를 그곳으로 변경하거나, rtl을 이용하여 문제를 풀었습니다. 다만 이번에는 ret에 그 주소값을 넣을 수 없게 제한이 걸려있습니다.

 

하지만 매개변수의 길이에 제한이 없기 대문에 ret바로 위에는 아무 주소나 입력 할 수 있습니다.

바로 이것이 이번 문제를 풀기위한 방법입니다.

즉 스택에서 ret다음 있는 메모리의 데이터를 어떻게 eip에 전달하느냐 입니다.

ret에 있는 데이터가 eip로 전달 되는 과정은 다음과 같습니다.

1.    Espret가 들어 있는 메모리를 가르킨다.

2.    ret(pop eip) 명령어를 통해 ret메모리의 내용을 eip에 전달한다.

 

그럼 스택상 ret위에 있는 영역의 값을 eip에 전달 하기 위해선 esp를 한번더 위로 올려주어야 합니다.

pop을 한번 더 해주어야 하는 것이지요.

 

어렵게 생각 할 것 없이 leave다음 ret2번 수행된다고 생각하면 됩니다.

즉 스택의 ret0x0804851e를 넣어 주면 되는 것이죠. 주소가 08로 시작하기 때문에 입력 조건에 걸러지지도 않습니다.

 

Ret0x0804851e를 넣어주고 뒤이어 쉘코드의 주소와 NOP슬라이드와 쉘코드를 넣어주면 됩니다.

 

페이로드는 다음과 같이 됩니다.

`python -c 'print "A"*44 + "\x1e\x85\x04\x08" + 쉘코드주소 + "\x90"* 100 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"'`

 

쉘코드의 주소를 알아내도록 하겠습니다.

NOP슬라이드가 들어있는 영역으로 보아 0xbffffc70으로 주소를 넣어주면 될듯 합니다.

 

완성된 페이로드는 다음과 같습니다.

`python -c 'print "A"*44 + "\x1e\x85\x04\x08" + "\x70\xfc\xff\xbf" + "\x90"* 100 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"'`

 

이제 테스트를 해보도록 하겠습니다.

세그펄트가 발생하였으니 core dump를 이용해 디버깅해 보도록 하겠습니다.

 


쉘코드가 들어가 있는 주소가 바뀌어서 안되었네요.

주소를 0xbffffab0으로 변경하여 다시 시도하도록 하겠습니다.

 

쉘이 획득 되었네요. 원본 파일을 대상으로 공격하도록 하겠습니다.

 


assassin의 쉘이 획득 되었습니다.

assassin의 패스워드는 pushing me away입니다.

giant의 소스를 확인하도록 하겠습니다.

소스코드가 많이 길어졌습니다.

먼저 코드의 설명을 하도록 하겠습니다.

 

우선 24~27번까지의 코드를 이용해 libc의 메모리 주소를 획득합니다.

libc가 위치하고 있는 메모리의 주소는 0x40018000입니다.

 

그리고 29~32번까지의 코드를 통해 execve의 오프셋을 구합니다.

execve의 오프셋은 0x00091d48입니다.

 

그리고 34번의 코드를 통해 execve의 메모리상 주소를 구합니다.

0x40018000 + 0x00091d48를 통해 얻은 execve의 메모리상 주소는 0x400a9d48입니다.

 

그후 조건 검사를 통해 argv[1][44]에 오게 되는 주소의 값이 execve의 주소와 같지 않으면 프로그램을 종료합니다.

 

즉 우리는 execve함수를 이용하여 이번문제를 풀어야 합니다.


execve함수에 대한 설명은 다음과 같습니다.

첫번째 인자를 실행하며 두번째 인자는 전달해줄 매개변수 입니다. 세번째 변수는 환경변수가 들어갑니다.

 

Execve에는 exit를 전달하여 바로 종료 execve를 바로 종료하고 system(“/bin/sh”)로 쉘을 획득 할 것입니다.

이를 위한 스택 구조는 다음과 같습니다.

NULL

“/bin/sh”’s address

exit’s address

system()’s address

execve’s address

SFP

Buffer(44byte)

Execve의 값은 0x400a9d48로 구해놓았습니다.

 

나머지 값을 알아보도록 하겠습니다.

system exit함수의 주소는 디버깅을 통해 구하면 됩니다.

system()의 주소는 0x40058ae0,

exit()의 주소는 0x400391e0이네요.

 

이제 /bin/sh의 주소를 얻도록 하겠습니다.

“/bin/sh”가 들어있는 메모리 주소는 0x400fbff9입니다.

NULL은 스택의 0xbfffffffc영역에는 항상 NULL이 들어가므로 이를 이용하겠습니다.

 

다시한번 필요한 정보를 정리해보겠습니다.

execve           : 0x400a9d48

system           : 0x40058ae0

exit               : 0x400391e0

“/bin/sh”        : 0x400fbff9

NULL             : 0xbffffffc

 

이 정보를 종합하여 페이로드를 짜면 다음과 같이 됩니다.

`python –c ‘print “A”*44 + “\x48\x9d\x0a\x40” + “\xe0\x8a\x05\x40” + “\xe0\x91\x03\x40” + “\xf9\xbf\x0f\x40” + “\xfc\xff\xff\xbf”’`


이제 이를 입력하도록 하겠습니다.

이때 중요한것은 \x0a\x00으로 인식하여 뒷부분이 들어가지 않으므로 매개변수를 “”로 묶어 주어야 합니다.

giant의 쉘이 떳습니다.

giant의 패스워드는 one step closer입니다.

 

이 방법 이외에도

execve(&“/bin/sh”, &&”/bin/sh”, NULL)과 같은 입력을 하는 것으로 스택을 구성하여 푸는 방법도 있습니다.


&”/bin/sh”0x400fbff9를 입력해 주면 되고

&&”/bin/sh”는 이름이 0x400fbff9giant의 링크파일을 생성하여 그것이 메모리에 올라가게 하여 그 메모리를 이용하는 것입니다.


이경우의 페이로드는 다음과 같이 됩니다.

`python -c 'print "\xf9\xbf\x0f\x40"'` "`python -c 'print "A"*44 + "\x48\x9d\x0a\x40" + "\xe0\x91\x03\x40" + "\xf9\xbf\x0f\x40" + "\xf7\xff\xff\xbf" + "\xfc\xff\xff\xfb"'`"


그러나 저는 다음 사진 처럼 쉘이 뜨지않고 그냥 종료되어 방법을 변경하였습니다.


+ Recent posts