Radare2



Radare is a portable reversing framework that can...

· Disassemble (and assemble for) many different architectures

· Debug with local native and remote debuggers (gdb, rap, webui, r2pipe, winedbg, windbg)

· Run on Linux, *BSD, Windows, OSX, Android, iOS, Solaris and Haiku

· Perform forensics on filesystems and data carving

· Be scripted in Python, Javascript, Go and more

· Support collaborative analysis using the embedded webserver

· Visualize data structures of several file types

· Patch programs to uncover new features or fix vulnerabilities

· Use powerful analysis capabilities to speed up reversing

· Aid in software exploitation


[출처 : https://rada.re/r/]




분석을 시작하기 전에...



 $ rabin2 -I GuessTheString 

 arch     x86

 baddr    0x0

 binsz    11529

 bintype  elf

 bits     64

 canary   true

 class    ELF64

 crypto   false

 endian   little

 havecode true

 intrp    /lib64/ld-linux-x86-64.so.2

 lang     c

 linenum  true

 lsyms    true

 machine  AMD x86-64 architecture

 maxopsz  16

 minopsz  1

 nx       true

 os       linux

 pcalign  0

 pic      true

 relocs   true

 relro    full

 rpath    NONE

 static   false

 stripped false

 subsys   linux

 va       true

예제 바이너리는 noxCTF2018의 GuessTheString이다.

rabin2 -I filename 명령어를 통해 바이너리에 대한 정보를 알아낼 수 있다.

나는 매번 까먹거나 귀찮아서 리눅스 명령어인 file 을 사용한다.



 $ r2 GuessTheString


 $ r2 -d GuesTheString


 $ r2 -w GuesTheString

실행 명령어는 r2 filename 이다.

실행 할 때 -d 옵션을 붙이면 디버그 모드로 실행이 가능하다.

그러면 앞에 d가 붙는 Debugger commands를 사용할 수 있다.

-w 옵션을 붙이면 쓰기 모드로 실행이 가능하며 바이너리 수정이 가능하다.



이제부터는 radare2를 실행시킨 이후의 명령어들이다.


 [0x00000790]> aa

 [x] Analyze all flags starting with sym. and entry0 (aa)


 [0x00000790]> aaa

 [x] Analyze all flags starting with sym. and entry0 (aa)

 [x] Analyze function calls (aac)

 [x] Analyze len bytes of instructions for references (aar)

 [x] Constructing a function name for fcn.* and sym.func.* functions (aan)

 [x] Type matching analysis for all functions (afta)

 [x] Use -AA or aaaa to perform additional experimental analysis. 

aa는 'analyze all' 을 의미한다.

aaa는 위에도 보이듯이 aa와 함께 추가적인 명령어들을 실행시켜준다.

일반적으로 aa만 해도 괜찮다고 하는데 나는 혹시 몰라서 aaa를 실행한다.

뭔가 aaa가 더 좋은 느낌이 든다...






제대로 분석해보자



디버거 모드일 때 사용해도 상관은 없지만 정적 분석을 먼저 하고

필요하다 싶으면 디버거 모드를 이용하여 동적 분석을 하기 때문에 구분해두었다.


 [0x00000790]> afl

 0x00000000    3 73   -> 75   sym.imp.__libc_start_main

 0x000006e0    3 23           sym._init

 0x00000710    1 6            sym.imp.puts

 0x00000720    1 6            sym.imp.strlen

 0x00000730    1 6            sym.imp.__stack_chk_fail

 0x00000740    1 6            sym.imp.system

 0x00000750    1 6            sym.imp.strcspn

 0x00000760    1 6            sym.imp.fgets

 0x00000770    1 6            sym.imp.fflush

 0x00000780    1 6            sub.__cxa_finalize_780

 0x00000790    1 43           entry0

 0x000007c0    4 50   -> 40   sym.deregister_tm_clones

 0x00000800    4 66   -> 57   sym.register_tm_clones

 0x00000850    5 58   -> 51   sym.__do_global_dtors_aux

 0x00000890    1 10           entry1.init

 0x0000089a    1 21           sym.O0O00O00O0

 0x000008af    1 36           sym.O0OO0O0O0O

 0x000008d3   10 98           sym.OO0OO0OO0O

 0x00000935    5 68           sym.OOO00O0O00

 0x00000979    5 81           sym.O0OO0O0O00

 0x000009ca    3 64           sym.O000O00O00

 0x00000a0a    5 130          sym.OO000OO000

 0x00000a8c    7 127          sym.O0OO0O00OO

 0x00000b0b    5 111          sym.OOO00O00O0

 0x00000b7a    6 106          sym.OO00O0O000

 0x00000be4    4 78           sym.O00OOOO000

 0x00000c32    3 62           sym.OOOOO00O00

 0x00000c70    6 77           sym.O00OO0O0OO

 0x00000cbd   14 214          sym.O0000OOO00

 0x00000d93    6 241          main

 0x00000e90    4 101          sym.__libc_csu_init

 0x00000f00    1 2            sym.__libc_csu_fini

 0x00000f04    1 9            sym._fini

afl은 함수 목록을 보여준다.

나는 IDA로 보기때문에 잘 안쓰는 기능이다.



 [0x00000790]> pdf @main


 [0x00000790]> pd @0xd93

pdf는 함수의 디스어셈블리를 보여주는 기능을 한다.

pd는 함수가 아니더라도 디스어셈블리를 볼 수 있도록 해준다.

@는 명령이 실행되는 탐색 위치를 지정하는데 사용된다.

현재 탐색 위치는 0x790이지만, @를 이용하여 특정 주소의 디스어셈블리를 살펴볼 수 있는 것이다.



 [0x00000790]> afn func1 0xcbd

afn name [addr] 명령어로 함수 이름 변경이 가능하다.

afl 명령어 결과를 보면 0xcbd 주소의 함수 이름은 'sym.O0000OOO00' 라고 임의로 붙어있는데,

보기좋고 접근하기 편하도록 'func1' 이라는 이름으로 변경해주었다.



 [0x00000790]> s main


 [0x00000d93]>

s는 현재 탐색 위치를 지정하는 데 사용한다.

현재 탐색위치가 0x790이었는데 s main 명령어를 실행하자 0xd93로 바뀌는 것을 알 수 있다.

main 대신에 s 0x790 처럼 그냥 주소를 넣어도 무방하다.

afn과 s 명령어를 조합하여 바로바로 접근이 가능하기 때문에 많이 사용하는 편이다.




 [0x00000d93]> VV

VV(대문자 V2개)는 그래프 모드로 볼 수 있도록 도와주는 명령어이다.

명령어를 입력하면 위의 그림과 같은 화면으로 넘어가게 된다.


그래프모드에서는 방향키로 움직이며 디스어셈블리를 볼 수 있고,

그림에는 나와있지 않지만 현재 노드는 점선 테두리가 하늘색으로 표시된다.

tab 키를 이용하여 다음 노드를 선택하거나 노드의 맨 윗 부분에 적혀있는 [g] 명령어로 그 노드를 선택할 수 있다.

방향키로 디스어셈블리를 쭉 분석하다가 현재 노드로 돌아가고 싶으면 .(온점)을 누르면 된다.


p 키를 누르면 그래프 타입이 바뀌는데, 처음에는 BB-NORM으로 되어있다.

BB-OFF는 해당 어셈과 주소를 같이 보여주며, BB-SUMM은 큼직큼직한 함수 호출이나 문자열 같은 참조같은 것들을 간략하게 볼 수 있다.

나는 BB-NORM, BB-OFF, BB-SUMM만 사용하는 편이다.


x 키를 누르면 현재 노드의 참조 위치를 볼 수 있다.

방향키와 엔터를 이용하여 바로 그 위치로 이동할 수도 있으며 q 키를 누르면 나갈 수 있다.


q 키를 누르면 그래프 모드가 종료된다.



 [0x00000790]> px 0x30 @0xf14

 - offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF

 0x00000f14  456e 7465 7220 796f 7572 2073 7472 696e  Enter your strin

 0x00000f24  673a 2000 0a00 476f 6f64 206a 6f62 2e20  g: ...Good job. 

 0x00000f34  4865 7265 2069 7320 796f 7572 2066 6c61  Here is your fla

px 명령어는 hexdump를 보여주는 명령어이다.

px 0x30 @0xf14는 0xf14 주소부터 0x30개의 hex를 보겠다는 것을 의미한다.






String 관련 명령어



 [0x00000790]> fs

 0    4 * strings

 1   51 * symbols

 2   58 * sections

 3   20 * segments

 4   12 * relocs

 5   12 * imports

 6    1 * functions


 [0x00000790]> fs strings; f

 0x00000f14 20 str.Enter_your_string:

 0x00000f2a 30 str.Good_job._Here_is_your_flag:

 0x00000f48 9 str.cat_flag

 0x00000f51 17 str.Maybe_next_time

fs는 flagspaces를 보여주는 명령어이다.

string이 4개있고, symbol이 51개 있는 것을 알 수 있다.

fs strings; f를 이용하면 문자열 플래그들을 목록화하여 보여준다.



 [0x00000790]> axt @@ str.*

axt 명령어는 데이터/코드의 참조를 찾아주는 명령어이다.

@@는 명령이 실행될 플래그를 지정하는데 사용할 수 있다.

axt @@ str.*는 모든 문자열의 참조 명령어를 찾겠다는 의미이다.



 [0x00000790]> izzq~flag

 0xf2a 30 29 Good job. Here is your flag: 

 0xf48 9 8 cat flag

izzq~string 명령어는 문자열을 검색하는 명령어이다.



 [0x00000790]> /c 0xf2a

/c [addr] 명령어로 문자열이 어디서 참조되는지 알 수 있다.

/c instr 명령어가 instruction을 검색하는 명령어인데 문자열 주소 그대로 가져다 쓰지 않는 경우에는 아무 결과도 나오지 않으므로 주의해야한다.

izzq로 문자열을 검색하고 /c로 참조되는 주소를 찾곤 한다.






바이너리 패치



반드시 -w  옵션을 줘서 쓰기모드로 실행해야 한다.

평소에는 분석 모드나 디버그 모드로 radare2를 열기 때문에 바이너리 패치는 혹시나 테스트해보고 싶은 일이 있을 경우에만 사용한다.

실행한 바이너리 값이 수정되어 저장되므로 복사본을 만들어서 돌리는 것이 좋다.


 [0x00000790]> s 0xe23

 [0x00000e23]> pi 1

 je 0xe4e

 [0x00000e23]> px 0x10

 - offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF

 0x00000e23  7429 488d 3dfe 0000 00e8 dff8 ffff 488b  t)H.=.........H.

