위치로 진입 점

마지막 업데이트: 2022년 7월 2일 | 0개 댓글
  • 네이버 블로그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 트위터 공유하기
  • 카카오스토리 공유하기
nearpc

How to write more testable code by Jin-Wook Chung

이전 글에서 테스트하기 어려운 코드와 쉬운 코드를 어떻게 구분하는가에 대해 알아보았다. TDD, Unit Test를 설명하는 글 속에서 만나게 되는 예제는 대부분 테스트하기 쉬운 경우에 속한다. 테스트하기 어려운 코드로 테스트를 쉽게 설명할 수 없기 때문이다. 실제 프로젝트에서는 테스트하기 쉬운 코드만 있는 것이 아니다. 요구사항을 구현하기 위해 메일도 보내야하고 데이터베이스에 데이터를 저장하거나 읽어야 한다. 소프트웨어를 통해 이루려는 것은 필연적으로 외부세상과 소통이 필요하기 때문에 테스트하기 어려운 코드는 피할 수 없다. 우리는 이를 최대한 줄이려고 노력해야 한다. 테스트하기 쉬운 코드를 가능한 많이 작성해야 한다는 뜻이다. 이런 노력은 궁극적으로 테스트에 드는 비용을 최소화하기 위함이다.

테스트하기 어려운 코드와 쉬운 코드 분리

이메일과 비밀번호를 입력받아 회원가입하는 시나리오를 생각해보자. C#으로 시나리오를 구현하면 아래와 같은 모습이 된다. . 은 코드가 생략되었음을 의미한다. 이메일, 비밀번호가 유효하면 그 정보를 UserStore 를 통해 데이터베이스에 저장하고 그렇지 않으면 예외를 던진다. 이메일과 비밀번호가 유효한 형식인가를 테스트하려고 해도 어쩔 수 없이 데이터베이스를 통한 통합테스트를 하거나, Test Double을 이용해야 한다. 테스트 비용이 높은 경우로 테스트하기 어려운 코드다. 이를 단번에 알 수 있는 방법이 있다. SignUp 메소드의 리턴타입을 보자. Task 타입, IO관련 작업이다. IO관련 작업은 외부세상과 소통을 의미하므로 테스트하기 어려운 경우다.

테스트하기 쉬운코드를 테스트하기 어려운 코드( UserStore.AddAsync )와 섞음으로 모든 코드가 테스트하기 힘들게 되었다. 해답은 간단하다. 이들을 분리시키자. 테스트하기 쉬운 코드를 최대한 어려운 코드에서 분리하여 순수함수 형태로 만들면 된다.

이 경우 테스트하기 쉬운코드를 분리하여, Email 과 Password 타입을 만들면 좋겠다. (Primitive Obsession) 아래와 같이 생성자에서 문자열 값을 받아 유효한 형식인지 체크할 수 있다.

테스트하기 어려운 코드는 가장 바깥 쪽에 위치

아래 그림과 같이 A 메소드가 B를 호출하고 B메소드가 C, 그리고 D 이런 순서를 가진 콜스택을 생각해보자. 이때 D 메소를 IO 관련 메소드라고 분류하여 빨간색으로 나타내보자.

picture1

그러면 B, C 그리고 A 메소드가 테스트하기 쉬워도 아래 그림처럼 D 메소드의 테스트 어려움이 모든 메소드에 전파 된다. 위 SignUp 경우에서 테스트하기 쉬운 코드가 분리되기 전 모습과 같다. 이런 테스트 어려움이 전파되는 것을 막기 위해 테스트하기 어려운 코드(D)와 쉬운 코드(B, C)를 분리할 필요가 있다. A 메소드는 콜스택에서 가장 바깥 쪽에 위치하여 진입점이 되어 분리될 수 없다.

picture2

분리된 D 메소드는 어디에든 위치시켜야 한다. SignUp 경우에서 UserStore.AddAsync 메소드가 어디에든 위치하여 실행되어야 사용자 정보가 저장되어 회원가입이 마무리된다. 테스트하기 어려운 함수가 콜스택 안쪽에 위치할수록 테스트하기 어려운 코드량이 늘어난다. 테스트 비용이 증가되는 것이다. 따라서 아래 그림처럼 테스트하기 어려운 코드를 가장 바깥쪽에 위치시키면 테스트 비용을 최소화할 수 있다. 이때 진입점 역할을 하는 A 메소드는 선택할 여지없이 테스트하기 어려운 코드로 분류된다.

