REVERSING] Radare2

노션으로 옮김·2020년 5월 21일
1

Study

목록 보기
28/33
post-thumbnail

Radare2

바이너리 분석에 특화된 도구들로 이루어진 리버스 엔지니어링 프레임 워크이다.

Static, Dynamic 분석을 모두 가능하며 지원되는 플랫폼이 매우 많다. (무려 ARM도 지원, 안드로이드 분석에도 활용 가능!)

CLI 기반이고 명령어가 많아 복잡한데 Cutter라는 GUI 버전도 있다. 하지만 아직 CLI의 기능을 모두 옮기지는 못한듯.

Tutorials

다음의 페이지에서 제공하는 튜토리얼을 통해서 간단히 사용법을 익혀보겠다.

https://monosource.gitbooks.io/radare2-explorations/content/tutorials.html

한 가지 주의할 점은, 튜토리얼에서 제공하는 파일은 직접 make를 실행해서 바이너리를 생성해야 하는데 환경에 따라 PIE가 설정될 수 있다.

따라서 Makefile을 다음과 같이 수정해야 한다.

#Makefile
patchme: patchme.o
        gcc -m32 -o patchme patchme.o -no-pie

Tutorial 1 - Simple Patch

바이너리를 실행하면, 패치시켜서 함수호출을 시킬 수 있냐고 물어본다.

root@ubuntu:/work/exploits/radare2/radare2-explorations-binaries/tut1-patchme# ./patchme 
Hello there! Can you patch me up to call my function?

원본을 patchme_fix로 카피한 바이너리를 radare2로 로드시킨다.

r2 -Aw patchme_fix

먼저 main 함수를 보면 앞서 봤던 문자열 출력 코드 다음에 nop 코드가 보인다.

목표 함수를 찾아 이 nop 부분을 call XXX로 변경시키는 것이 최종 목표이다.

fafl로 radare2가 분석한 심볼이나 라벨 등을 확인할 수 있다.

[0x08048310]> afl
0x08048310    1 50           entry0
0x08048343    1 4            fcn.08048343
0x080482f0    1 6            sym.imp.__libc_start_main
0x08048370    4 50   -> 41   sym.deregister_tm_clones
0x080483b0    4 58   -> 54   sym.register_tm_clones
0x080483f0    3 34   -> 31   
...
...

하지만 이것으로 찾고자 하는 사용자 정의 함수를 명확히 분별하긴 힘들다.

문자열을 검색해본다.(iz : information strings)

[0x08048310]> iz
[Strings]
nth paddr      vaddr      len size section type  string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
0   0x0000101c 0x0804a01c 53  54   .data   ascii Hello there! Can you patch me up to call my function?
1   0x00001052 0x0804a052 10  11   .data   ascii Thank you!

Thank you!라는 문자열이 확인된다.

문자열이 참조되는 위치를 찾는다.

[0x08048310]> axt 0x804a052
(nofunc) 0x8048433 [DATA] push loc.secret

0x804833 seek하는데, 명령어의 결과값을 이용해 인자를 전달할 수 있다.

[0x08048310]> axt 0x804a052~[1]
0x8048433
[0x08048310]> s `axt 0x804a052~[1]`

함수가 아니므로 pdf로는 확인할 수 없고, Vp를 입력하여 visual mode disassembler를 확인한다.

현재 위치에서 조금 올라가면 함수 프롤로그를 볼 수 있는데, 함수로 지정되어 있지는 않다.

함수 시작주소에서 uf를 입력해 함수로 지정해준다.
그리고 ur로 이름을 callme로 변경한다.

그리고 nop 위치에서 A를 입력하면 어셈블리 코드를 입력할 수 있다.

call callme를 입력하여 패치한다.

q를 두 번 입력해 빠져나온 후, 해당 파일을 실행해보면

[0x08048430]> q
root@ubuntu:/work/exploits/radare2/radare2-explorations-binaries/tut1-patchme# ./patchme_fix 
Hello there! Can you patch me up to call my function?
Thank you!
root@ubuntu:/work/exploits/radare2/radare2-explorations-binaries/tut1-patchme# 

패치되어 callme()가 실행된 것을 확인할 수 있다.

Tutorial 2 - Memory Manipulation

비밀번호를 입력받고 일치 여부를 출력해주는 프로그램이다.

./xor
Enter the password: 1234
Wrong!

프로그램을 로드한다. -d는 디버깅 및 트레이싱을 하겠다고 명시해주는 옵션이란다.