s 명령어로 먼저 현재 탐색 위치를 지정해준다.

0xe23에 있는 명령어는 je 0xe4e 이고, hex로 7429 이다.



 [0x00000e23]> wx 90

 [0x00000e23]> px 0x10

 - offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF

 0x00000e23  9029 488d 3dfe 0000 00e8 dff8 ffff 488b  t)H.=.........H.

wx 명령어를 이용하면 현재 탐색 위치의 값을 hex 값으로 수정할 수 있다.

wx 90 명령어로 0xe23의 hex값을 0x90으로 변경하였다.



 [0x00000e23]> wa jne 0xe4e

 Written 2 byte(s) (jne 0xe4e) = wx 7529

 [0x00000e23]> pi 1

 jne 0xe4e

 [0x00000e23]> px 0x10

 - offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF

 0x00000e23  7529 488d 3dfe 0000 00e8 dff8 ffff 488b  t)H.=.........H.

wa 명령어는 현재 탐색 위치의 값을 입력한 opcode로 수정한다.

wa jne 0xe4e는 원래 opcode였던 je 0xe4ejne 0xe4e로 변경하는 명령어이다.






디버그 모드



 반드시 -d  옵션을 줘서 디버그 모드로 실행해야 한다. 


 [0x7f83010eb030]> dc

