ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Pwnkr] Leg Write-up
    표튜터와 함께하는 Pwnable/Pwnable.kr Write-up 2019. 4. 2. 19:12

    이번 문제는 leg였다. 역시나 조금 어려웠던 것 같다.

    거의 다 풀었는데.. 까비..

    주어진 코드를 보도록 하자.


    < leg.c >

    코드를 보면 우리가 입력할 수 있는 key값을 각각의

    key1( ), key2( ), key3( )의 리턴값들을 더한 것과 같으면

    FLAG를 볼 수 있다는 것을 알 수 있다. 즉 코드를 분석할 줄알아야한다.


    또하나 주어진 코드를 보도록 하자. 어셈블리어 코드를 주었다.

    코드가 너무 길어서 짤랐다.


    < leg.asm 의 main >

    main의 어셈블리어 코드를 보면 함수가 끝나고난 뒤 r0의 값을

    가지고 모으는 것을 알 수 있다. 이를 통해 r0가 함수의 리턴값이라는 것을

    예측할 수 있었다. key1( )과 key2( )가 끝나고 나온 리턴값 r0를 각각

    r4와 r3에 저장했고 add를 이용하여 r4에 r4 + r3을 한 값을 저장했다.

    또한 key3( )를 진행한뒤 나온 리턴값 r0를 r3에 저장하고

    key1( )과 key2( )의 리턴값이 저장된 r4와 더해서 r2에 저장했다.

    마지막으로 cmp를 통해 r3와 비교한 것으로 보아 r3는 우리가 입력하게되는

    값이라는 것을 예측 할 수 있었다. 즉, 이 문제는 r0를 쫓아가야한다는 것을 알 수 있었다!!



    < leg.asm의 key1( ), key2( ), key3( ) >

    이런식으로 r0에 대한 부분을 추적해 보았다.

    그리고 공부하는 겸 이 코드 전체의 의미를 한번 주석처리해보았다.

    혹시나 필요하다면 참고해도 좋을 것같다.( 틀린점이 있다면 댓글 부탁드립니다. )


    leg asm 분석.rtf



    이 문제를 풀기 위해서는 ARM Assembly에 대한 배경지식이 조금 필요하다.

    문제와 관련이 없더라도 우선은 적어보도록 하겠다.



    ARM(Advanced Risc Machine) 이란?

    32bit RISC 프로세서로 CPU에 한 종류이다. 성능에 따라 다양한 CPU를 제공한다.

    고정된 길이의 명령어를 사용하기 때문에 여러 명령처리가 가능하다는 장점이 있다.

    그렇기 때문에 디코딩 속도가 빠르고 각 명령어가 한 클럭에서 동기화되므로 파이프라인에

    최적화가 된다고 한다. 하지만 컴파일 과정이 복잡하고

     고정된 명령어 길이로 인해 코드 효율은 낮다.


    ARM과 x86은 Thumb모드 유무에 대한 차이가 있다. ARM은 모바일이나 임베디드 등의

    PC와 다른 환경에 사용되도록 설계되었기 때문에 저전력이며 32비트가 아닌 16비트가

    가능하다 그렇기 떄문에 2가지 모드를 지원하며 그것이바로 ARM모드와 Thumb모드이다.



    * ARM 모드

    register : R0 ~ R15

    기계어 코드 길이 : 32bit(4byte)



    * Thumb 모드

    register : R0 ~ R7

    기계어 코드 길이 : 16bit (2byte)



    * 모드 전환

    모드 전환은 BLX, BX 등 X로 끝나는 분기문 명령으로 전환할 수 있다.



    * RISC아키텍쳐 방식

    상대적으로 명령어 수가 적음

    명령어들의 복잡도가 낮음

    하드웨어보다 소프트웨어의 유연성과 기능성을 제공함

    컴파일러에 의존함



    우리가 알아야 할 것은 크게 3가지로 나누어 볼 수 있다.


    1. Branch 명렁어

    2. Data Processing

    3. Load / Store



    < 우선 Branch 명령어이다 >

    Branch 명령어 들은 PC(EIP 역할)에 주소를 넣고 저장된 주소로

    프로그램 실행 번지를 바꾸는 역할을 한다.

    보통 함수가 호출될 때 대상 함수로 jmp하는 용도로 사용된다.


    < 예시 >

    B  < offset > : offset으로 jmp

    BL < offset > : offset으로 jmp하며 동시에 R14(LR)에 다시 돌아올 주소를 저장




    < 다음으로 Data Processing이다 >

    대상이 되는 레지스터에 어떠한 연산 등의 과정을 거쳐 저장하는 행위를 한다.


    < 예시 >

    연산 방향은 <- 이다.
    mov r0, [r1, r2] : r1 + r2 주소에 있는 값을 r0에 저장


    mov r0, r1, ROR #1 : r1을 오른쪽으로 한 비트 만큼 rotation해서 r0에 저장


    add r2, r4, r3 : r4에 r3를 더해서 r2에 저장


    sub sp, r11, #8 : r11에서 8을 뺀 뒤 sp에 저장




    < ldr (LOAD) / str (Store) >

    < 예시 >


    ldr r0, [ pc, #32 ] : pc에서 32byte만큼 더한 주소에 있는 값을 r0에 저장


    ldr r0, [pc], #32 : r0에 pc의 값을 저장한 뒤 pc = pc+32로 update함

    이를 post-indexed 방식이라고 한다.



    str r6, [sp, #-4]! : r6에 있는 레지스터 값을 sp-4의 주소에 저장한다. 

    이 때 !를 이용하면 sp값이 업데이트가 된다.



    str r6 [sp, #-4]와 비교하자면  *(sp + offset)를 r6에 저장

    그러나 !를 사용하면 sp=sp-4 한 뒤 r6에 저장됨 결과는 같으나

    sp값이 달라진다는 차이가 있다.(update됨)이를 pre-indexed 방식이라 한다.

    또한 str은 연산방향이 반대라는 차이점이 있다!!




    PC ( Program Counter ) : CPU가 현재 실행하고 있는 명령어의 주소를 가리킨다.

    IR (R14) : link register로 함수 호출 전, lr에 복귀할 주소를 저장한뒤 jmp한다.

    SP : stack pointer로 x86의 esp와 비슷한 역할을 한다.

    Address Register : 현재 사용하는 data를 접근하기 위한 data의 주소값

    Data Register : Address Register가 가리키는 주소값




    위에서 설명한 개념으로도 충분히 문제를 풀 수 있을 것이라고 생각하였지만

    우리는 한 가지 개념을 더 알아야 한다. 그것은 바로 ARM에서의 PC값 계산이다.


    ARM에서 명령을 실행할 경우 4단계로 표현할 수 있다.

    fetch -> decode -> execute -> write

    CPU의 효율을 위해서 fetch와 decode와 execute는 동시에

    수행되는데 흐름(Pipe line)은 다음의 그림과 같다. (이 과정은 ARM마다 다르다)


     LOAD가 fetch에서 Decode가 진행되면 동시에 ADD를 fetch한다.

    LOAD가 excute하면 ADD는 Decode되고 STORE가 fetch되는 식의 흐름이다.

    그러므로 PC의 계산을 그저 보이는대로 하면 안된다.




    이제 문제로 돌아와서 각각의 함수를 다시 보자



    < key1 ( ) >

    0x00008cdc의 주소에서 pc값을 r3에 넣었는데 현재 주소는 execute이다.

    pc는 항상 fetch하고 있는 곳을 가리키기 때문에 fetch를 가리키는 주소를

    찾아야 한다. 0x00008ce0의 주소는 decode이므로 0x00008ce4가

    바로 fetch가 될 것이다. fetch인 주소, 이 값이 바로 pc값이다.

    r0 = 0x00008ce4




    < key2( ) >

    0x00008d04는 execute이다. 0x00008d06은 decode고

    0x00008d08이 바로 fetch를 가리키는 pc값이 될 것이다.

    adds 명령어로 +4를 해주었기 때문에 pc값 +4가 r0가 될 것이고

    그 값은 0x00008d08+4 = 0x00008d0c가 된다.

    r0 = 0x00008d0c




    < key3( ) >

    마지막으로 key3( )는 지금까지 이용한 pc가 아니라

    lr을 r0로 리턴해주고 있다. lr은 함수호출 전의 복귀할 주소이므로

    main으로 돌아가서 진행될 주소값이 바로 r0가 된다.


    그러므로 0x00008d80가 바로 r0값이 된다.


    우리가 필요한 값들을 전부 더하면

    0x8ce4 + 0x8d0c + 0x8d80 = 0x1a770

    즉, 10진수로 108400이 된다. 그대로 입력해보자!!




    FLAG를 볼 수 있었다!! 풀 때는 몰랐는데

    배우게된 개념들이 굉장히 흥미로웠다~




    참고 https://umbum.tistory.com/500

    https://selpman.tistory.com/23

    http://egloos.zum.com/orangetigger/v/359478

    http://recipes.egloos.com/5027277

    https://hyunmini.tistory.com/80

    https://koyo.kr/post/pwnable-kr-leg/


    반응형

    '표튜터와 함께하는 Pwnable > Pwnable.kr Write-up' 카테고리의 다른 글

    [Pwnkr] Cmd1 Write-up  (0) 2019.04.04
    [Pwnkr] Lotto Write-up  (0) 2019.04.03
    [Pwnkr] Shellshock Write-up  (0) 2019.04.01
    [Pwnkr] Mistake Write-up  (0) 2019.04.01
    [Pwnkr] Random Write-up  (0) 2019.04.01

    댓글

Designed by Tistory.