picture3

Function Root

A 메소드는 특정 기능 수행의 진입점 역할을 한다. 테스트하기 어려운 IO 관련 코드를 담고 있는 경우가 많다. 그래서 그 자신 역시 테스트하기 어려운 형태가 되는 특징이 있다. 이런 메소드 또는 함수를 Function Root라 하자.

어플리케이션에 필요한 모듈들을 한꺼 번에 구성할 수 있는 장소를 Composition Root라고 한다. Composition Root는 프로그램 시작점이지만, Function Root는 기능 시작점이라는 것에 차이점이 있다.

Function Root가 될 수 있는 위치를 프레임워크 별로 나눠 생각해보면 다음과 같다

  • UI 프로그램의 이벤트 핸들러
  • Web API의 액션메소드
  • Azure Functions 또는 AWS Lambda에서 호출되는 함수
  • etc

Summary

테스트 비용을 줄이기 위해 테스트하기 쉬운코드를 많이 작성해야 함은 자명한 사실이다. 그러기 위해 테스트하기 어려운 IO 코드를 그렇지 않은 코드로 부터 물리적으로 분리 시키자. 그러면 테스트하기 쉬운 순수함수 코드를 얻을 수 있다. 테스트 하기 어려운 IO 관련 코드는 Function Root 에 위치시키자. Function Root에서 콜스택 안쪽으로 들어 갈수록 테스트 비용이 증가하기 때문이다.

아래 흐름을 보자. --> 표시는 참조한다, 의존한다는 의미다. 중요한 모듈(Domain Model)이 상대적으로 덜 중요한 모듈(Data Access)에 의존하고 있다. Dependency Inversion Principle(DIP) 위배에 해당한다.

User Interface Module –> Domain Model Module –> Data Access Module

위 경우는 다음과 같이 바뀌어야 한다. Domain Model이 Data Access에 의존하는 것이 아니라 그 반대가 되어야 한다. 테스트 비용 입장에서 봐도 Domain Model이 Data Access에 의존하면 테스트 어려움 때문에 테스트 비용이 증가하게 된다. Domain Model은 온전히 IO관련 작업에서 분리되어 순수함수 형태의 코드로 테스트 되는 것이 좋다.

위치로 진입 점

Problem : Find the Serial when the Name of "CodeEngn.com"

이번 문제는 Unlock Code라는 것부터 찾아야합니다.

파일을 ollydbg로 열면 일종의 팩이 되어있다는 것을 알 수 있습니다. UPX는 아닙니다.

PEiD로 보면 이미지베이스 00400000에 코드베이스 00001000이라고합니다. 시작 주소는 00401000이 되겠네요.

back to user mode라는 방법을 이용합니다. 간략히 설명하면 프로그램을 실행시켜 특정 이벤트 발생으로 인한 멈춤 상태까지 간 다음에 일시정지 - Execute till user code를 누른 후 이벤트를 계속 진행시킵니다. 그럼 해당 이벤트 위치로 코드가 이동하게 되며 이런 방법으로 00401000의 코드를 확인할 수 있습니다. 한번 찾아두면 다음부터는 ollydbg가 알아서 코드분석을 해줍니다. 처음 ollydbg를 켜자마자 00401000의 위치로 가면 이상하게 보이지만 이 방법으로 가면 제대로 언팩된 코드를 볼 수 있습니다.

GetDlgItemTextA를 힌트로 해당 부분에 bp를 걸어봅니다. 그 아래에 "You have entered~"와 바로 위에 점프문도 있네요. 이 부분이 Unlock Code의 행방을 가르는 부분이라는 것을 알 수 있습니다.

CMP EAX, 7과 JBE로 인해 코드는 8글자 이상이어야 합니다. aaaaaaaa로 하겠습니다. 그럼 name과 serial 부분에 입력을 할 수 있게 됩니다.

그런데 계속 진행하다보면 그 후 코드가 다시 이상해집니다. aaaaaaaa을 넣고 name과

serial을 아무 값이나 넣고 check해보면 프로그램이 죽습니다.

ollydbg에서는 I/O command 라는 코멘트가 있는 부분에서 죽으며 위아래로 보면 004011C0의 NOP아래로 쭉~ 0040123B까지 코드가 이상하다는 것을 확인할 수 있습니다.

