CTF | wargame

labyrenth 2017 / Mobile 2 - routerlocker (mips 리버싱, gdb set follow-fork-mode child)

nopdata 2018. 2. 27. 21:33


mips가 mobile로 분류 되어 있다. 환경을 맞추고 실행을 시켜 보면 위와 같이 나온다.
License file not found로 봐서는 파일을 읽어 들이는 것으로 보인다.

[ IDA - fopen (loc_400908) ]

mips에서 파라미터가 들어가는 형태는 a0, a1을 사용하는 듯 하다. 형태를 좀 자세히 알기 위해서 fopen의 원형을 보면

FILE *fopen(const char *filename, const char *mode);

이와 같은 형태를 띈다. IDA에서 출력한 정보를 바탕으로 보면 

fopen($a0, $a1)의 형태를 지닌다.
mode로는 0x34(이거 찾아야 되는데...) filename으로는 li, sb명령으로 구성된 데이터를 사용한다.

mips에서 배열 조회는 인덱스(배열명)의 형태로 한다. 예를 들어 a[10]이라고 한다면 10(a)로 사용을 하게 된다.
li는 load immediate, sb는 store byte를 의미한다.

li로 값을 변수($v0)에 저장하고, 변수에 있는 값을 배열($fp)에 인덱스에 맞추어 저장한다. 인덱스 넘버는 52번부터시작하며 다 모아 조합을 하면 /tmp/router.lck가 나온다.
여기서 fp는 frame pointer로 새 프레임 시작 부분을 보면 $sp를 이용해서 프레임 위치를 잡는 것을 볼 수 있다.
addiu는 3개의 인자를 받는데,

addiu $v0, $v1, $v2

라고 한다면 $v0 = $v1+$v2가 된다. 따라서, $v1에는 $fp+52가 저장된다.


mode값인 $a1은 이전에 설정이 되는데 move $a1, $zero로 0이 셋팅된다.

/tmp/router.lck파일을 생성하고 프로그램을 다시 실행 시키면 이번에는
License file invalid. 로 메시지가 변경된다.

파일을 읽어들여 비교하는 구문이 존재하는 듯 하다.

이번에는 fread를 찾아본다.

[ IDA - fread (loc_400A90) ]
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
fread($fp+100, 1, 29, $fp+36)

파일에서 29만큼의 문자를 읽어 $fp+100에 저장한다. 여기서 $fp+36에 파일 디스크립터가 존재한다.

[ IDA - loc_400B1C ]

다음 흐름인 loc_400B1C를 보면 파일 디스크립터를 닫고, 특정 li, sb연산을 한다. 파일 이름을 만들어 냈듯이, 위치에 맞추어 하나씩 생성하는 형태이다.
예를 들어 가장 첫 값인 0xffffff88을 보면
fp[76] = (0xffffff88 & 0xff)과 같은 형태를 띈다. 아래 스크립트를 이용해서 뽑아내면 

sb = [76, 78, 71, 88, 72, 85, 82, 80, 92, 90, 83, 68, 95, 89, 74, 79, 94, 96, 75, 69, 81, 87, 91, 84, 73, 77, 70, 93, 86]
li = [4294967176L, 10, 4294967263L, 4294967184L, 4294967262L, 121, 4294967226L, 37, 4294967196L, 4294967220L, 22, 4294967234L, 106, 4294967279L, 4294967189L, 84, 93, 115, 4294967213L, 27, 94, 4294967262L, 4294967232L, 6, 4294967261L, 65, 4294967236L, 4294967195L, 56]
for i in range(len(li)):
    if li[i] > 255:
        li[i] = li[i]&0xff
sb = map((lambda x:x-68),sb)
result = [0]*29
for i in sb:
    result[i] = li.pop(0)
result = ''.join(map(chr,result))

print result

c21bc4dfdedd95ad88410a54255eba16067938de90efb4c09c9b5d6a73가 나온다.
해당 문자열의 위치는 $fp+68 ~ $fp+96으로 29자리이다.

[ IDA - loc_400D10 ]

그 다음 흐름이다. $fp+100에 있는 값의 길이를 확인한다.(strlen)
이전에서 보았듯이 $fp+100에는 파일(/tmp/router.lck)에서 읽은 문자열의 길이가 저장되어 있다.

strlen의 결과가 $v0에 저장되는 듯 하다. 좀 정확히 알기 위해서 gdb를 이용하기로 했다. 근데 해당 부분에 bp를 걸어도 멈추지 않는다.....
알고보니 첫 부분에 다음과 같이 fork로 생성하는 부분이 있었다.

[ IDA- main 시작부 ]

