Dreamhack-rev-basic-8 문제 풀이

Posted by : at

Category : reversing



0. 메타데이터 확인


/assets/img/posts/2024-12-01-rev-basic-8/Untitled.png


PE64, C언어로 작성된 프로그램


1. 동적 분석


Input 나오고, 사용자 입력 받고, Correct / Wrong 판별해 보여줌

콘솔에서 stdin 받음


2. 정적 분석


역시나 똑같음

/assets/img/posts/2024-12-01-rev-basic-8/Untitled_1.png


이제 sub_140001000을 들여다보자.


2.1. sub_140001000

/assets/img/posts/2024-12-01-rev-basic-8/Untitled_2.png


안예뻐

/assets/img/posts/2024-12-01-rev-basic-8/Untitled_3.png


이젠예뻐

byte_140003000 안에 담긴 데이터는 아래와 같음

AC F3 0C 25 A3 10 B7 25 16 C6 B7 BC 07 25 02 D5 C6 11 07 C5


궁금한 점. 왜 -5를 곱한 걸까? 이 문제의 의도가 뭘까?

1byte 자료형이 마이너스를 표현하는 방법에 대해 알아보라는 뜻 아닐까?

흠… 진짜 왜지?


2.2. 2’s complement


이진수의 모든 자리 숫자를 반전시키고, 1을 더해서 2의 보수를 구한다.

2의 보수인 수는 원래 숫자의 음수로 취급된다.

(자세한 건 생략)


2.3. sub_140001000 내부 비교 루틴(어셈블리)


  for ( i = 0; (unsigned __int64)i < 0x15; ++i )  {    if ( (unsigned __int8)(-5 * usr_input[i]) != byte_140003000[i] )      return 0i64;  }


뭔가 감이 안 오는 것 같으면 어셈을 한 번 보는 것도 좋으니, 어셈으로 위 비교문의 if 조건을 보자.

movsxd  rax, [rsp+18h+var_18]mov     rcx, [rsp+18h+arg_0]movzx   eax, byte ptr [rcx+rax]imul    eax, 11111011band     eax, 11111111bmovsxd  rcx, [rsp+18h+var_18]lea     rdx, byte_140003000
movzx   ecx, byte ptr [rdx+rcx]cmp     eax, ecxjz      short loc_140001053


imul~and 까지가 가장 중요한 부분이다.

앞서 설명한 2’s complement를 감안할 때, imul 의 operand 2는

0x11111011(2) == -0x00000101(2) == -5(10)

and 연산의 이유는 뭐지?


아 r계열 레지스터(64bit) 에 e계열 레지스터인 것처럼 값을 넣었으니까(32bit) 상위 32bit의 값을 정리해 주려고 하는 거구나

  • 5 곱하는 이유…char 자료형이니 8bit 내에서 일어나는 일을 보고 싶은 것 같은데

이걸 어떻게 표현해야 하지?


imul이라는 레지스터가 중요함. 이건 부호 연산을 할 수 있는 레지스터임 → 이걸 썼다는 것은 음수를 썼을 가능성이 높다는 뜻

동적 분석은 보통 x64dbg 사용한다

연산하고 and 연산이 추가되는 경우는, 하위 n 비트를 구하기 위한 목적이 크다. 형변환이라든가… 자료형(크기) 다른 포맷으로 변경한다든가….


2.3. 난관에 봉착하다


  • 5*usr_input == 정답배열이어야 하는데

이걸 뒤집으면

5 * usr_input == -정답배열

즉, 정답배열에 2’s complement를 취한 걸 정답’ 라고 하면

뭘 5번 덧셈해야(ALU는 덧셈연산밖에 못하니까) 정답’ 이 되는지 나와야 하는데, 안 나온다.

심지어 usr_input은 char type라서 1byte짜리로, 부동소수점 타입도 안 된다.


2.4. 난관에서 빠져나왔다


어차피 1byte짜리니까, 0~255까지 모든 숫자를 넣어보면서,

  • 5*usr_input == 정답배열을 충족하는 0x15짜리 길이 문자열을 도출해도 된다고 생각했다.

브루트포싱은 아래와 같이 했음

tmp=['AC F3 0C 25 A3 10 B7 25 16 C6 B7 BC 07 25 02 D5 C6 11 07 C5']
tmp2=tmp[0].split(' ')
byte_140003000=[]
solve=[]
for i in tmp2:
    byte_140003000.append(int(i, 16))
for i in byte_140003000:
    solve.extend(brute_force for brute_force in range(0, 256) if ((brute_force*0xFB)&0xFF == i & 0xFF))
print(solve)
solve_tmp=[chr(i) for i in solve]
solve_string=[]
solve_string.append(''.join(solve_tmp))
print(solve_string)


플래그 획득까지 완료


3. 질문.


그런데 왜 역산이 불가능하지?

개인적으로 추측해 보았다.

1byte로 표현하는 수라면, 0~255 까지를 한 주기로 하는 정수의 모듈러 군이다.

모듈러 군 내에서 곱셈은 일대일 함수가 아니므로 곱셈과 나눗셈의 역산 관계가 성립되지 않는다. 하나의 역원 존재 불가

곱셈이고 나눗셈이고 덧셈의 연장인데 왜? 라고 생각할 수 있을 것 같다. 그러나 모듈러 연산이 적용되는 닫힌군이라는 게 이 역산 불가의 원인으로 작용하므로… 음수는 의미없고

이런 원리를 이용한 게 PKI이고.

즉 밑을 상대적으로 작게(255) 들고 가는 모듈러 집합이라 5라는 작은 수를 곱해도 80% 이상 확률로(해당 군의 약 20%인 0~50까지는 5를 곱해도 255 이상이 아님) 나머지 연산이 이루어질 수 있기 때문에 어쩔 수 없이 (1-0.8^0x14) * 100 == 98.845% 의 확률로 복원 불가능한 정보임

따라서 브루트 포싱만이 답이라고 생각함.



About TouBVA
TouBVA

A Security Researcher, now concentrating on Security Consulting.

Email : touBVa@gmail.com

Website : https://toubva.github.io