디버그 모드로 실행하게 되면 왼쪽에 보여지는 주소가 위의 모드들과는 다르게 나타난다.

dc 명령어는 실행을 계속하는 역할을 하며,

맨 처음에 프로그램을 실행하거나 브레이크 포인트 때문에 멈춘 상황에서 다시 실행을 이어나갈 수 있게 해준다.



 [0x7f83010eb030]> db 0x5627380e6d93

 [0x7f83010eb030]> db main


 [0x7f83010eb030]> db -0x5627380e6d93

 [0x7f83010eb030]> db -main

 [0x7f83010eb030]> db-

db 명령어로 Breakpoint를 설정하거나 해제할 수 있다.

-를 붙이면 해제 할 수 있는데, 뒤에 주소를 적지 않으면 모든 Breakpoint들을 해제할 수 있다.



 [0x7f83010eb030]> dr

 rax = 0x000000e7

 rbx = 0x00000000

 rcx = 0x7f83010df858

 rdx = 0x00000000

 r8 = 0x0000003c

 r9 = 0x000000e7

 r10 = 0xffffffffffffff70

 r11 = 0x00000246

 r12 = 0x7f83010df838

 r13 = 0x7f83010e4e80

 r14 = 0x00000000

 r15 = 0x00000000

 rsi = 0x00000000

 rdi = 0x00000000

 rsp = 0x7ffd9cab6db8

 rbp = 0x7f83010df838

 rip = 0x7f8300de2909

 rflags = 0x00010246

 orax = 0x000000e7