찾아보니 gdb 설정 중 set follow-fork-mode child를 이용하면 자식 프로세스도 디버깅이 가능하다고 한다.

[ gdb - main+1128 ]

main+1128은 strlen이 호출되는 jal strlen 부분이다. strlen 실행 후, 값을 비교해보면 위에서 추측한 대로 $v0의 값이 0x1d로 strlen의 결과가 저장된 것을 볼 수 있다.
loc_400D10의 strlen 다음을 보면 sltu 구문이 있다. sltu의 원래 구문은 sltu $d, $s, $t로, 3개의 인자가 필요하다.
gdb로 비교를 해 본 결과, sltu $v0, $v0, $s0로 되어 있다. 아마, 결과가 저장되는 $v0여서이거나 2번째 인자와 같아서 그런 듯 하다.


[ IDA - loc_400c18 ]

다음 분기이다. 성공, 실패로 갈리는 주요한 부분이다. 마지막에, beq로 $v1, $v0가 같으면 성공으로, 같지 않으면 실패로 분기한다.
beq위치에 bp를 잡고 확인을 해 보면 값은 다음과 같다.

[ GDB - main+976 ]

보면 $v0는 74가, $v1에는 c2가 들어있다. 처음에 스크립트를 통해 만들어낸 문자의 값이 c2이다.
chr(0x74)는 't'가 되며, 이 문자를 lck파일의 첫 문자로 바꿔서 다시 디버깅을 해 보니

[ GDB - main+976 ]

이번에는 $v0의 값도 c2가 되었다. loc_400c18을 처음부터 다시 분석해보면 먼저 $fp+31의 값을 가져온다. 처음에 이 값은 0으로 설정 되어 있는데 이 값은 현재 비교 문자의 인덱스 번호로 보면 된다.
sll연산은 shift lefg logical로 마지막 인자 값 만큼 shift left를 한다. 기타 and 연산을 하고 난뒤, la $v0, runtime_pad라는 명령을 한다.
la $v0,runtimepad는 ida에서와 gdb에서 출력 형태가 조금 다르다.
결과적으로는 li $v0,0x411040명령과 같으나 gdb에서는 
lui v0,0x41
addiu v0,v0,4160

두 개의 명령으로 나뉘어 실행이 된다. 그 다음 연산을 보면 0x411040+?를 이용하여 값을 runtimepad에서 가져온다.
결과적으로 보면 runtimepad가 키 값이 된다. 마지막 부분 분석을 하다가 보니 runtime pad와 위에서 생성한 암호문의 xor연산을 시키면 문자열이 나온다는 것을 알았다.

최종적으로 나온 스크립트

runtimepad = [182, 165, 2, 182, 221, 115, 55, 53, 95, 35, 165, 201, 176, 141, 233, 171, 129, 163, 223, 12, 128, 175, 180, 125, 99, 121, 244, 92, 79, 22, 19, 195, 251, 190, 94, 48, 34, 46, 130, 102, 27, 222, 103, 193, 153, 76, 190, 35, 68, 180, 123, 245, 231, 44, 65, 105, 209, 120, 223, 219, 69, 91, 207, 73, 116, 7, 19, 143, 64, 24, 231, 230, 206, 250, 86, 55, 252, 116, 51, 129, 227, 220, 164, 12, 68, 128, 87, 186, 62, 249, 217, 167, 180, 94, 205, 165, 235, 68, 91, 239, 70, 243, 91, 109, 207, 224, 56, 167, 199, 184, 106, 24, 22, 225, 197, 3, 30, 24, 82, 222, 132, 246, 39, 240, 96, 45, 39, 75]
sb = [76, 78, 71, 88, 72, 85, 82, 80, 92, 90, 83, 68, 95, 89, 74, 79, 94, 96, 75, 69, 81, 87, 91, 84, 73, 77, 70, 93, 86]
li = [4294967176L, 10, 4294967263L, 4294967184L, 4294967262L, 121, 4294967226L, 37, 4294967196L, 4294967220L, 22, 4294967234L, 106, 4294967279L, 4294967189L, 84, 93, 115, 4294967213L, 27, 94, 4294967262L, 4294967232L, 6, 4294967261L, 65, 4294967236L, 4294967195L, 56]
for i in range(len(li)):
    if li[i] > 255:
        li[i] = li[i]&0xff
sb = map((lambda x:x-68),sb)
enc = [0]*29
for i in sb:
    enc[i] = li.pop(0)
result = ""
for i in range(0,29):
    indx = ((i<<2) & 0xff) + ((i & 3) & 0xff)
    result += chr(runtimepad[indx] ^ enc[i])
print result



[ routerlocker - 성공 화면 ]
flag : PAN{that_ransomware_ran_somewhere}

재미있군