나는 내가 본 보안 관련 비디오에 대한 메모를 작성하기 시작했다 (빠른 리콜의 방법).
이것들은 초보자에게 더 유용 할 수 있습니다.
여기서 노트 순서는 어려움이 아니라 내가 쓰는 방식에 대한 역도 순서로 진행됩니다 (즉, 최신 첫 번째).
이 작품은 Creative Commons Attribution-Noncommercial-Sharealike 4.0 International License에 따라 라이센스가 부여됩니다.
2017 년 8 월 12 일에 작성되었습니다
Gynvael의 자신감에 영향을받는 CTF 2017 Livestreams 여기 및 여기; 그리고 그의 Google CTF Quals 2017 Livestream에 의해
때로는 도전이 VM을 구현하여 복잡한 작업을 구현할 수 있습니다. VM을 완전히 리버스 엔지니어링하고 도전을 해결하기 위해 항상 노력할 필요는 없습니다. 때로는 조금 다시 살 수 있으며 일이 일어나고있는 일을 알면 VM에 연결되어 필요한 물건에 액세스 할 수 있습니다. 또한 VMS에서 타이밍 기반 측면 채널 공격이 더 쉬워집니다 (주로 실행 된 수많은 "실제" 지침으로 인해 발생합니다.
바이너리의 암호화 적으로 흥미로운 기능은 상수를 찾고 온라인으로 검색함으로써 간단하게 인식하고 빠르게 반복 할 수 있습니다. 표준 암호화 함수의 경우 이러한 상수는 함수를 빠르게 추측하기에 충분합니다. 더 간단한 암호화 기능을 훨씬 쉽게 인식 할 수 있습니다. 많은 xors와 그런 일이 일어나고 쉽게 식별 할 수있는 상수가 없다면 아마도 손으로 달린 암호화 (그리고 아마도 깨진) 일 것입니다.
때로는 육각형과 함께 IDA를 사용할 때 해체 뷰가 디 컴파일보기보다 낫습니다. 소환장에서 많은 합병증이있는 것처럼 보이지만 분해 관점에서 반복적 인 패턴을 알 수 있다면 특히 그렇습니다. (우주 막대를 사용하여 두 개를 빠르게 전환 할 수 있습니다). 예를 들어, (고정 된 크기) Big-Integer 라이브러리가 구현 된 경우, 대결보기는 끔찍하지만 분해보기는 이해하기 쉽습니다 ( adc 와 같은 반복적 인 ""지침이있는 반복적 인 "지침으로 인해 쉽게 인식 할 수 있음). 또한 이와 같은 분석 할 때 IDA의 그래프보기에 "그룹 노드"기능을 사용하면 각 노드가하는 일을 이해하는 것처럼 그래프의 복잡성을 빠르게 줄이는 데 매우 유용합니다.
이상한 아키텍처의 경우 좋은 에뮬레이터를 갖는 것이 매우 유용합니다. 특히, 메모리 덤프를 줄 수있는 에뮬레이터는 에뮬레이터에서 메모리를 얻으면 일단 메모리를 빠르게 파악하고 흥미로운 부분을 인식 할 수 있습니다. 또한 편안한 언어 (예 : Python)로 구현 된 에뮬레이터를 사용하면 원하는 방식을 정확하게 실행할 수 있습니다. 예를 들어, 코드의 흥미로운 부분이 있으면 여러 번 실행하려면 (예 : 무차별 인 또는 무언가로) 에뮬레이터를 사용하여 전체 프로그램을 실행하지 않고 코드의 일부만 수행하는 것을 신속하게 코딩 할 수 있습니다.
갈 때 게으른 것이 좋습니다. 리버 엔지니어링에 시간을 낭비하지 말고 실제로 더 어려운 재활용 작업을 수행하는 데 소요되는 시간을 줄일 수 있도록 Recon (RE Challenge!)에 충분한 시간을 보내십시오. 이러한 상황에서 정찰은 각 기능을 철저히 분석하는 데 너무 많은 시간을 소비하지 않고 다른 기능을 빠르게 살펴 보는 것입니다. 기능의 내용을 빠르게 측정하면 (예 : "암호화처럼 보인다"또는 "메모리 관리처럼 보입니다"등).
알 수없는 하드웨어 또는 아키텍처의 경우 Google에서 충분한 시간을 보내면 도구를 더 빨리 구축하는 데 도움이되는 유용한 도구 나 문서를 통해 운이 좋을 수 있습니다. 종종, 장난감 에뮬레이터 등에서 시작하는 빠른 포인트로 유용 할 수있는 장난감 에뮬레이터 등을 찾을 수 있습니다. 또는 빠른 "수정"스크립트를 작성한 다음 일반 도구를 사용하여 흥미로운 물건이 있는지 확인할 수있는 흥미로운 정보 (예 : 비트 맵이 저장되는 방법 또는 문자열이 저장되는 방법 등)를 얻을 수 있습니다.
김프 (이미지 조작 도구)는 원시 픽셀 데이터를 볼 수있는 매우 멋진 오픈/로드 기능이 있습니다. 이 바이너리 데이터에서 자산이나 반복 구조를 신속하게 찾는 데이를 사용할 수 있습니다. 더 많은 정보가 수집 될 수 있는지 확인하기 위해 설정을 엉망으로 만드는 데 시간을 보내십시오.
2017 년 7 월 2 일에 작성되었습니다
infoseciitr #bin 채팅에서 @p4n74 및 @h3rcul35와의 논의에 영향을받습니다. 우리는 때때로 초보자들이 더 큰 도전 바이너리, 특히 제거 될 때 어떻게 시작하는지에 대해 논의하고있었습니다.
RE 챌린지를 해결하거나 PWN을 할 수 있으려면 먼저 효과적으로 활용하려면 주어진 바이너리를 분석해야합니다. 이진이 제거 될 수 있으므로 ( file 사용하여 발견) 분석을 시작할 위치를 알아야합니다.
바이너리에서 취약점을 찾을 때 몇 가지 스타일의 분석이 있습니다 (그리고 내가 수집 한 것에서 다른 CTF 팀은 선호도가 다릅니다).
1.1. 전체 코드를 c
이러한 종류의 분석은 드물지만 작은 이진에 매우 유용합니다. 아이디어는 코드 전체를 리버스 엔지니어로 사용하는 것입니다. 각각의 모든 기능이 IDA (디 컴파일러보기를 사용하여)에서 열리고 (바로 가기 : N) 및 Retyping (바로 가기 : Y)을 사용하여 디 컴파일 된 코드를 훨씬 더 읽기 쉽게 만드는 데 사용됩니다. 그런 다음 모든 코드가 별도의 .c 파일로 복사/내보내므로 원본과 동등한 (동일하지는 않지만) 이진을 얻도록 컴파일 할 수 있습니다. 그런 다음 소스 코드 레벨 분석을 수행 할 수 있습니다. Vulns 등을 찾을 수 있습니다. 일단 취약점이 발견되면, IDA의 멋지게 디 컴파일 된 소스를 따라 내려서, 분해 뷰 사이를 나란히 전환하고 (공간을 사용하여 공간을 사용하여 그래프와 텍스트보기를 빠르게 전환하는 데 사용).
1.2. 결제의 최소 분석
대부분의 바이너리는 비교적 쓸모가 없기 때문에 (공격자의 관점에서) 이것은 매우 자주 이루어집니다. 의심 스럽거나 VUMN으로 이끌 수있는 기능 만 분석하면됩니다. 이렇게하려면 시작하기위한 몇 가지 방법이 있습니다.
1.2.1. 메인에서 시작하십시오
일반적으로 제거 된 바이너리의 경우 메인조차도 레이블이 지정되지 않지만 (IDA 6.9는 귀하를 위해 표시합니다), 시간이 지남에 따라 입력 지점에서 메인에 도달하는 방법 (기본적으로 열린 곳)에서 메인에 도달하는 방법을 인식하는 법을 배웁니다. 당신은 그것으로 뛰어 들어 거기에서 분석을 시작합니다.
1.2.2. 관련 문자열을 찾으십시오
때로는 출력 등이있을 수있는 특정 문자열을 알고 있습니다. 유용 할 수 있습니다 (예 : "축하합니다. 깃발은 %s입니다"). 문자열보기 (바로 가기 : Shift+F12)로 점프하고 문자열을 찾은 다음 xrefs (바로 가기 : x)를 사용하여 뒤로 작업 할 수 있습니다. XREFS를 사용하면 해당 체인의 모든 함수에서 XREFS를 사용하여 메인 (또는 알고있는 지점)에 도달 할 때까지 해당 문자열의 함수 경로를 찾을 수 있습니다.
1.2.3. 임의의 기능에서
때로는 특정 문자열이 유용하지 않을 수 있으며 메인에서 시작하고 싶지 않습니다. 대신, 전체 기능 목록을 빠르게 뒤집어 의심스러워 보이는 기능 (예 : 많은 상수 또는 많은 xors 등) 또는 중요한 기능 (Malloc, Free 등)을 호출하는 기능을 찾고, 거기서부터 시작하여 두 가지 (호출하는 기능을 따르는 기능)와 뒤로 이동합니다 (기능의 xrefs).
1.3. 순수한 분해 분석
때로는 소환 뷰를 사용할 수 없습니다 (이상한 아키텍처, 반응 방지 기술, 손으로 서면 어셈블리, 또는 불필요하게 복잡해 보이는 소환). 이 경우 분해보기를 순전히 살펴 보는 것이 완벽하게 유효합니다. 자동 주석을 켜는 것이 (새로운 아키텍처)에 매우 유용하며 각 명령어를 설명하는 의견을 보여줍니다. 또한 노드 색상화 및 그룹 노드 기능이 엄청나게 도움이됩니다. 이 중 어느 것도 사용하지 않더라도 분해에 정기적으로 댓글을 표시하면 많은 도움이됩니다. 개인적 으로이 작업을 수행하는 경우, 파이썬과 같은 의견을 적어 두는 것이 좋습니다. 따라서 Python으로 수동으로 변환 할 수 있습니다 (특히 Z3 등을 사용해야 할 RE 도전에 유용합니다).
1.4. BAP 등과 같은 플랫폼 사용
이러한 종류의 분석은 (반) 자동화되어 있으며 일반적으로 훨씬 더 큰 소프트웨어에 더 유용하며 CTF에서 거의 직접 사용되지 않습니다.
퍼징은 처음에 실제로 이해하지 않고도 VUMN에 빠르게 도달하는 효과적인 기술이 될 수 있습니다. 퍼지를 사용하면 많은 저지방 과일 스타일의 Vulns를 얻을 수 있으며, 실제 Vuln에 도달하려면 분석하고 트라이싱해야합니다. 자세한 정보는 퍼지 및 유전자 퍼징의 기본 사항에 대한 내 메모를 참조하십시오.
정적 분석을 사용하여 VUMN을 찾은 후 동적 분석을 사용하여 빠르게 악용을 구축 할 수 있습니다. 또는 VUMN 자체를 찾는 데 사용될 수 있습니다. 일반적으로 디버거 내부에서 실행 파일을 시작하고 버그를 트리거하는 코드 경로를 따라 가려고합니다. 오른쪽 위치에 중단 점을 배치하고 레지스터/힙/스택 등의 상태를 분석하면 무슨 일이 일어나고 있는지에 대한 좋은 아이디어를 얻을 수 있습니다. 디버거를 사용하여 흥미로운 기능을 신속하게 식별 할 수 있습니다. 예를 들어, 초기에 모든 기능에 임시 중단 점을 설정하여 수행 할 수 있습니다. 그런 다음 2 번의 산책을 진행합니다 - 하나는 모든 흥미로운 코드 경로를 통해; 그리고 하나는 단 하나의 흥미로운 길을 통해. 첫 번째 도보는 모든 흥미로운 기능을 여행하고 해당 브레이크 포인트를 비활성화하여 흥미로운 것들이 두 번째 보행 중에 중단 점으로 표시됩니다.
분석을위한 나의 개인적인 스타일은 일반적으로 메인 (또는 문자열에서 비 소설 기반 응용 프로그램의 경우)에서 정적 분석을 시작하고 홀수로 보이는 기능을 빠르게 찾는 것입니다. 그런 다음 여기에서 시간과 분기를 앞뒤로 내려 놓고 정기적으로 의견을 적어두고 변수를 지속적으로 이름을 바꾸고 실행하여 소환을 향상시킵니다. 다른 사람들과 마찬가지로, 나는 Apple, Banana, Carrot 등과 같은 이름을 사용하여 겉보기에 유용하지만 아직 알려지지 않은 기능/변수/등을 분석하기 쉽게 분석 할 수 있도록 (FUNC_123456 이름을 추적하는 것은 너무 어렵습니다). 또한 정기적으로 IDA의 구조보기를 사용하여 구조 (및 열거)를 정의하여 대충을 더욱 멋지게 만듭니다. VUMN을 찾으면 보통 PWNTools와 함께 스크립트를 작성하는 것으로 이동합니다 ( gdb.attach() 를 호출하는 데 사용). 이런 식으로, 나는 무슨 일이 일어나고 있는지 많은 것을 통제 할 수 있습니다. GDB 내부에서는 보통 일반 GDB를 사용하지만 필요한 경우 PEDA를 즉시로드하는 명령 peda 추가했습니다.
내 스타일은 내 도구와 제가 글을 올리기 위해 쓴 사용자 정의 도구로 더 편안해 졌기 때문에 내 스타일은 확실히 발전하고 있습니다. 다른 분석 스타일에 대해 기꺼이 듣고 기꺼이 내 스타일의 변경 사항에 대해 기꺼이 더 빨리 얻을 수 있습니다. 당신이 가진 의견/비판/칭찬은 언제나 그렇듯이 트위터 @jay_f0xtr0t에서 도달 할 수 있습니다.
2017 년 6 월 4 일에 작성되었습니다
Gynvael Coldwind 의이 멋진 라이브 스트림의 영향을 받아 ROP의 기본 사항을 논의하고 몇 가지 팁과 요령을 제공합니다.
ROP (Return Oriented Programming)는 NX (비 실행 가능 메모리) 보호를 우회하는 데 사용되는 전형적인 착취 기술 중 하나입니다. Microsoft는 NX를 DEP (데이터 실행 방지)로 통합했습니다. Linux 등조차도 효과적 이므로이 보호 기능을 사용하면 더 이상 쉘 코드를 힙/스택에 배치 할 수 없으며 점프하기 만하면 실행할 수 있습니다. 이제 코드를 실행하려면 기존 코드 (메인 바이너리 또는 라이브러리-Linux의 LIBC, LDD 등)로 이동하여 Windows의 Kernel32, NTDLL 등)로 이동합니다. ROP는 이미 존재하는이 코드의 조각을 재사용하고 그 조각을 결합하여 원하는 일을 수행하는 방법을 찾아서 존재합니다 (물론 행성을 해킹하십시오 !!!).
원래 ROP는 Ret2LiBC로 시작하여 시간이 지남에 따라 더 많은 작은 코드를 사용하여 더욱 발전했습니다. 어떤 사람들은 그것을 완화하기위한 추가적인 보호로 인해 ROP가 "죽은"상태라고 말할 수도 있지만, 많은 시나리오에서 여전히 악용 될 수 있습니다 (그리고 많은 CTF에는 반드시 필요).
ROP의 가장 중요한 부분은 가제트입니다. 가제트는 "ROP 용 사용 가능한 코드"입니다. 이는 일반적으로 ret 로 끝나는 코드 조각을 의미합니다 (그러나 다른 종류의 가제트도 유용 할 수 있습니다. 예를 들어 pop eax; jmp eax 등). 우리는이 가제트를 함께 묶어 ROP 체인 으로 알려진 착취를 형성합니다.
ROP의 가장 중요한 가정 중 하나는 스택을 제어한다는 것입니다 (즉, 스택 포인터는 제어하는 버퍼를 가리 킵니다). 이것이 사실이 아닌 경우, ROP 체인을 구축하기 전에이 제어를 얻기 위해 다른 트릭 (예 : 스택 피벗)을 적용해야합니다.
가제트를 어떻게 추출합니까? 다운로드 가능한 도구 (예 : Ropgadget) 또는 온라인 도구 (예 : Ropshell)를 사용하거나 자신의 도구를 작성하거나 (필요한 경우 특정 도전에 따라 조정할 수 있기 때문에 더 어려운 과제에 더 유용 할 수 있습니다). 기본적으로, 우리는이 가제트를 위해 점프 할 수있는 주소 만 필요합니다. 여기에서 ASLR 등에 문제가있을 수 있습니다 (즉, 실제로 ROP를 수행하기 전에 주소가 누출됩니다).
이제 우리는이 가제트를 어떻게 사용하여 로프 체인을 만들까요? 먼저 "기본 가제트"를 찾습니다. 이들은 우리를 위해 간단한 작업을 수행 할 수있는 가제트 (예 : pop ecx; ret , 가제트를 배치하여 ECX에 값을로드하는 데 사용될 수있는 다음로드 할 값을이어서 나머지 체인이 이어지고 값을로드 한 후에 반환됩니다). 가장 유용한 기본 기기는 일반적으로 "레지스터 설정", "레지스터로 가리키는 주소에서 저장 등록 값"등입니다.
우리는 이러한 원시 기능에서 구축되어 더 높은 수준의 기능을 얻을 수 있습니다 (Eveloitation Abstraction이라는 게시물과 유사). 예를 들어, Set-Register 및 Store-Value-At-Dress 가제트를 사용하여 특정 값으로 특정 주소를 설정할 수있는 "Poke"기능을 제시 할 수 있습니다. 이를 사용하여 메모리의 특정 위치에 특정 문자열을 저장할 수있는 "Poke-String"기능을 작성할 수 있습니다. 우리는 poke-string을 가지고 있기 때문에 기본적으로 거의 끝났습니다. 우리는 메모리에서 원하는 구조를 만들 수 있으며 원하는 매개 변수로 원하는 기능을 호출 할 수 있기 때문에 (우리는 등록 할 수 있고 스택에 값을 배치 할 수 있기 때문입니다).
이러한 저급 프리미티브에서 더 복잡한 일을하는 더 큰 기능으로 구축 해야하는 가장 중요한 이유 중 하나는 실수를 저지르는 기회를 줄이는 것입니다 (그렇지 않으면 ROP에서 일반적).
ROP에 대한 더 복잡한 아이디어, 기술 및 팁이 있지만 다른 시간 동안 별도의 메모를위한 주제 일 수 있습니다. :)
추신 : GYN은 반품 지향적 착취에 대한 블로그 포스트를 가지고 있으며 읽을 가치가 있습니다.
2017 년 5 월 27 일에 작성; 2017 년 5 월 29 일에 연장되었습니다
Gynvael Coldwind 의이 놀라운 라이브 스트림의 영향을 받아 그는 유전자 퍼징의 기본 이론에 대해 이야기하고 기본 유전자 퍼저를 구축하기 시작합니다. 그런 다음이 라이브 스트림에서 구현을 완료합니다.
"Advanced"퍼지 ( "퍼징의 기본 사항"노트에 설명 된 맹인 퍼저와 비교). 또한 바이트 등을 수정/돌연변이하지만 맹인 "멍청한"퍼저보다 조금 더 똑똑합니다.
유전자 퍼저가 필요한 이유는 무엇입니까?
일부 프로그램은 멍청한 퍼저를 향해 "불쾌"할 수 있습니다. 취약점은 모든 조건에 도달해야 할 수도 있기 때문입니다. 멍청한 퍼지에서, 우리는 그것이 진전을 이루고 있는지 전혀 모른다는 전혀 없기 때문에 이런 일이 발생할 가능성이 매우 낮습니다. 특정 예로, 코드가있는 if a: if b: if c: if d: crash! (Crasher 코드라고합시다),이 경우 프로그램을 충돌시키기 위해 만족하려면 4 가지 조건이 필요합니다. 그러나, 멍청한 퍼저는 4 개의 돌연변이 a , b , c , d 동시에 발생할 가능성이 매우 낮기 때문에 a 조건을 지나칠 수 없을 수 있습니다. 사실, a 진행 되더라도 다음 돌연변이는 프로그램에 대해 아무것도 알지 못하기 때문에 !a 돌아올 수 있습니다.
잠깐, 이런 종류의 "나쁜 경우"프로그램은 언제 나타 납니까?
파일 형식 파서에서 하나의 예를 가져 오는 것이 일반적입니다. 특정 코드 경로에 도달하려면 "이 값은 이것이어야하며 그 값은 그 것이어야하며 다른 값은 다른 것이되어야합니다"등의 여러 확인을 지나쳐야 할 수도 있습니다. 또한 거의 실제 소프트웨어는 "복잡하지 않은"상태가 없으며 대부분의 소프트웨어에는 많은 가능한 코드 경로가 많이 있으며, 그 중 일부는 주 내의 많은 것들이 올바르게 설정된 후에 만 액세스 할 수 있습니다. 따라서, 이러한 프로그램의 코드 경로 중 다수는 기본적으로 멍청한 퍼즐에 접근 할 수 없습니다. 또한, 때때로, 일부 경로는 충분한 돌연변이가 없었기 때문에 완전히 접근 할 수 없을 수 있습니다 (미친 듯이 불가능하지 않음). 이러한 경로 중 하나라도 버그가 있다면 멍청한 퍼즐은 결코 버그를 찾을 수 없습니다.
그렇다면 우리는 어떻게 멍청한 퍼즐보다 더 잘하는가?
위에서 언급 한 Crasher 코드의 제어 흐름 그래프 (CFG)를 고려하십시오. 우연히 바보 퍼저가 갑자기 a 얻었다면, 새로운 노드에 도달했다는 것을 인식하지 못하지만 샘플을 폐기하면서 계속 무시할 것입니다. 반면에, AFL (및 기타 유전자 또는 "스마트"퍼즐)이하는 일은 이것을 새로운 정보 ( "새로 도달 한 경로")로 인식 하고이 샘플을 새로운 초기 지점으로 보관하는 것입니다. 이것이 의미하는 바는 이제 퍼즐이 a 블록에서 시작하여 더 이동할 수 있다는 것입니다. 물론, 때로는 샘플에서 !a a 돌아갈 수 있지만 대부분의 경우에는 b 블록에 도달 할 수 없습니다. 이것은 다시 새로운 노드에 도달하므로 코퍼스에 새 샘플을 추가합니다. 이로 인해 점점 더 많은 경로를 점검 할 수 있으며 마지막으로 crash! .
이것이 왜 작동합니까?
코퍼스에 돌연변이 된 샘플을 추가하여 그래프를 더 많이 탐색함으로써 (예 : 이전에는 탐색되지 않은 부품에 도달 할 수 있음) 이전에는 도달 할 수없는 영역에 도달 할 수 있으므로 그러한 영역을 퍼지 할 수 있습니다. 우리는 그러한 영역을 퍼지 할 수 있기 때문에 해당 지역의 버그를 발견 할 수 있습니다.
유전자 퍼징이라고하는 이유는 무엇입니까?
이런 종류의 "스마트"퍼징은 일종의 유전자 알고리즘과 비슷합니다. 시편의 돌연변이 및 교차는 새로운 표본을 유발합니다. 우리는 테스트 된 조건에 더 적합한 표본을 유지합니다. 이 경우 조건은 "그래프에 몇 개의 노드가 도달 했습니까?"입니다. 더 많은 트래버스를 유지할 수 있습니다. 이것은 유전자 조류와 똑같지는 않지만 변형입니다 (우리는 탐험되지 않은 영역을 가로 지르는 모든 표본을 유지하고 크로스 오버를하지 않기 때문에). 기본적으로 기존 인구에서 선택한 후 돌연변이, 체력 테스트 (새로운 영역을 보았는지) 및 반복.
잠깐만, 우리는 단지 풀리지 않은 노드를 추적합니까?
아니요, 실제로는 아닙니다. AFL은 노드가 아닌 그래프의 에지 트래버스를 추적합니다. 또한 "Edge Travel 든 아니든"이라고 말하는 것이 아니라 가장자리가 횡단되었는지를 추적합니다. 모서리가 0, 1, 2, 4, 8, 16, ... 시간을 가로 질러 가면 "새로운 경로"로 간주되며 코퍼스에 추가됩니다. 이것은 노드가 아닌 가장자리를 보는 것이 응용 프로그램 상태를 구별하는 더 좋은 방법이기 때문에, 지수 적으로 증가하는 에지 횡단 수를 사용하면 더 많은 정보가 제공됩니다 (한 번에 가장자리가 한 번 트래버스가 두 번 트래버스와는 상당히 다르지만 트래버스 10과 너무 다르지 않음).
그렇다면 유전자 퍼저에서 무엇을 필요로합니까?
우리는 두 가지가 필요하며 첫 번째 부분을 추적자 (또는 추적 계측)라고합니다. 기본적으로 응용 프로그램에서 어떤 지침이 실행되었는지 알려줍니다. AFL은 컴파일 단계 사이를 점프하여 간단한 방식으로이를 수행합니다. 어셈블리가 생성되지만 프로그램을 조립하기 전에 기본 블록 (점프/브랜치 유형의 명령을 확인하여 엔딩을 찾아서)을 찾아서 블록/에지를 실행 된 각 블록에 (아마도 그림자 메모리 또는 무언가에) 코드를 추가합니다. 소스 코드가없는 경우 추적에 다른 기술을 사용할 수 있습니다 (예 : PIN, 디버거 등). ASAN조차도 커버리지 정보를 제공 할 수 있습니다 (이에 대한 문서 참조).
두 번째 부분의 경우 추적자가 제공 한 커버리지 정보를 사용하여 새로운 경로를 추적하고 생성 된 샘플을 향후 무작위 선택을 위해 코퍼스에 추가합니다.
트레이서를 만들기위한 여러 메커니즘이 있습니다. 소프트웨어 기반 또는 하드웨어 기반 일 수 있습니다. 하드웨어 기반의 경우 예를 들어 메모리에 버퍼가 주어지면 해당 버퍼로 통과 된 모든 기본 블록의 정보를 기록하는 일부 인텔 CPU 기능이 있습니다. 커널 기능이므로 커널은이를 지원하고 API (Linux가하는)로 제공해야합니다. 소프트웨어 기반의 경우 코드를 추가하거나 디버거 (임시 중단 점을 사용하거나 단일 스테핑을 통해)를 사용하거나 주소 소독제의 추적 능력을 사용하거나 후크 또는 에뮬레이터 또는 다른 여러 방식을 사용하여 수행 할 수 있습니다.
메커니즘을 차별화하는 또 다른 방법은 블랙 박스 추적 (수정되지 않은 바이너리 만 사용할 수있는 곳) 또는 소스 코드에 액세스 할 수있는 소프트웨어 흰색 상자 추적 (소스 코드 자체를 수정하여 추적 코드를 추가)입니다.
AFL은 컴파일 중에 소프트웨어 계측을 추적하는 방법 (또는 QEMU 에뮬레이션을 통해)을 사용합니다. Honggfuzz는 소프트웨어 및 하드웨어 기반 추적 방법을 모두 지원합니다. 다른 스마트 퍼즐은 다를 수 있습니다. Gyn이 빌드 한 것은 주소 소독제 (ASAN)에서 제공하는 추적/적용 범위를 사용합니다.
일부 퍼즐은 Forkserver 또는 다른 아이디어를 만드는 것과 같은 "Speedhacks"(즉, 퍼지 속도 증가)를 사용합니다. 어느 시점에서 이것을 조사 할 가치가있을 수 있습니다 :)
2017 년 4 월 20 일에 작성되었습니다
Gynvael Coldwind 의이 멋진 라이브 스트림의 영향을 받아 퍼지가 무엇인지에 대해 이야기하고 처음부터 기본 퍼즐을 구축합니다!
처음에 퍼저 르는 무엇입니까? 그리고 우리는 왜 그것을 사용합니까?
입력 데이터를 취하는 라이브러리/프로그램이 있다고 생각하십시오. 입력은 어떤 식 으로든 구조화 될 수 있습니다 (PDF, PNG, XML 등; "표준"형식 일 필요는 없습니다). 보안 관점에서, 입력과 프로세스 / 라이브러리 / 프로그램 사이에 보안 경계가있는 경우 흥미롭고, 우리는 해당 경계를 넘어 의도하지 않은 동작을 유발하는 "특별한 입력"을 전달할 수 있습니다. 퍼저는이 작업을 수행하는 방법 중 하나입니다. 정상적인 실행 (안전하게 처리 된 오류 포함) 또는 충돌로 이어지기 위해 입력의 "돌연변이"(손상 될 수 있음 )를 통해이를 수행합니다. 이것은 에지 케이스 로직이 잘 처리되지 않기 때문에 발생할 수 있습니다.
충돌은 오류 조건에서 가장 쉬운 방법입니다. 다른 사람들도있을 수 있습니다. 예를 들어, ASAN (주소 소독제)을 사용하면 더 많은 것을 감지 할 수 있으며 이는 보안 문제 일 수 있습니다. 예를 들어, 버퍼의 단일 바이트 오버 플로우는 자체적으로 충돌을 일으키지 않을 수 있지만 ASAN을 사용하면 퍼지로 이것을 잡을 수 있습니다.
퍼즐에 대한 또 다른 사용은 하나의 프로그램 퍼지로 생성 된 입력이 다른 라이브러리/프로그램에서도 사용될 수 있으며 차이가 있는지 확인할 수 있다는 것입니다. 예를 들어, 일부 고정밀 수학 라이브러리 오류가 다음과 같이 나타났습니다. 이것은 일반적으로 보안 문제로 이어지지 않으므로 우리는 이것에 많이 집중하지 않습니다.
퍼즐은 어떻게 작동합니까?
퍼저는 기본적으로 돌연변이-자료 반복 루프로, 응용 프로그램의 상태 공간을 탐색하여 충돌 / 보안 VUMN의 상태를 "무작위로"찾으려고 노력합니다. 그것은 악용을 찾지 못하고 vuln 만 찾습니다. 퍼지의 주요 부분은 뮤지토 그 자체입니다. 나중에 이것에 대해 더.
퍼지에서 출력?
Fuzzer에서 디버거는 (때로는) 응용 프로그램에 첨부되어 충돌로부터 어떤 종류의 보고서를 얻고 나중에 보안 VUMN 대 양성 (그러나 아마도 중요한) 충돌로 분석 할 수 있습니다.
먼저 퍼지에 가장 적합한 프로그램 영역을 결정하는 방법은 무엇입니까?
퍼지 할 때는 일반적으로 단일 조각 또는 작은 프로그램 세트에 집중하고 싶습니다. 이것은 일반적으로 수행 할 실행량을 줄이기 위해 주로 수행됩니다. 일반적으로 구문 분석 및 처리에만 집중합니다. 다시, 보안 경계는 우리에게 어떤 부분이 중요한지 결정하는 데 많은 문제가됩니다.
퍼지의 종류?
퍼저에 주어진 입력 샘플을 코퍼스 라고합니다. 올드 스쿨 퍼저 (AKA, "맹인"/"멍청한"퍼즈 저)에는 큰 코퍼스가 필요했습니다. 새로운 것들 (일명 "유전자"퍼즐, 예를 들어 AFL)은 스스로 국가를 탐험하기 때문에 반드시 큰 코퍼스가 필요하지는 않습니다.
퍼즐은 어떻게 유용합니까?
퍼즐은 주로 "낮은 교수형 과일"에 유용합니다. 복잡한 로직 버그를 찾지 못하지만 버그를 쉽게 찾을 수 있습니다 (실제로 수동 분석 중에는 때때로 놓칠 수 있습니다). 이 노트 전체에서 입력을 말할 수 있지만 일반적으로 입력 파일을 참조 할 필요는 없지만 그럴 필요는 없습니다. 퍼지는 stdin 또는 입력 파일 또는 네트워크 소켓 또는 다른 많은 사람들 일 수있는 입력을 처리 할 수 있습니다. 그래도 일반성의 손실이 너무 많지 않으면 현재 파일로 생각할 수 있습니다.
(기본) 퍼저를 쓰는 방법?
다시 말하지만, 그것은 단지 돌연변이-반복 루프 여야합니다. 대상을 자주 호출 할 수 있어야합니다 ( subprocess.Popen ). 또한 입력을 프로그램 (예 : 파일)에 전달하고 충돌 ( SIGSEGV 등이 잡힐 수있는 예외를 유발)을 감지 할 수 있어야합니다. 이제 입력 파일에 대한 뮤테를 작성하고 돌연변이 파일에서 대상을 계속 호출해야합니다.
돌연변이터? 무엇?!?
여러 가지 가능한 돌연변이 체가있을 수 있습니다. 쉽게 (즉, 구현하기 간단한)는 비트를 돌연변이하거나 바이트를 돌연변이하거나 "마법"값으로 돌연변이하는 것일 수 있습니다. 충돌 가능성을 높이려면 1 비트 또는 무언가 만 변경하는 대신 여러 가지를 변경할 수 있습니다 (아마도 일부 매개 변수화 비율). 또한 (임의의 돌연변이 대신) 바이트/단어/dwords/등을 일부 "마법"값으로 변경할 수 있습니다. 마법의 값은 0 , 0xff , 0xffff , 0xffffffff , 0x80000000 (32 비트 INT_MIN ), 0x7fffffff (32 비트 INT_MAX ) 일 수 있습니다. 기본적으로 보안 문제를 유발하는 데 공통적 인 것을 선택합니다 (일부 에지 케이스를 트리거 할 수 있기 때문에). 우리는 프로그램에 대한 더 많은 정보를 알고 있다면 더 똑똑한 뮤지 터를 쓸 수 있습니다 (예 : 문자열 기반 정수의 경우 정수 문자열을 "65536" 또는 -1 등으로 변경하는 것을 쓸 수 있습니다). 청크 기반 돌연변이터는 조각을 주위로 움직일 수 있습니다 (기본적으로 입력을 재구성). 첨가제/부속 돌연변이터도 작동합니다 (예 : 완충액에 더 큰 입력을 유발). 자리는 또한 작동 할 수 있습니다 (예 : 때로는 EOF가 잘 처리되지 않을 수도 있음). 기본적으로, 창의적으로 많은 창의적인 방법을 시도하십시오. 프로그램에 대한 경험이 많을수록 (일반적으로 착취) 더 유용한 돌연변이가 가능할 수 있습니다.
그러나 이것이 "유전 적"퍼징은 무엇입니까?
그것은 아마도 나중에 토론 일 것입니다. 그러나 현대식 (오픈 소스) 퍼저에 대한 몇 가지 링크는 AFL과 Honggfuzz입니다.
2017 년 4 월 7 일에 작성되었습니다
PICOCTF 2017의 멋진 도전에 영향을 미쳤습니다 (컨테스트가 아직 진행 중이기 때문에 챌린지의 이름을 보류했습니다)
경고 :이 메모는 일부 독자들에게는 간단하고 명백해 보일 수 있지만, 레이어링은 최근까지 나에게 명확하지 않기 때문에 말이 필요합니다.
물론 프로그래밍 할 때 우리 모두는 클래스 및 객체, 기능, 메타 기능, 다형성, 모나드, 팬츠, 또는 그 재즈 등 추상화를 사용합니다. 그러나 착취 중에 실제로 그런 일을 할 수 있습니까? 분명히, 우리는 위에서 언급 한 추상화를 구현할 때 발생하는 실수를 악용 할 수 있지만 여기서는 다른 것에 대해 이야기하고 있습니다.
여러 CTF에서 이전에 악용을 쓸 때마다 쉘을 떨어 뜨리는 임시 악용 스크립트였습니다. 나는 놀라운 pwntools를 프레임 워크 (서비스에 연결하고 사물을 변환하고 다이나넬 등)로 사용하지만 그 문제입니다. 각 악용은 임의의 코드 실행 목표를 향해 노력하는 임시 방법 인 경향이있었습니다. 그러나이 현재 도전과 "고급"형식의 문자열 익스플로잇에 대한 이전의 메모에 대한 생각은 일관된 방식으로 내 익스플로를 계층화하고 다른 추상화 레이어를 통해 필요한 목표에 도달 할 수 있다는 것을 깨달았습니다.
예를 들어, 취약점을 논리 오류로 간주하여 버퍼 후 작은 범위의 어딘가에 4 바이트를 읽고 쓰십시오. 우리는 코드 실행과 마지막으로 깃발을 얻는 데 이것을 남용하고 싶습니다.
이 시나리오에서, 나는이 추상화가 short-distance-write-anything 원시라고 생각합니다. 이것 자체로 분명히 우리는 많은 일을 할 수 없습니다. 그럼에도 불구하고, 나는 작은 파이썬 기능 vuln(offset, val) 만듭니다. 그러나 버퍼 직후에 유용 할 수있는 일부 데이터/메타 데이터가있을 수 있으므로, 우리는 이것을 남용하여 read-anywhere write-anything-anywhere 쓰기를 구축 할 수 있습니다. 즉, 이전에 정의 된 vuln() 함수를 호출하는 짧은 파이썬 기능을 작성합니다. 이 get_mem(addr) 및 set_mem(addr, val) 함수는 단순히 vuln() 함수를 사용하여 포인터를 덮어 쓰면 간단히 (이 예제에서) 간단하게 이루어지며, 이는 이진의 다른 곳에서는 피할 수 있습니다.
이제 이러한 get_mem() 및 set_mem() 추상화가 발생한 후, 기본적으로 get_mem() 에서 2 개의 주소를 누출하고 LIBC 데이터베이스와 비교하여 기본적으로 2 개의 주소를 누출하여 anti-aslr 추상화를 구축합니다 (데이터베이스를 만들기위한 @niklasb 감사합니다). 이것들의 오프셋은 나에게 libc_base 안정적으로 제공하므로 GOT의 모든 기능을 LIBC의 다른 기능으로 바꿀 수 있습니다.
이것은 본질적으로 EIP에 대한 통제권을 부여했습니다 (내가 원하는 경우 그 기능 중 하나를 "트리거"할 수있는 순간). 이제 남아있는 것은 올바른 매개 변수로 트리거를 호출하는 것입니다. 따라서 매개 변수를 별도의 추상화로 설정 한 다음 trigger() 호출하면 시스템에 쉘 액세스가 있습니다.
TL; DR : 소규모 익스플로잇 프리미티브 (전력이 너무 많지 않음)를 만들 수 있으며,이를 결합하고 더 강한 프리미티브의 계층 구조를 구축함으로써 우리는 완전한 실행을 얻을 수 있습니다.
2017 년 4 월 6 일에 작성되었습니다
Gynvael Coldwind 의이 멋진 라이브 스트림의 영향을 받아 형식 문자열 익스플로잇에 대해 이야기합니다.
간단한 형식 문자열 익스플로잇 :
%p 사용하여 스택에 무엇이 있는지 확인할 수 있습니다. 형식 문자열 자체가 스택에 있으면 스택에 주소 ( FOO )를 배치 한 다음 위치 지정자 n$ (예 : AAAA %7$p AAAA 0x41414141 , 7이 스택의 위치 인 경우)를 반환 할 수 있습니다. We can then use this to build a read-where primitive, using the %s format specifier instead (for example, AAAA %7$s would return the value at the address 0x41414141, continuing the previous example). We can also use the %n format specifier to make it into a write-what-where primitive. Usually instead, we use %hhn (a glibc extension, iirc), which lets us write one byte at a time.
We use the above primitives to initially beat ASLR (if any) and then overwrite an entry in the GOT (say exit() or fflush() or ...) to then raise it to an arbitrary-eip-control primitive, which basically gives us arbitrary-code-execution .
Possible difficulties (that make it "advanced" exploitation):
If we have partial ASLR , then we can still use format strings and beat it, but this becomes much harder if we only have one-shot exploit (ie, our exploit needs to run instantaneously, and the addresses are randomized on each run, say). The way we would beat this is to use addresses that are already in the memory, and overwrite them partially (since ASLR affects only higher order bits). This way, we can gain reliability during execution.
If we have a read only .GOT section, then the "standard" attack of overwriting the GOT will not work. In this case, we look for alternative areas that can be overwritten (preferably function pointers). Some such areas are: __malloc_hook (see man page for the same), stdin 's vtable pointer to write or flush , etc. In such a scenario, having access to the libc sources is extremely useful. As for overwriting the __malloc_hook , it works even if the application doesn't call malloc , since it is calling printf (or similar), and internally, if we pass a width specifier greater than 64k (say %70000c ), then it will call malloc, and thus whatever address was specified at the global variable __malloc_hook .
If we have our format string buffer not on the stack , then we can still gain a write-what-where primitive, though it is a little more complex. First off, we need to stop using the position specifiers n$ , since if this is used, then printf internally copies the stack (which we will be modifying as we go along). Now, we find two pointers that point ahead into the stack itself, and use those to overwrite the lower order bytes of two further ahead pointing pointers on the stack, so that they now point to x+0 and x+2 where x is some location further ahead on the stack. Using these two overwrites, we are able to completely control the 4 bytes at x , and this becomes our where in the primitive. Now we just have to ignore more positions on the format string until we come to this point, and we have a write-what-where primitive.
Written on 1st April 2017
Influenced by this amazing live stream by Gynvael Coldwind, where he explains about race conditions
If a memory region (or file or any other resource) is accessed twice with the assumption that it would remain same, but due to switching of threads, we are able to change the value, we have a race condition.
Most common kind is a TOCTTOU (Time-of-check to Time-of-use), where a variable (or file or any other resource) is first checked for some value, and if a certain condition for it passes, then it is used. In this case, we can attack it by continuously "spamming" this check in one thread, and in another thread, continuously "flipping" it so that due to randomness, we might be able to get a flip in the middle of the "window-of-opportunity" which is the (short) timeframe between the check and the use.
Usually the window-of-opportunity might be very small. We can use multiple tricks in order to increase this window of opportunity by a factor of 3x or even up to ~100x. We do this by controlling how the value is being cached, or paged. If a value (let's say a long int ) is not aligned to a cache line, then 2 cache lines might need to be accessed and this causes a delay for the same instruction to execute. Alternatively, breaking alignment on a page, (ie, placing it across a page boundary) can cause a much larger time to access. This might give us higher chance of the race condition being triggered.
Smarter ways exist to improve this race condition situation (such as clearing TLB etc, but these might not even be necessary sometimes).
Race conditions can be used, in (possibly) their extreme case, to get ring0 code execution (which is "higher than root", since it is kernel mode execution).
It is possible to find race conditions "automatically" by building tools/plugins on top of architecture emulators. For further details, http://vexillium.org/pub/005.html
Written on 31st Mar 2017
Influenced by this amazing live stream by Gynvael Coldwind, where he is experimenting on the heap
Use-after-free:
Let us say we have a bunch of pointers to a place in heap, and it is freed without making sure that all of those pointers are updated. This would leave a few dangling pointers into free'd space. This is exploitable by usually making another allocation of different type into the same region, such that you control different areas, and then you can abuse this to gain (possibly) arbitrary code execution.
Double-free:
Free up a memory region, and the free it again. If you can do this, you can take control by controlling the internal structures used by malloc. This can get complicated, compared to use-after-free, so preferably use that one if possible.
Classic buffer overflow on the heap (heap-overflow):
If you can write beyond the allocated memory, then you can start to write into the malloc's internal structures of the next malloc'd block, and by controlling what internal values get overwritten, you can usually gain a read-what-where primitive, that can usually be abused to gain higher levels of access (usually arbitrary code execution, via the GOT PLT , or __fini_array__ or similar).