코드가 이상한 부분으로의 진입점은 00401135의 CALL 004011E8입니다. 그런데 004011E8은 함수의 시작점인데도 불구하고 ADD BL, AH로 되어있습니다. 실제로 이 부분은 Unlock Code로 뭐가 들어가는지에 따라 코드가 변경됩니다. 그리코 코드의 변경은 004010A7의 CALL 004012F7이 실행된 이후에 이루어집니다.

005012F7의 함수를 보면 unlockcode의 길이를 1과 비교하고 아래에 문제의 주소값인 004011C1과 0040123C가 있습니다. 함수 내의 반복은 해당 명령문만큼인 7B번 이루어지며 403318의 값과 DL을 XOR연산하고있습니다. EBX+EAX의 값은 문제의 코드 부분으로 이 주소의 값, 즉 명령어를 바꿔주고 있습니다. EBX의 값을 하나씩 올리면서 코드를 밑으로 훑고 있네요.

그런데 00403318의 값은 현재 72로 고정값입니다. 이 값으로 모든 코드를 바꿨을 때 004011E8의 코드는 ADD BL, AH로 원하는 명령어가 아닙니다. 함수의 기본 시작 형태인 PUSH EBP는 55. 즉, XOR 결과를 55로 만들어줍니다. 현재 004011E8의 값은 70. 70과의 XOR연산으로 55가 나오는 수는 0x25입니다. 00403318의 값을 25로 만들어주어야 하는데 의심되는 코드로 0040109F의 CALL 004012CD를 봅니다. 코드의 언팩은 특정 값에 따라 달라지는데 이 값은 00403318에 있고 unlockcode에 의해 결정되면 unlockcode를 다루는 함수를 의심해볼 수 위치로 진입 점 있습니다. 해당 함수 내에서 루프를 돌면서 403318의 값을 바꿔주고 있습니다. 8자리 이상의 조합으로 루프를 거쳐 25가 나오게 하는 경우의 수를 이전과 같이 브루트포스를 이용할 수 있겠지만 문제는 unlockcode를 구하는 것이 아니므로 00403318의 값을 직접 수정해주겠습니다.

위치로 진입 점

보다 복잡한 사용을 위해(가령 매 빌드 후 임의의 프로세싱을 해야한다던가) CLI 대신 API 를 이용해서 번들러를 초기화 할 수 있습니다. 모든 옵션이 설명된 아래 예시를 보세요.

모든 번들러 이벤트 목록입니다.

  • bundled 이벤트는 처음 한 번만, Parcel 이 성공적으로 번들링을 마친 후 발생되어, 메인 번들을 callback 에 전달합니다.
  • buildEnd 이벤트는 재빌드를 포함한 빌드 후 매번 발생됩니다. 에러가 발생한 경우에도 발생합니다.

Bundle 은 Parcel 이 애셋을 함께 번들링하기 위해 사용하며, 번들 트리를 빌드하기 위해 자식, 형제 번들을 포함합니다.

  • type : 애셋의 종류 (e.g. js, css, map, . )
  • name : 번들의 이름 ( entryAsset 의 Asset.generateBundleName() 로 생성)
  • parentBundle : 부모 번들. 진입점 번들일 경우 null
  • entryAsset : 번들의 진입점. name 의 생성 및 애셋 수집에 사용.
  • assets : 번들 안에 있는 모든 애셋의 집합( Set )
  • childBundles : 모든 자식 번들의 Set
  • siblingBundles : 모든 형제 번들의 Set
  • siblingBundlesMap : 모든 형제 번들의 Map
  • offsets : 번들 안의 애셋 속의 모든 위치의 Map . 정확한 소스맵 생성을 위해 사용됨

Bundle 은 parentBundle , childBundles , siblingBundles 을 포함하고, 이 모든 속성은 빠르게 번들트리를 순회하여 만들어집니다.

매우 기본적인 애셋 트리로 번들 트리를 생성합니다.

애셋 트리

index.html 는 index.js 와 index.css 를 필요로 합니다.

index.js 는 test.js 와 test.txt 를 필요로 합니다.

번들 트리:

index.html 은 메인 번들을 위해 진입 애셋으로 사용됩니다. 이 메인 번들은 index.js 과 index.css 2 개의 자식 번들을 만듭니다. 둘 다 html 과 다른 타입이기 때문입니다.

index.js 는 test.js , test.txt 두 파일을 필요로 합니다.