dr 명령어는 디버그 레지스터 상태를 보여준다.



 [0x562199d4cd93]> afvd

 var local_8h = 0x7ffc5db73ca8  0x7c95cbccb7c68b00   .......|

 var local_30h = 0x7ffc5db73c80  0x0000000000000000   ........ @rsp r15

 var local_28h = 0x7ffc5db73c88  0x0000000000000000   ........ r15

 var local_20h = 0x7ffc5db73c90  0x0000000000000000   ........ r15

 var local_18h = 0x7ffc5db73c98  0x0000000000000000   ........ r15

 arg arg_30h = 0x7ffc5db73ce0  0x0000000000000000   ........ r15

디버그 명령어는 아니지만 디버그 모드에서 많이 써서 여기에 넣었다.

afvd 명령어는 로컬 변수들의 상태를 보여준다.

차례대로 자료형, 이름, 주소, 값, 아스키값을 보여준다.



 [0x5627380e6cbd]> dc

 child exited with status 0


 ==> Process finished

 

 [0x7f83010eb030]> ood

 [0x7f83010eb030]> ood arg1 arg2

프로그램의 끝까지 실행하고 나서 dc를 입력하면 'Process finished'를 출력한다.

이때, ood 명령어를 이용하여 디버그 모드로 다시 열기가 가능하다.

ood 명령어의 뒤에 인자들을 같이 써서 여는 것도 가능하다.


그런데 ood를 하고 나서 소스를 보면 invalid로 바뀌는 경우가 있다.

이는 코드 어딘가에 aslr이 있어서 그런 것이라고 한다 ☞ link

그럴 땐 r2를 종료하고 다시 실행하도록 하자....^^






마무리



radare2를 알고 나서 리버싱이 한결 쉬워진 것 같다.

명령어들이 전부 생각나지는 않지만 문제를 풀면서 자주 사용했던 유용한 명령어들을 정리해봤다.

맨날 까먹어서 구글에 하나하나 검색하는데 이거 보면서 열심히 하면 되겠다 ^^


드디어 글을 다 써서 홀가분하다.

미루고 미루고 미루다가 드디어 완성했다!

개강하기 전에 다 작성해서 다행이다.