티스토리 뷰

최종 수정: 2015-10-27

hackability@TenDollar


안녕하세요. Hackability 입니다.


이번에 포스팅 할 내용은 2015 CSAW 에서 Reversing 으로 출제된 FTP 라는 문제 입니다.


리버싱 문제이기 때문에 처음에 IDA로 구조를 익히는데 가장 시간이 오래 걸렷습니다. 구조를 분석한 결과 이 문제를 해결하기 위해서는 로그인만 하면 되었습니다.


그래서 Login 로직을 분석해본 결과

ID는 blankwall로 입력을 하고 Password를 특정 값으로 입력해야지만 맞출 수 있게 되어 있었습니다.

__int64 __fastcall sub_401540(__int64 a1)
{
  int i; // [sp+10h] [bp-8h]@1
  int v3; // [sp+14h] [bp-4h]@1

  v3 = 0x1505;
  for ( i = 0; *(_BYTE *)(i + a1); ++i )
    v3 = 0x21 * v3 + *(_BYTE *)(i + a1);
  return (unsigned int)v3;
}

여기서 return 값이 0xD386D209 가 되는 a1(제가 입력 줄 수 있는 값)을 찾는것이 문제 였습니다.  이 문제도 금방 해결할 수 있었는데 sign으로 안하고 unsigned으로 계산하다가 10시간 (진짜 10시간)이 가버렷습니다 ㅡㅡ......

일단 제가 접근한 방식은 8바이트 입력을 주면 (패스워드는 몇 바이트인지 모름 하지만 최대 8바이트) 하나씩 위의 포문으로 계산됨

따라서, 다음과 같은 식을 만들 수 있었습니다.  패스워드가 n 바이트라고 가정하면

0xd386d209 = 0x21^n + 0x21^(n-1) * x[0] + 0x21^(n-2) * x[1] ... 0x21^0 * x[n] 으로 일반화 시킬 수 있습니다.

파이썬으로 n 을 찾는 코드를 만들어 돌렷을 때 저는 6글자가 나와서 6바이트로 시도 했습니다. (다른 라이트업보시면 제 답과는 다를 겁니다 -_-....)

0xd386d209 = 0x21^6 + 0x21^5 * x[0] + 0x21^4 * x[1] + 0x21^3 * x[2] + 0x21^2 * x[3] + 0x21^1 * x[4] + 0x21^0 * x[5]

0x21^6은 상수 이기 때문에 양변에 이 수를 빼면

0x868C9548 = 0x21^5 * x[0] + 0x21^4 * x[1] + 0x21^3 * x[2] + 0x21^2 * x[3] + 0x21^1 * x[4] + 0x21^0 * x[5]

이런식으로 나옵니다.

그리고 x는 0~255 까지의 범위를 갖는다고 가정을 하면 (여기서 이 가정이 잘못됬었음 -_- 사실 127 까지 뿐이 못갖음)

x[0]가 갖을 수 있는 수는 0x868c9548 - x[0] * 0x21^5 < 0x21^4 * 255 를 만족 해야 합니다. 왜냐하면 다음항의 최대 수 보다 작아야지 x[1]이 결정될 수 있기 때문입니다.

이렇게 해서 계산을 해서 프로그램 돌려서 정확한 6자리를 구했는데 한 6시간 동안 계속 답이 안맞아서 삽질 하다가 나중에 보니깐 signed로 계산을 하길래 저도 계산을 signed 로 변환 시켜서 다시 계산을 했습니다. -_-

TEST CODE

#include <stdio.h>

unsigned int sub_401540(char *a1)
{
        int i; // [sp+10h] [bp-8h]@1
        int v3; // [sp+14h] [bp-4h]@1
        v3 = 5381;
        for ( i = 0; a1[i] ; ++i ) 
                v3 = 33 * v3 + a1[i];
        return (unsigned int)v3;
}

int main()
{
        unsigned int final = 0xd386d209;
        char test[7] = {0x58, 0xea, 0xe3, 0xf8, 0xd1, 0x16, 0x00};

        unsigned int res = sub_401540(test);
        printf("Answer : %08x \n", final);
        printf("Mine   : %08x \n", res);
}



자리수 찾는 코드

def get_n(n):
exp = 0x21
return exp**n * 0x1505

final = 0xd386d209
tmp = final
c = 0x1505
e = 0x21
n = 5

print "final = %s" % (hex(final))

for i in range(0, n-1):
x = get_n(n-i)
y = get_n(n-i-1) * 255
res = -1

for j in range(0, 256):
if tmp - (x*j) < y:
res = j
print "[%d] = %d (%s) < y = %s" % (i, j, hex(j), hex(y))
break

if res == -1:
print "not found"
break

tmp = (tmp - (x * res)) & 0xFFFFFFFF

print "[tmp] = %d (%s)" % (tmp, hex(tmp))




EXPLOIT CODE

import socket

#server = "localhost"
server = "54.175.183.202"
port = 12012

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))

print s.recv(1024)

s.send("USER blankwall\n")

print s.recv(1024)
print s.recv(1024)

s.send("PASS \x58\xea\xe3\xf8\xd1\x16\x00")

print s.recv(1024)

msg = "CWD /home/ctf/ftp2/"
s.send(msg)
print s.recv(1024)

msg = "PWD"
s.send(msg)
print s.recv(1024)
print s.recv(1024)

msg = "SIZE flag"
s.send(msg)
print s.recv(1024)

msg = "RDF"
s.send(msg)
print s.recv(1024)

s.close()

로그인 성공 후 커맨드로 RDF를 하면 바로 flag를 줍니다.



댓글