test.js 은 index.js 번들의 애셋으로 추가됩니다. index.js 와 같은 에샛 타입이기 때문입니다.

test.txt 는 index.js 와 다른 애셋 타입이기 때문에 이를 위해 새 번들이 생성되고, 해당 번들은 index.js 번들의 자식으로 추가됩니다.

침입이 검출됨

시스템의 여러 진입점(예: 웹 페이지, 공유 폴더, 이메일 또는 USB/외부 디스크/CD/DVD 등의 이동식 장치)에서 침입이 발생할 수 있습니다.

표준 동작

침입 항목이 ESET Smart Security Premium에서 처리되는 방법에 대한 일반적인 위치로 진입 점 예로, 다음 방법을 사용하여 침입을 검출할 수 있습니다.

각 방법에서는 표준 치료 수준을 사용하며, 파일을 치료하고 검역소로 이동하거나 연결을 종료하려고 시도합니다. 화면의 오른쪽 하단에 있는 알림 영역에 알림 창이 표시됩니다. 탐지/치료된 개체에 대한 자세한 내용은 로그 파일을 참조하십시오. 치료 수준 및 동작에 대한 자세한 내용은 치료 수준를 참조하십시오.

ANTIVIRUS_BEHAVIOR_01

감염된 파일에 대해 컴퓨터 검사

컴퓨터가 맬웨어에 감염된 증상(예: 속도가 느려짐, 작동이 자주 중단됨 등)을 보이면 다음을 수행하는 것이 좋습니다.

1. ESET Smart Security Premium을(를) 열고 컴퓨터 검사 를 클릭합니다.

2. 컴퓨터 검사 를 클릭합니다(자세한 내용은 위치로 진입 점 컴퓨터 검사 참조).

3. 검사를 마치면 검사한 파일, 감염된 파일 및 치료된 파일 수가 표시된 로그를 검토합니다.

디스크의 특정 부분만 검사하려면 사용자 지정 검사 를 클릭하고 바이러스를 검사할 대상을 선택합니다.

치료 및 삭제

실시간 파일 시스템 보호에 대해 수행할 동작이 미리 정의되어 있지 않으면 경고 창에 옵션을 선택하라는 메시지가 표시됩니다. 일반적으로 치료 , 삭제 및 무시 옵션을 사용할 수 위치로 진입 점 있습니다. 무시 옵션은 감염된 파일을 치료되지 않은 상태로 두기 때문에 선택하지 않는 것이 좋습니다. 단, 파일이 무해하며 잘못 검출된 것이 확실하다면 무시를 선택해도 됩니다.

ANTIVIRUS_BEHAVIOR_AND_USER_INTERACTION_01

파일이 악성 코드를 첨부한 바이러스에 의해 파일이 공격을 받았다면 치료를 적용합니다. 이 경우 먼저 감염된 파일을 치료해 원래 상태로 복원합니다. 악성 코드만 포함된 파일은 삭제됩니다.

감염된 파일이 “잠긴“ 상태거나 시스템 프로세스에서 사용 중이면 일반적으로 시스템을 다시 시작하여 해제된 후에만 삭제됩니다.

검역소에서 복원

검역소는 ESET Smart Security Premium 기본 프로그램 창에서 도구 > 추가 도구 > 검역소 를 클릭하여 접근할 수 있습니다.

검역소로 보낸 파일은 원래 위치에 복원할 수도 있습니다.

• 이렇게 하려면 복원 기능을 사용합니다. 이 기능은 마우스 오른쪽 버튼 메뉴에서 검역소에 지정된 파일을 마우스 오른쪽 단추로 클릭하여 사용할 수 있습니다.

• 위치로 진입 점 파일이 사용자가 원치 않는 애플리케이션으로 표시된 경우 복원 후 검사에서 제외 옵션이 활성화됩니다. 또한 제외를 참조하십시오.

• 마우스 오른쪽 버튼 메뉴에서는 복원 대상 옵션도 제공하여 제거된 위치가 아닌 위치로 파일을 복원할 수 있습니다.

• 예를 들어 읽기 전용 네트워크 공유에 있는 파일의 경우 복원 기능을 사용할 수 없습니다.

여러 위협

