이번 섹션에서는 단일 null 바이트의 과다 사용으로 인한 피해에 대해 배워볼 것입니다
독약 혹은 독약 null 바이트라고도 불리죠
이 용어는 1998년에 올라프 커치가 만들어졌습니다 스택의 단일 바이트 오버플로를 묘사하는 데 사용했죠
안드로이드 스택 2014년에 검색되었습니다
구글 프로젝트 제로 소속의 크리스 에번스가 '포이즌드 Null: 2014 버전'이라는 블로그 글을 올렸습니다
글립코스터 힙에서 하나의 null 오버플로를 루트 셸에 활용했다고 설명하고 있습니다
강의 자료에 그 블로그 포스트를 링크해뒀으니 확인해보세요
네 독극물을 이용하는 두 가지 방법을 배울 거예요
널 바이트를 이용해서
다음 과제에 어떤 걸 쓸지 결정하세요 바이너리요
독극물인 널바이트 모험을 에인헤랴르 가문이라는 기술을 배우면서 시작합니다 네 이 기술의 기원은 좀 불분명하지만
제 생각에는 크리스 에번스의
공입니다 pkexec 공격에 이 기술을 사용했거든요
하지만 2년 후 2016년에 도쿄 서부 사의 ctf 팀 멤버이자 펜서터인 마츠쿠마 히로키가
에인헤랴르 기법을 설명한 슬라이드를 발표했는데 하나의 널 오버플로를 이용해 큰
규모의 필드로 옮겼습니다 크리스 에번스의 공격과 매우 흡사하죠
네 제가 현학적인 걸 좋아한다면 솔라 디자이너의 공이라고 하겠어요 왜냐하면 보시게 될 텐데 이
기술은 세이프 언링크에 대한 새로운 접근법일 뿐이니까요 ?
네 어떻게 생각하든 에인헤랴르 디렉터리를 검색하면 이 기술을 배울 수 있을 거예요
, 이진법이 나올 거예요
늘 하던 대로 바이너리가 어떤 보호를 제공했는지부터 확인하세요
확인했습니다 체세크를 보면 에인헤야르 쌍성은 보호 장치가 돼 있어요 렐로의 카나리아와
NX를 전부 검사했어요
일반적인 이유로 비직립적이고 글립코 버전 2와 연결되어 있습니다 28 컴파일드
GDB에서 실행해 이진법이 어떻게 작동하는지 보죠
네 바로요 사용자 이름을 묻네요 "username" 필드가 뭐에 사용될지 짐작하실
겁니다 이제
가공할 걸 입력하죠
메뉴를 인쇄하기 전에 새는 곳이 있다는 말을 듣습니다 사용자 이름을 입력하라는 요청을 받고 유출이
일어난 이유는 추후에 밝혀질 겁니다
프로그램 메뉴를 보면 옵션 1에서 3개의 덩어리를 요청할 수 있을 것 같습니다
네 크기는 패스트빈만 빼고 작게만 할 수 있으니까 0x90 사이즈로 몇
개 부탁드릴게요
옵션 2는 인덱스로 이 덩어리들을 자유롭게 할 수 있습니다
옵션 3으로 데이터를 입력할 수 있습니다
네 물론 옵션 4는 target을 출력합니다 "user"라는 이름의 스트럭트의 일부로 이 프로그램의
데이터 섹션에 있는 것으로 사용자 이름과 대상 데이터 모두를 갖고 있죠 안 됩니다,
네 네 GDB를 ctrl+c로 깨고 "user"를 분석해 "ptype"와 덤프 사분자를 보면 가짜 청크 메타데이터를
위조할 때 익숙한 레이아웃이 드러납니다
우리가 통제하는 데이터 이 경우엔 우리 사용자 이름 그다음은 민감한
데이터 이 경우엔 대상 x들이죠 ,
네 기본 하중을 분석해 보니 '시각'으로 계산한 결과 0x90 크기의 토막 두 개가 나왔어요
하나는 정리가 안 된 채 분리됐고 다른 하나는 제가 작성한 데이터가 들어
있어요
관례에 따라 시간을 드릴 테니 에인헤야르 바이너리 집에 대해 잘 숙지해
두시고 물론 버그를 찾으세요 그동안 영상을 정지했다가 준비되면 다시 시작해요
네 바라건대 이 강의를 시작하면서 단일 null 오버플로를 확인하셨으면
좋겠습니다 바로 그 버그니까요
에인헤르야르 바이너리를 GDB에 입력해서 실행할게요
한 덩어리를 요청해서 가비지 데이터로 채울 거예요
그럼 제가 가서 확인해 볼게요
여기 보면 Y가 이 덩어리들을 채운 것을 볼 수 있습니다. 사용자 데이터는 단일한 null 바이트 오버플로로 이어집니다.
이 경우에는 맨 위 덩어리 사이즈 필드로 이어집니다.
궁금하실까 봐 말씀드리자면 오버플로가 발생하는 이유는 이 프로그램이 올바른 바이트를
입력한 후 문자열을 종료하기 위해 null 바이트를 추가하기 때문입니다 ,
앞서 언급했듯이 null 바이트의 과다 유출이 발생하는 좀 더 일반적인 방법 중 하나입니다
네 이 버그를 어떻게 할지 생각해 보죠 상부 청크의 영역에 이렇게 넘쳐난다고
큰 영향은 없을 거예요
크기는 아주 조금 변하지만 특별히 작지도 크지도 않습니다
네 일반적인 크기에 하나의 널 바이트를 넘치면 피해가 커지죠 그러기 전에
두 가지를 고려해야 합니다
첫 번째는 희생자의 몸뚱이를 할당할지 무료로 줄지를 정합니다
네 이 구역에서 둘 다 공략할 건데 일단
에인헤랴르 저택은 할당된 금액을 정해야 해요
두 번째는 희생자의 크기를 고려해야 합니다 한 바이트 크기로 목표물을
정하면 양이 넘쳐 0이 되고 0 크기 덩어리는 유용하지 않죠
하지만 피해자의 크기가 0으로 끝나는 0x100이라면요?
프로그램을 다시 시작해서 0x100 사이즈로 만들게요
보시다시피 0x90 사이즈로 넘쳐나고 있어요 그다음은 0x100이고요
0x100 덩어리에서 가장 적은 바이트는 0이 아니고 prev_inuse 비트가 있습니다
네 그러니까 바이트 하나를 크기 필드에 넣어도 크기 값엔 영향이 없지만 prev_inuse 플래그는
확실히 없애는 거죠
다시 합선 실험 1부를 떠올려 보세요 링크 해제 기술을 위험하게 활용했잖아요 네 거기에 오버플로를 이용해서 청크의 prev_inuse
플래그를 지웠어요 다른 청크 크기로 통제된 오버플로를 이용해서요
에인헤리야르 네 "예전 사용 전 깃발 제거"
네 그리고, 언링크 기술 둘 다 단일 null 바이트 오버플로를 이용해 활용할 수 있습니다 이미
0 바이트로 끝나는 큰 크기의 필드를 대상으로 할 수 있죠
기억하시죠? 네 말록이 이전 덩어리가 나온
줄 잘못 알고 속아서 우리가 만든 가짜 덩어리로
합체시킨 겁니다 ? 네
에인헤리야르 가에서도 여전히 같은 일이 벌어집니다
유일한 차이점은 최종 목표는 링크 해제 과정을 이용하는 게 아니라
안전한 링크 해제 체크를 전달해 가짜 청크를 할당하고 다른 민감한 데이터나
인라인 맬록 메타데이터를 중첩하는 거죠
이걸 하기 위해서 우리는 물론 pwntools의 도움이 필요합니다 GDB를 닫고 pwntools_template를
복사합니다 ??? 파이튼 대본이죠
선택 편집기에서 복사본을 열고 아래로 스크롤하세요
네 예상대로 제가 쓴 헬퍼 함수의 예가 있어요 스크립트는 사용자
이름 입력도 관리하죠
네 일반적으로 평소에는 점선 밖에 있지만 이 경우엔 사용자 이름 코드에 따라
있습니다 에인헤르야르 바이너리가
익숙한 순서와는 좀 다르게 일을 하기 때문이죠
네 사용자 이름 코드는 유지하고 힙 누출 캡처와 말록에 호출 두 번 도우미 기능만 쓰고 나머지는 다 없애요
, 알았죠?
네 그럼 방금 보여드린 GDB와 같은 상태예요 0x90 사이즈
A와 0x100 사이즈 B죠
네 다음으로 넘어가서 하나의 널 바이트 오버플로를 b의 사이즈에
적용하면 사용 전 단계가 지워집니다
네 통합 공격에서는 가짜 prev_size 필드를 제공해야 합니다 뒤로 통합하는
동안 멀록이 가짜 덩어리를 찾을 수 있도록요 우선 자리
표시 값을 설정하겠습니다
편집을 이용하세요 청크 A를 쓰레기로 채우는 도우미 기능
counterfe prev_size 필드가 사용자 데이터의 마지막 4괄호에 있죠
네 청크 A의 사용자 데이터에 입력된 정보가 없으면 프로그램 종료가
잘못되어 사용 전 표시가 지워집니다
대사를 추가하고 GDB와 NOASLR 옵션으로 스크립트를 실행하세요
ctrl+c로 브레이크하고 'vis' 명령을 발행합니다
좋아요, 네 지워졌다는 걸 보여주죠 아직 할당되어 있는데도요
네 지금 B를 풀어 주면 말로크가 이전 덩어리와 결합하려 할 거예요 그러면 prev_size
필드를 이용해 이전 덩어리를 찾겠죠 ?
네 네 a는 무료가 아니기 때문에 우리가
컨트롤 할 수 있죠 "BB"
알겠소 세이프 언링크 기법으로 가짜 프리브사이즈 필드를 멀록이 가짜 조각을
찾을 수 있는 값으로 설정했어요 ? 네 통합을 위해 사용자 데이터를 링크 해제했습니다
에인헤랴르 집도 똑같이 할 겁니다 두 가지 작은 차이점만 빼고요
첫 번째 차이점은 가짜는 폐기장에 안 산다는 거죠
대신 이 프로그램 데이터 섹션에 대상 데이터를 중첩해서 만들 겁니다
두 번째는 링크 해제 과정을 활용하는 데 관심이 없기 때문입니다 청크 포인터의 주소를
모릅니다 세이프_언링크 바이너리와 달리 이 프로그램은 청크 포인터를 저장해 두거든요
유출하지 않았습니다
우리 관심사는 링크 해제 과정이 성공해서 가짜 덩어리가 B 덩어리와 합병되는 것뿐이에요
편집기로 돌아가 Fake 청크를 위조하고 counterfe prev_size 필드를 적절한 값으로 설정합니다
지금쯤이면 메타데이터를 위조하는 데 익숙해져서 자연스럽게 익숙해졌길 바라요
네 'user name' 변수를 수정하고 해당 'user' 스트럭트에 해당하는 내용을
목표 데이터 직전인 에인헤르야르 하우스 데이터 섹션에 입력할 겁니다
첫 번째 쿼드는 사용하지 않는 prev_size 필드를 형성하기 때문에
어떤 값이든 취할 수 있습니다. Fake 청크의 크기는 타겟을 겹칠 만큼 커야 합니다.
네 물론 안전 연결을 끊으려면 이 덩어리가 필요하지만 이젠 방법을
알죠 앞뒤 포인터를 가짜 덩어리의 주소로 설정합니다
네 가짜 청크 메타데이터가 준비됐으니 사이즈 필드를 청크 A 다음에 시작하는 청크 B와
가짜 청크 사이의 거리에 설정하기만 하면 됩니다
맞아요 누수가 많아서 가능했죠 하지만 에인헤랴르 집을 이용할 다른 방법도 있을
거예요 , 이 방법이 필요 없는 방법요
일반적인 옵션으로 스크립트를 실행합니다 모든 게 제자리에 있는지 확인하기
위해서요
'비스' 명령에 따르면 위조 prev_size 필드는 0x1080으로 변했습니다
아주 작죠 작아 보이지만 맞습니다 디버깅할 때 aslr을 비활성화했습니다 기본값 힙이 프로그램
쓰기 가능한 세그먼트 직후 매핑된다는 뜻이죠
안심하세요, ASLR이 생산 조건에서 우리 취약점을 시험할 수 있게 되면 prev_size 필드는
훨씬 큰 가치를 차지할 겁니다
'user' 스트럭트에 'dq' 명령을 써서 메모리를 버리면 가짜 덩어리가 보여요
0x31이라는 용량의 필드를 갖추고 있어 목표물을 겹칠 만큼 충분한 사용자
데이터가 확보되며 fd와 bk 포인트도 스스로 확보하여 안전 링크를 해제할 수 있습니다
B를 풀어 주면 말로크가 주변에서 적합한 사람을 찾을 거예요 네 왜냐하면 단일 바이트 오버플로로
B의 사전_인용 플래그를 지웠거든요 그래서 맬록이 이전
것과 다시 통합하려 할 거예요 네 먼저 간 덩어리를 찾기 위해 주소에서
크기 b의 값을 뺍니다
네 이 경우에는 0x603090 빼기 0x1080은 0x602010 프로그램 데이터에
있는 가짜 청크의 주소입니다 안 돼!
그럼 말록이 가짜 덩어리를 링크 목록에서 풀고 b 덩어리와 합칠
거예요
네 맨 위와 아래 사이에 가짜 덩어리가 있다면 무체중 빈으로
연결되겠죠
네 하지만 이 시나리오에선 덩어리 b가 상단의 덩어리와
연결되죠 따라서 덩어리 b와 가짜 덩어리는 나중에 상단의 덩어리로 통합됩니다
어느 쪽이든, 이로써 타깃 데이터 중첩 메모리를 새로운 부분에 할당할 수 있습니다
이제 수동으로 B 덩어리를 풀어보죠 계속하세요 알트++로 가서 프로그램 창으로 가세요 메뉴 프린트를
반환하고 옵션 2를 눌러 인덱스 1의 덩어리를 빼세요
네 네 우리가 말록의 취약점 완화를 촉발한 것 같군요 오류 메시지는 '오락된 크기 대 prev_size'예요
?
네 그나저나 어느 오류 조정 메시지가 어떤 오류와 연결됐는지 잊으셨다면 '핸드랩
pdf'의 '조정' 부분 아래의 '조정' 오류 메시지를 확인하세요
이런 완화 현상은 당연하게도 prev_size check 글리벡 버전
2에서 소개되었습니다. 26살요 에인헤야르 가문과 버전 2는 연결되어 있어요
28
네 링크 해제에 수표가 적용돼 있어요 매크로, 소스 코드를 파헤쳐서 찾아야
해요
alt+를 이용하여 gdb 창으로 이동하고 다음 명령을 이용해 사이즈 vs를 둘러싼 소스 코드를
봅니다 prev_size check
오류 메시지가 있고 이건 확인 그 자체입니다
네 비교를 시도하는 거예요 그 덩어리를 가리키는 prev_size 필드와 연결되지
않은 크기를요
네 맞습니다 두 값이 일치해야 하는데 이 체크 표시는 단일 바이트 널을 착취하는 것을
막기 위한 시도로 특별히 소개되었습니다
다행히도 이 수표를 통과시킬 방법이 몇 가지 있어요
첫 번째는 가짜 미리_사이즈 필드에 가짜 청크 크기 필드를 맞추는 겁니다
네 하지만 에인헤야르 바이너리는 우리가 사용자 이름을 입력한 후에 힙 시작 주소를 유출합니다
거기엔 Fake 청크 메타데이터가 있어서 돌아가서 편집할 수 없어요
그러니까 ASLR을 작동시키면 가짜 덩어리와의 거리가 예측 불가능해지는데
가짜 덩어리를 만든 후에는 착취가 실패할 거예요
크기 vs를 통과할 수 있는 두 번째 방법이 있어요 prev_size check. 비디오를
정지하고 한 번 보세요. 여기서 'p'는 연결이
끊긴 청크의 주소예요
매크로란 p의 크기를 찾는 겁니다
다음 덩어리는 p 크기장을 이용해 다음 덩어리를 찾고 있어요
매크로의 목적은 청크의 미리 크기 필드를 찾는 거예요
아직 모르셨다면 사이즈 vs의 구현에 있는 결점은
네 prev_size check 그게 연결되지 않은 상태에서 작동한다는 거죠 '피해자'
덩어리요
prev_size 필드가 어디에 있는지 모릅니다.
네 비교할 미리 보기 영역과 미리 보기 영역의 크기에 의존해야
한다는 뜻이죠
그렇습니다 이로 인해 거시기가 찾으려고 하는 미리 크기 영역과 실제로 찾으려고 하는 미리 크기 영역을 단절시킬
수 있습니다
가짜 돈을 이 체크를 받을 때 어떤 일이 일어나는지 생각해보죠
네 크기는 덩어리로 결정됩니다 매크로라고 하는데 전달된 주소의
2차 4문자를 확인하죠 결과는 정확합니다 이 경우에는 0x31이 나왔어요
이 값은 다음과 같이 발견된 prev_size 필드와 비교합니다
다음 덩어리는 거창한 방법으로 다음 덩어리를 찾으려 합니다 주소와 피해자 덩어리의
크기를 더해서요 여기 0x602010 1 더하기 0x30은 0x602040 청크 b의 주소가 아니라 프로그램 데이터
섹션에 있는 무작위 메모리 조각이에요
다음, prev_size 매크로 간단히 해당 위치의 값을 반환합니다
네 그 결과 fake 청크 사이즈를 prev_size 필드와 비교하는 대신
우리가 통제하는 fake 청크에서 상쇄된 메모리와 비교합니다
맞아요 아쉽네요 하지만 이 경우에는 가짜 청크 이후의 기억에 영향을 줄 수 없어요 가짜 크기의
필드를 또 만들 수 없죠 하지만 그럴 필요는 없어요
Fake 청크 크기에 대해 선택할 수 있는 값이 하나 있는데 항상 크기 대 V를 통과하는 거죠 prev_size check
그 값은 8입니다
가짜 덩어리가 8이라면 크기 vs. 네 네 ????
0x602010 플러스 8은 0x602018 이 덩어리의 8번 크기를 희생자의
것과 비교해 보세요
두 값이 일치하고 수표가 통과됐습니다
8명의 사이즈가 항상 크기 대 크기 시험을 통과합니다 prev_size check 합법적인 사이즈도 아닌데
말이죠
GDB를 닫고 에디터로 돌아가서 fake 청크 사이즈를 수정합니다.
네 다 했고요 무료에 추가하세요 도우미 기능으로 B 덩어리를 무료로 얻습니다 이제 Fake
청크와 성공적으로 뒤바뀌길 바랍니다
네 마무리하기 전에 GDB와 NOASLR 옵션으로 공격 스크립트를 실행해보죠
멀록의 상태가 어떤지 한 번 더 봅시다
'유저' 스트럭트에서 메모리를 깨고 검사하세요
많이 바뀌진 않았습니다 가짜 청크 크기가 이제 상단 청크 크기처럼 보이는 것 빼고는요
'톱_청크' 명령을 발급하면 상단 청크가 프로그램 데이터 섹션에 있는 게 확인됩니다
그래서 결국 힘의 집에서도 비슷한 결과가 나왔어요
기억하세요, B 덩어리는 가짜 덩어리와 통합됐을 뿐 아니라 위쪽 덩어리를 손상시켰다는
걸요 가짜 덩어리와 이전의 위쪽 덩어리 사이의 전체 공간이 이젠 위쪽 덩어리로 통합됐다는
거죠 안 돼요
네 B 덩어리가 상단에 닿지 않았다면 가짜 덩어리가 정처 없이 연결됐을 겁니다
이런! 네 안 그러면... 새 크기는 덩어리 크기 필드를
함께 추가하는 게 아니라 prev_size 값을
이 크기에 더하여 계산합니다
네 ASLR을 사용하면 히프와 프로그램 데이터 구역의 거리가 크게 늘어나
커다란 가짜 덩어리가 무체중 빈에 연결될 수 있어요 그럼 우리가 할당했을
때 크기 확인에 실패하죠
아시다시피 포스의 집 글립코 버전 2 네 28 이하의 크기는 상단 크기 확인
기능이 없어요 그래서
지금 하는 방식이 더 낫죠 상단 크기와 통합하는 겁니다 통합과 할당 사이에서
가짜 크기를 수정할
수 없다면요
자 그럼 에인헤랴르 저택의 통합 단계를 성공적으로 마쳤으니 편집자에게
가짜 덩어리를 요청해서 임의로 쓰도록 하죠
네 다음 요청은 제일 위에서 서비스됩니다 타깃 데이터 바로 앞에 있죠 네, 맞아요
네 몇 개의 쿼드워드만 보내면 가짜 fd와 bk, 타겟이 있는 가짜
청크 사용자 데이터의 시작점과 거리를 만들 수 있어
마지막으로 생산 조건에서 착취를 실행합시다 GDB와 NOASLR 옵션 없이요
메뉴에서 옵션 4를 선택하면 에인헤랴르 저택 기술을 활용해 임의로 쓸 수 있다는
걸 확인하는 데이터가 출력됩니다
요약하자면 이 바이너리는 힙에 단일 바이트 버그가 있어서 prev_inuse
플래그를 지웠습니다
네 피해자의 크기는 널바이트로 끝났어요 넘쳐난 게 크기 값에 영향을 주지 않고 깃발만 영향을
준 거죠 ?
네 가짜 미리 보기 크기를 입력했어요 피해자 조각과 다른 메모리 공간에서
준비한 가짜 조각 사이의 거리를 설정했죠
잠깐만요 피해자를 풀어 줬을 때 사용 전 경고 깃발을 보고 말록은 범인이 예전으로 돌아가 정리해야 한다고 생각했어요
안 돼요!
맞아요 네 안 돼요!
이 실험이 성공하려면 가짜 덩어리가 안전한 연결을 끊는 수표를 통과해야 했습니다
가짜 fd와 bk 포인터를 줘서 가짜 덩어리를 가리켰죠
사이즈를 통과했어요 언링크 과정에서 prev_size check 가짜 청크
사이즈 필드를 체크 8을 통과하는 값으로 설정합니다
네 피해자 덩어리가 가짜 덩어리와 통합되면 최상위 덩어리와도 통합돼요 ? 네
덕분에 위쪽 덩어리가 가짜 덩어리가 있는 곳으로 이동하게 됐죠
네 마지막 메모리 요청을 했고 새로운 상부 지점에서 목표 데이터를 중복했습니다
나중에 추가로 작성했죠 ?
네 다음으로 넘어가기 전에 에인헤랴르 가문에 관해 몇 가지 주목할 점이 있습니다 prev_size check는 글리벡
버전 2에서 소개되었습니다. 26, 그 말은 이전 버전에 대해 개발할 때
이용하지 않아도 된다는 거죠
하지만 1초 더 튼튼한 크기 대 네 prev_size check는 글리벡 버전 2에서 소개되었습니다. 29 즉, Fake 청크의
크기 필드가 prev_size 필드에 대응되어야 합니다
네 마지막으로 에인헤랴르 집의 이 예시에서는 가짜 덩어리를 사용했지만 기존의
빈 덩어리를 합치는 데 사용하지 못할 이유가 없죠
사실 그게 더 이롭죠
네 이 방법은 새는 곳이 많지 않을 뿐더러 같은 흙더미 사이의 거리도 상대적이고
결정론적이니까요 그리고 합법적인 자유 덩어리가 본질적으로 연결을 끊는 수표를 통과할 겁니다
걱정 마세요 다음 도전에 맞서 에인헤랴르 가문을
이용할 기회가 생길 겁니다 네 당분간 이 기술을 다시 기억하시려면 평소처럼
HeapLAB PDF를 통해 강의 리소스에서 주석이 있는 솔루션 스크립트를 다운받으세요
다음 영상에서는 네 , 독극물입니다