root@ubuntu:/work/exploits/radare2/radare2-explorations-binaries/tut2-memory# r2 -Ad xor
Process with PID 23957 started...
= attach 23957 23957
bin.baddr 0x08048000
Using 0x8048000
asm.bits 32
glibc.fc_offset = 0x00148
[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] Check for objc references
[x] Check for vtables
[TOFIX: aaft can't run in debugger mode.ions (aaft)
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
 -- Use radare2! Lemons included!

izz는 모든 섹션의 문자열을 출력해준다.

[0x0804859b]> izz
[Strings]
nth paddr      vaddr      len size section   type    string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
0   0x00000028 0x00000028 4   10             utf16le 4 \t(
1   0x00000154 0x08048154 18  19   .interp   ascii   /lib/ld-linux.so.2
2   0x0000025d 0x0804825d 9   10   .dynstr   ascii   libc.so.6
3   0x00000267 0x08048267 14  15   .dynstr   ascii   _IO_stdin_used
4   0x00000276 0x08048276 12  13   .dynstr   ascii   __printf_chk
5   0x00000283 0x08048283 7   8    .dynstr   ascii   strncmp
6   0x0000028b 0x0804828b 14  15   .dynstr   ascii   __isoc99_scanf
7   0x0000029a 0x0804829a 4   5    .dynstr   ascii   puts
8   0x0000029f 0x0804829f 17  18   .dynstr   ascii   __libc_start_main
9   0x000002b1 0x080482b1 16  17   .dynstr   ascii   __stack_chk_fail
10  0x000002c2 0x080482c2 9   10   .dynstr   ascii   GLIBC_2.7
11  0x000002cc 0x080482cc 11  12   .dynstr   ascii   GLIBC_2.3.4
12  0x000002d8 0x080482d8 9   10   .dynstr   ascii   GLIBC_2.0
...
...

마땅한 정보는 없다.
dcu mainmain 직전까지 실행시키고 Vp로 코드를 확인한다.

스택에 순차적으로 저장되어 생성된 어떤 문자열과scanf()로 입력받은 문자열을 sym.check()에서 비교한다.

[0xf7fd6c70]> dcu main
Continue until 0x0804859b using 1 bpsize
hit breakpoint at: 804859b
[0xf7fd6c70]> Vp

내용을 확인하기 위해 dcu sym.check로 코드를 진행하고 sym.check의 코드를 확인한다.

위 캡쳐화면에는 안나와있지만, 인자가 두 개 전달되는데 하나는 입력값이고 다른 하나는 시리얼이다.

상위에서 arg_4로 전달되는게 입력값인데, 이것을 edx에 넣고 eax와 xor하고 있다.
eax는 처음에 0x2a로 초기화되고 루프를 돌며 3씩 더해진다. 42, 45, 48, etc... 가 되겠다.

루틴을 확인했으므로 이것에 맞게끔, radare2의 기능을 이용해 xor 연산된 값을 구할 수 있다.

[0x08048660]> woe 42 3 @ esi!32
from 42 to 255 step 3 size 1
[0x08048660]> ps @ esi!32
*-0369<?BEHKNQTWZ]`cfilorux{~\x81\x84\x87
[0x08048660]> p8 32 @ esi
2a2d303336393c3f4245484b4e5154575a5d606366696c6f7275787b7e818487

이것은 esi가 가리키는 위치에 0x2a부터 3씩 증가되는 값을 나열하여 저장시키는 것이다.
(esi가 어딜 가리키는지는 상관안하고 진행하는거다. 테스트를 위해서)

그래서 마지막에 보면 p8 32 @ esiesi에 저장된 값을 확인해보면 0x2a부터 3씩 증가되는 헥스값이 출력되고 있다.

이렇게 초기화한 key 값과 XOR 인코딩된 시리얼과 연산하면, 원본 시리얼을 구할 수 있을 것이다.

XOR 인코딩된 시리얼은 eax에 저장되어 있다.
(현재 eip의 위치는 dcu sym.check를 실행한 상태이므로 sym.check가 실행되기 직전이다.)

wox 명령어로 XOR 연산을 시킬 수 있다.

[0x08048660]> ps @ eax
gb~|mMT\x0b6\x1a?*{/$%i)\x14\x1a[\x0c\x0dZ\x0b*
J\x19\xe9\xb3\xda
[0x08048660]> wox `p8 32 @ edi` @ eax!32
[0x08048660]> ps @ eax
MONO[th4t_wa5~pr3tty=ea5y_r1gh7]

하지만!

현재 최신 버전의 radare2에서는 wox 명령어를 저렇게 사용할 수 없었다.

단순히 xor 시킬 1바이트의 헥스값만을 입력할 수 있다.
예를 들어

[0x8048660] wox 90

위 명령어를 실행하면 현재 radare2가 가리키는 위치인 0x8048660의 값과 90을 xor 시킬 것이다.

어쨌든.. 튜토리얼대로 시리얼을 구할 수는 없었다.
직접 다른 언어로 스크립트를 작성해야 한다..


Reference

https://en.wikipedia.org/wiki/Radare2#cite_note-13

0개의 댓글