컴퓨터 검사 중 감염된 파일이 치료되지 않은 경우(또는 치료 수준이 치료 안함 으로 설정된 경우) 이러한 파일에 대한 동작을 선택할지를 묻는 경고 창이 표시됩니다. 파일에 대해 수행할 동작을 선택한 다음(목록의 각 파일에 대해 개별적으로 동작 설정) 마침 을 클릭합니다.

압축파일의 파일 삭제

기본 치료 모드에서는 압축파일에 감염된 파일이 있고 감염되지 않은 파일은 없는 경우에만 전체 압축파일을 삭제합니다. 따라서 감염되지 않은 무해한 파일이 있는 압축파일은 삭제되지 위치로 진입 점 않습니다. 단, 엄격한 치료 모드에서는 감염된 파일이 하나라도 포함되어 있으면 압축파일 내의 다른 파일 상태에 관계없이 압축파일을 삭제하므로 엄격한 치료 검사를 수행하는 경우에는 주의해야 합니다.

Intro.

post-thumbnail

버그(bug) : 실수로 발생한 프로그램의 결함
디버거(Debugger) : 버그를 찾아주는 도구
- 프로그램을 어셈블리 코드 단위로 실행하면서, 실행결과를 사용자에게 보여줌
- 추상적으로 생각한 아이디어의 결과를 직관적으로 확인 -> 작성한 코드의 문제점을 명확하게 위치로 진입 점 찾을 수 있다.
- 즉, 버그 탐색의 효율을 높임.

  • 리눅스의 대표 Debugger 중 하나
  • 여러 plugin이 존재하는데 그 중 바이너리 분석 용도로 사용되는 Plugin 들은 아래와 같다.
    - gef
    • peda
    • pwngdb
    • pwndbg

    플러그인
    - 기본 소프트웨어를 지원해서 특수한 기능을 확장할 수 있도록 설계된 부속 프로그램.
    바이너리 분석
    - 0과 1로 이루어진 이진 값의 의미를 분석하는 것
    - 바이너리 코드를 역어셈블(Disassemble)하여 값으로만 존재하는 데이터가 어떤 명령어(Instruction)이며 어떤 피연산자(Operand)를 의미하는지 알 수 있다.

    실습예제

    • gcc -o debugee debugee.c : debugee.c라는 파일로 debugee라는 명을 가진 실행 파일을 만든다.
    • gdb debugee : debugee 를 실행했을 때, 프로그램 내부에서 어떤 일이 일어나고 있는지 보여줌
    • gdq에서 나오려면 q 작성 후 Enter 입력하면 됨.

    About ELF

    리눅스는 실행 파일의 형식으로 ELF(Executable and Linkable Format) 를 규정
    ELF : 헤더와 섹션들로 구성

    • 헤더 : 실행에 필요한 여러 정보
    • 섹션 : 컴파일된 기계어 코드, 프로그램 문자열을 비롯한 여러 데이터

    헤더 중 진입점(Entry Point, EP) 라는 필드가 있는데, 운영체제는 ELF를 실행할 때,
    진입점의 값부터 프로그램을 실행

    아래는 readelf -h 로 확인한 결과다.

    debugee라는 파일의 진입점은 0x401050 이다.

    명령어 - Start

    진입점부터 프로그램을 분석할 수 있게 해주는 gdb 의 명령어
    우선 그러기 위해 debugee 파일을 gdb를 이용해 실행하자.
    1.

    start를 해보자

    이 결과를 보니, Dreamhack에서 표시한 내용과 달랐다.
    그 이유를 찾아보니, 우리가 실행한 결과에서 breakpoint는 in main() 이며,
    Dreamhack은 start()다. 그래서 Dreamhack 처럼 결과를 보기위해 구글링.

    b _start : break를 _start로 지정 -> r : run, break 지점을 변경 후 실행



    제일 마지막 부분을 통해, main 함수로 넘어감을 알 수 있다.
    자세한 내용은 _start() 에서 확인. 아직 완전히 이해하지 못하여 작성 X

    • gdb start를 통해 진입점부터 프로그램을 분석할 수 있다.
      - b _start 미사용 시 breakpoint 가 main 으로 설정됨.
      • 이에, readelf 로 확인한 진입점과 값이 다름.
      • 그 이유는 _start 가 main 이전에 실행되기 때문
      • DISASM 영역에 있는 ► 가 가리키는 주소는 현재 rip의 값이다.
      • 중단점을 _start 로 지정한 경우, ► 의 주소와 진입점의 위치로 진입 점 주소는 일치한다.

      Context

      프로그램은 실행되면서 레지스터를 비롯한 여러 메모리에 접근

      따라서, 디버거를 이용하여 프로그램의 실행 과정을 자세히 관찰하려면 컴퓨터의 각종 메모리를 한눈에 파악할 수 있는 것이 좋다.

      맥락(Context) : pwndbg 에서 주요 메모리들의 상태를 프로그램이 실행되고 있는 "맥락".
      이를 가독성 있게 표현할 수 있는 인터페이를 가지고 있는 것이 pwndbg

      Context의 4가지 영역

      어셈블리를 실행할 때마다 갱신 -> 어셈블리 명령어가 메모리에 어떤 영향을 주는지 쉽게 파악

      1. Registers : 레지스터의 상태를 보여줌
      2. Disasm : rip 부터 여러 줄에 걸쳐 디스어셈블된 결과를 보여줌
      3. Stack : rsp 부터 여러 줄에 걸쳐 스택의 값들을 보여줌
      4. Backtrace : 현재 rip 에 도달할 때까지 어떤 함수들이 중첨되어 호출됐는지 보여줌

      디스어셈블된 결과 = 기계어를 어셈블리로 바꾼 결과

      Break & Continue

      gdb 이용하여 프로그램 분석할 때, 일반적으로 전체 프로그램 중 아주 일부분의 동작에만 관심
      => 비효율적인 분석 방법이 될 수 있다.

      위와 같은 문제를 해결하기 위해 디버거에는 breakcontinue 라는 기능을 제공

      • break : 특정 주소에 중단점(breakpoint)를 설정
      • continue : 중단된 프로그램을 계속 실행시키는 기능

      즉, break로 원하는 함수에 중단점 설정 -> 프로그램 계속 실행
      = 해당 함수까지 멈추지 않고 실행 후 중단.
      = 중단된 지점부터 다시 세밀하게 분석 가능

      start : 진입점부터 프로그램을 분석할 수 있도록 자동으로 중단점 설정
      run : 단순히 실행만 시킴 = 중단점 미설정 시 프로그램이 끝까지 멈추지 않고 실행

      Disassembly

      gdb 는 프로그램을 어셈블리 코드 위치로 진입 점 단위로 실행하고 결과를 보여줌
      프로그램의 코드는 기계어로 구성
      gdb 는 기계어를 디스어셈블하는 기능을 기본적으로 탑재
      추가로, pwndbg에는 디스어셈블된 결과를 가독성 좋게 출력해주는 기능 탑재

      disassemble 함수명

      gdb 가 기본적으로 제공하는 디스어셈블 명령어.
      함수 이름을 인자로 전달하면 해당 함수가 반환될 때 까지 전부 디스어셈블하여 보여줌.

      예시
      gdb debugee -> disassemble main 실행

      결과

      가독성 향상 명령어

      u

      nearpc

      pd

      Navigate

      관찰하고자 하는 함수의 중단점에 도달했으면, 그 지점부터는 명령어를 한 줄씩 자세히 분석.
      이때 사용하는 명령어에는 ni 와 si 가 존재.

      • 공통점
        - 두 명령어 모두 어셈블리 명령어를 한 줄 실행한다
      • 만약 call 등을 통해 서브루틴을 호출하는 경우
        - ni : 서브루틴의 내부로 들어가지 않는다.
        • si : 서브루틴의 내부로 들어간다.

        예시
        main 함수에서 printf 함수를 호출하는 지점까지 실행

        printf 함수를 호출하는 지점을 찾는 방법은 disassemble main 을 이용한다.
        Dreamhack 의 예제 같은 경우는 main+57 에 위치함을 disassemble main 을 통해 확인

        위의 사진을 통해 나의 printf 는 main+61 에 위치함을 알 수 있다.

        결과

        ni 입력 결과


        b *main+61 이후 ni 로 실행해본 결과, rip 가 printf 바로 다음으로 넘어갔음을 확인.

        printf를 실행했는데 아무 문자열도 출력되지 않는 이유

        printf 가 출력하고자 하는 문자열은 stdout 의 버퍼에서 대기 후 출력

        버퍼 : 데이터가 목적지로 이동하기 전에 잠시 저장되는 장소

        stdout 버퍼는 특정 조건이 만족됐을 때만 데이터를 목적지로 이동.

        • 프로그램이 종료될 때
        • 버퍼가 가득 찼을 때
        • fflush 와 같은 함수로 버퍼를 비우도록 명시했을 때
        • 개행문자가 버퍼에 들어왔을 때

        fflush 함수 : 시스템이 지정된 출력 stream 과 연관된 버퍼를 비우게 함.
        stream : 실제의 입력이나 출력이 표현된 데이터의 이상화된 흐름.
        즉, 운영체제에 의해 생성되는 가상의 연결 고리를 의미하며, 중간 매개자 역할
        한 방향으로만 통신이 가능하다는 특징이 존재.

        si 입력 결과


        printf 함수 내부로 rip 가 이동했음을 확인

        프로그램을 분석하다가, 어떤 함수의 내부까지 궁금할 때는 si, 그렇지 않으면 ni 사용

        위 결과를 통해 main 함수에서 printf 를 호출한 것이므로
        main 함수 위에 printf 가 쌓인 것을 확인 할 수 있다.

        finish

        si 이용해 함수 내부까지 필요한 부분 모두 분석했는데, 함수의 규모가 커서
        ni 로는 원래 실행 흐름으로 돌아가기 어려울 때 사용.

        rip 가 printf 바로 다음인 것으로 보아 원래 흐름으로 돌아왔다고 볼 수 있음.

        examine

        가상 메모리에 존재하는 임의의 주소의 값을 관측해야할 때 사용
        x 라는 명령어를 사용한다.
        사용 시,위치로 진입 점

        • 특정 주소에서 원하는 길이만큼의 데이터를 원하는 형식으로 인코딩하여 확인
        • Format letter
          - o(octal)
          • x(hex)
          • d(decimal)
          • u(unsigned decimal)
          • t(binary)
          • f(float)
          • a(address)
          • i(instruction)
          • c(char)
          • s(string)
          • z(hex, zero padded on the left)
          • b(Size letters)(byte)
          • h(halfword)
          • w(word)
          • g(giant, 8 bytes)

          예시
          1. rsp 부터 80바이트를 8바이트씩 hex 형식으로 출력

          2. rip 부터 10줄의 어셈블리 명령어 출력

          3. 특정 주소의 문자열 출력

          Telescope

          pwndbg가 제공하는 강력한 메모리 덤프 기능
          특정 주소의 메모리 값들을 보여주는 것

            메모리가 참조하고 있는 주소를 재귀적으로 탐색하여 값을 보여줌.

          메모리 덤프 : System 의 물리 Memory 를 File 형태로 저장하는 방법
          해당 File 의 구조는 실제 Memory 구조와 동일

          예시

          Vmmap

          가상 메모리의 레이아웃을 보여줌
          어떤 파일이 매핑된 영역 -> 해당 파일의 경로까지 보여줌

          • /home/uniasus/debugee
          • /usr/lib/x86_64-linux-gnu/lib-2.31.so
          • /usr/lib/x86_64-linux-gnu/ld-2.31.so

          예시

          리눅스에서 ELF 실행할 때,
          ELF의 코드와 여러 데이터를 가상 메모리에 매핑
          -> 해당 ELF 에 링크된 공유 오브젝트를 추가 메모리에 매핑

          • 자주 사용되는 함수들을 미리 컴파일해둔 것
            - 윈도우의 DLL(Dynamic Link Library) 과 대응되는 개념
            - 라이브러리 : 표준화된 함수 및 데이터 타입을 만들어서 모아놓은 것
          • C언어의 printf, scanf 등이 리눅스에서는 libc 에 구현
          • so 에 이미 구현된 함수를 호출할 때는 매핑된 메모리에 존재하는 함수를 대신 호출

          gdb / python

          gdb를 통해 디버깅할 때 직접 입력할 수 없는 경우가 존재.

          예시
          숫자와 알파벳이 아닌 값을 입력하는 경우.
          이러한 값은 이용자가 직접 입력할 수 없는 값 -> 파이썬으로 입력 값 생성하여 사용
          아래 실습은 프로그램의 인자로 전달된 값이용자로부터 입력받은 위치로 진입 점 값을 출력하는 예제

          gdb / python argv

          run 명령어의 인자로 $()와 함께 파이썬 코드를 입력하면 값을 전달할 수 있다.

          예시
          파이썬에서 print 함수를 통해 출력한 값을 run 명령어의 인자로 전달하는 명령어


0 개 댓글

답장을 남겨주세요