beanstalk
먼저, license key 를 입력받고, 그 key 를 기반으로 어떤 값들을 생성하고, 그 값들중 어느 값만 추출해서 license key 를 비교한다.
맞으면, FLAG 를 입력받고, 어떤값들을 이용해 암호화한 뒤, 비교한다.
first step
license_to_key()
license 키 형식이 BEAN-XXXX-XXXXXXXXXX-XXXXXX
인지 검사하고
std::hex 를 이용해 하위 바이트부터 first_enc
에 1바이트씩 채운다.
ex : 1234-1234567890-123456 → 0x56341290785634123412 (메모리에는 0x12부터 차례대로 저장)
second step
Beanstalk::Beanstalk()
void __fastcall Beanstalk::Beanstalk(struct_this *this, const unsigned __int8 *license_key) { _QWORD *v2; // rbx __int64 v3; // rax int i; // [rsp+18h] [rbp-18h] int j; // [rsp+1Ch] [rbp-14h] this->bufs = operator new[](0x50uLL); for ( i = 0; i <= 9; ++i ) { v2 = &this->bufs[i]; *v2 = operator new[](0x100uLL); for ( j = 0; j <= 255; ++j ) { *(j + this->bufs[i]) = *(t() + (j ^ license_key[i])); } this->license[i] = *(this->bufs[i] + 0x77LL); }
this→bufs
에 배열포인터를 할당한 뒤,
this→bufs[i]
에 0x100 크기의 배열을 할당한다.
this→bufs[i][j]
에는 t()[j^license_Key[i]]
값들을 차례대로 채운다.
t()
함수는 k()
함수와 다르게 인자를 받지않고, opcode 의 주소
를 반환해서
j^license_key[i]
를 인덱스로해서 값을 가져온다.
다행히도, t()
함수에서 리턴하는 주소에 있는 opcode 들은 값이 중복되지 않아서 역연산이 가능하다.
이렇게 생성된 bufs 배열 중, 각 인덱스에 0x77
에 있는 값을 license 로 가져온다.
이 부분을 다시보자. Beanstalk
으로 license
값을 이용해 bufs 배열의 값들을 채워놓은 뒤,
각 인덱스의 0x77 에 있는 것을 this→license 배열에 넣고,
k()
함수로 opcode 10바이트의 위치를 리턴해 this→license 와 비교한다.
int *k(int x);
k(0) ~ k(1) 값과 생성된 license
값과 비교하는데,
이 함수가 좀 변태같다.
내부에 있는 sub_2605
를 호출하면, ret address 가 mov edi,0A5A~~ 부분을 가리키고 있는데,
그거를 rax 에 pop 한 뒤,
인자 x 를 0으로 주면, mov edi,0A5A~~
부분을,
인자 x 를 1으로 주면, mov edi,0A5A~~ + 0xa
부분을 리턴한다.
그렇다는 것은, mov edi,0A5A~~
부터의 opcode
들을 차례대로 가져와서 비교한다는 것 이다.
list(ida_bytes.get_bytes(here(),0xA))
위 명령어로 저 위치에 있는 opcode
를 가져올 수 있다.
license=[191, 11, 15, 162, 165, 148, 14, 215, 203, 133]
license[i] = t()[0x77^original_license[i]]
복호화 → t().index(license[i])^0x77 = original_license[i]
#BEAN-2A21-B91DC84834-24E676 for i in range(len(key)): original_license.append(t.index(license[i])^0x77)
third step
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(MyKey, v7, v9); std::operator<<<std::char_traits<char>>(&std::cout, "Flag: "); std::operator>><char>(&std::cin, MyKey); if ( std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::size(MyKey) <= 0x2F ) { std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::size(MyKey); if ( std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::size(MyKey) ) { for ( i = 0; i <= 7; ++i ) flags[i] = *std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::at(MyKey, i); Beanstalk::encrypt(this, flags, v19); f(); // flags[i] 에 MyKey[i+j] 차례대로 대입 (8바이트) // f()[i+j] 와 flags[i] 비교 (8바이트) // 틀리면 중간에 종료하고, 다 맞으면 j 에 8을 더하고, // for문 위에 if문으로 점프 v12 = std::operator<<<std::char_traits<char>>(&std::cout, "[+] Correct!"); std::ostream::operator<<(v12, &std::endl<char,std::char_traits<char>>); v5 = 0; }
알맞은 license key 를 입력하면 이 부분으로 온다.
여기서 디컴파일이 이상하게돼서 처음에는 플래그 8자리밖에 못 얻었는데,
이중 for문이였다.
f()
함수도 마찬가지로 함수 내의 opcode 주소를 리턴하는데 따로 설명은 안하겠습니다.
fourth step
unsigned __int8 *__fastcall Beanstalk::encrypt(struct_this *this, const unsigned __int8 *a2, unsigned __int8 *a3) { unsigned __int8 *result; // rax int i; // [rsp+28h] [rbp-8h] int j; // [rsp+2Ch] [rbp-4h] for ( i = 0; i <= 3; ++i ) *&this->flags[2 * i] = (a2[2 * i] << 8) | a2[2 * i + 1];// 6 7 4 5 2 3 0 1 srand((*(this->bufs[8] + 0xALL) << 16) | (*(this->bufs[5] + 14LL) << 8) | *(this->bufs[1] + 14LL) | (*(this->bufs[9] + 31LL) << 24)); Beanstalk::g0(this, 3); Beanstalk::z(this, 2, 3); Beanstalk::g1(this, 2); Beanstalk::z(this, 1, 2); ------------------------------------------------- Beanstalk::z(this, 3, 2); Beanstalk::g4(this, 2); Beanstalk::z(this, 2, 1); Beanstalk::g0(this, 1); Beanstalk::z(this, 1, 0); Beanstalk::g1(this, 0); for ( j = 0; j <= 3; ++j ) { a3[2 * j] = HIBYTE(*&this->flags[2 * j]); result = &a3[2 * j + 1]; *result = *&this->flags[2 * j]; } return result; }
처음에 딱 보고 멘붕이왔는데, 그냥 비트연산을 조금 많이하는 것 뿐이였다.
this→flags 배열에 내가 입력한 flag 를 2바이트씩 넣는데, 이때 순서를 바꿔서 넣는다.
ex : 0x3412 → 0x1234
그리고, srand 함수를 호출하는데, bufs 배열에 있는 값을 이용한다.
이미 알맞은 license key 를 구했으니, seed 값을 알 수 있다.
sr=(bufs[8][0xa]<<16)|(bufs[5][14] <<8) |(bufs[1][14]) |(bufs[9][31] << 24)
그 후, g0~g4, z
함수를 차례대로 호출하고, 다시 순서를 바꿔준다.
z(struct_this *this, int a1, int a2)
this→flags[2*n1] ^= this→flags[2*a2] ^ rand()
rand() 함수 리턴값을 구할 수 있으니, 그대로 다시 실행시키면 된다.
복호화 → this->flags[2*n1] ^= this->flags[2*a2] ^ rand()
근데, flag 를 뒤에서부터 복호화해야되기 때문에,
rand 값을 z 함수가 호출된 수 만큼 리스트에 넣어서, 뒤에서부터 rand 값을 사용해야한다.
(g 시리즈 함수에서는 rand 함수 사용안됨)
def z(n1,n2): global flags global c global rands flags[n1]^=flags[n2]^rands[c] c-=1
이런식으로 구현했다.
g 시리즈 함수
g0 → g(this,n,0,1,2,3)
g1 → g(this,n,4,5,6,7)
g2 → g(this,n,8,9,0,1)
g3 → g(this,n,2,3,4,5)
g4 → g(this,n,6,7,8,9)
다 똑같이 g 함수를 호출하는데, 나머지 4개 인자가 다르다.
g(n,l0,l1,l2,l3)
struct_this *__fastcall Beanstalk::g(struct_this *this, int n1, int l0, int l1, int l2, int l3) { this->flags[2 * n1] ^= *(this->bufs[l3] + this->flags[2 * n1]) << 8; this->flags[2 * n1] ^= *(this->bufs[l2] + (this->flags[2 * n1] >> 8)); this->flags[2 * n1] ^= *(this->bufs[l1] + this->flags[2 * n1]) << 8; this->flags[2 * n1] ^= *(this->bufs[l0] + (this->flags[2 * n1] >> 8)); }
언뜻 보면 복호화되지 않을 것 같았다.
하지만 2바이트씩 word 로 참조한다는 사실을 알면 복호화 하기 쉽다.
마지막 구문부터 보자.
this->flags[2 * n1] ^= this->bufs[l0][this->flags[2 * n1] >> 8];
flags[2*n1] 의 상위바이트를 인덱스로 해서 flags[2*n1] 와 xor 한다.
이 때, 상위바이트는 변형되지 않으므로, xor 하는 값을 알 수 있다.
그래서 그냥 한번 더 실행하면 복호화된다. ^^
그래서, encrypt 함수에서 호출하는 g시리즈 함수, z 함수에 대해 복호화함수를 만든 뒤,
뒤에서부터 호출하면 플래그가 복호화된다.
뭔가 걸리는게 있다면 rand 값인데,
encrypt 함수 내부에 srand 가 있기 때문에 8바이트씩 암호화 할 때 마다
srand 로 초기화해주기 때문에, c 변수를 31로 초기화 하면서 사용하면 된다.
full code
#!/usr/bin/python
from ctypes import *
rands=[]
r=CDLL("/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28")
#g0 0 1 2 3
#g1 4 5 6 7
#g1 8 9 0 1
#g3 2 3 4 5
#g4 6 7 8 9
def g0(n1):
global flags
flags[n1]^=bufs[0][flags[n1]>>8]
flags[n1]^=bufs[1][flags[n1]&0xff]<<8
flags[n1]^=bufs[2][flags[n1]>>8]
flags[n1]^=bufs[3][flags[n1]&0xff]<<8
def g1(n1):
global flags
flags[n1]^=bufs[4][flags[n1]>>8]
flags[n1]^=bufs[5][flags[n1]&0xff]<<8
flags[n1]^=bufs[6][flags[n1]>>8]
flags[n1]^=bufs[7][flags[n1]&0xff]<<8
def g2(n1):
global flags
flags[n1]^=bufs[8][flags[n1]>>8]
flags[n1]^=bufs[9][flags[n1]&0xff]<<8
flags[n1]^=bufs[0][flags[n1]>>8]
flags[n1]^=bufs[1][flags[n1]&0xff]<<8
def g3(n1):
global flags
flags[n1]^=bufs[2][flags[n1]>>8]
flags[n1]^=bufs[3][flags[n1]&0xff]<<8
flags[n1]^=bufs[4][flags[n1]>>8]
flags[n1]^=bufs[5][flags[n1]&0xff]<<8
def g4(n1):
global flags
flags[n1]^=bufs[6][flags[n1]>>8]
flags[n1]^=bufs[7][flags[n1]&0xff]<<8
flags[n1]^=bufs[8][flags[n1]>>8]
flags[n1]^=bufs[9][flags[n1]&0xff]<<8
def z(n1,n2):
global flags
global c
global rands
flags[n1]^=flags[n2]^rands[c]
c-=1
t=[163, 215, 9, 131, 248, 72, 246, 244, 179, 33, 21, 120, 153, 177, 175, 249, 231, 45, 77, 138, 206, 76, 202, 46, 82, 149, 217, 30, 78, 56, 68, 40, 10, 223, 2, 160, 23, 241, 96, 104, 18, 183, 122, 195, 233, 250, 61, 83, 150, 132, 107, 186, 242, 99, 154, 25, 124, 174, 229, 245, 247, 22, 106, 162, 57, 182, 123, 15, 193, 147, 129, 27, 238, 180, 26, 234, 208, 145, 47, 184, 85, 185, 218, 133, 63, 65, 191, 224, 90, 88, 128, 95, 102, 11, 216, 144, 53, 213, 192, 167, 51, 6, 101, 105, 69, 0, 148, 86, 109, 152, 155, 118, 151, 252, 178, 194, 176, 254, 219, 32, 225, 235, 214, 228, 221, 71, 74, 29, 66, 237, 158, 110, 73, 60, 205, 67, 39, 210, 7, 212, 222, 199, 103, 24, 137, 203, 48, 31, 141, 198, 143, 170, 200, 116, 220, 201, 93, 92, 49, 164, 112, 136, 97, 44, 159, 13, 43, 135, 80, 130, 84, 100, 38, 125, 3, 64, 52, 75, 28, 115, 209, 196, 253, 59, 204, 251, 127, 171, 230, 62, 91, 165, 173, 4, 35, 156, 20, 81, 34, 240, 41, 121, 113, 126, 255, 140, 14, 226, 12, 239, 188, 114, 117, 111, 55, 161, 236, 211, 142, 98, 139, 134, 16, 232, 8, 119, 17, 190, 146, 79, 36, 197, 50, 54, 157, 207, 243, 166, 187, 172, 94, 108, 169, 19, 87, 37, 181, 227, 189, 168, 58, 1, 5, 89, 42,70]
key=[191, 11, 15, 162, 165, 148, 14, 215, 203, 133]
lic=[]
bufs=[]
#BEAN-2A21-B91DC84834-24E676
for i in range(len(key)):
lic.append(t.index(key[i])^0x77)
for i in range(len(lic)):
temp=[]
for j in range(0x100):
temp.append(t[lic[i]^j])
bufs.append(temp)
sr=(bufs[8][0xa]<<16)|(bufs[5][14] <<8) |(bufs[1][14]) |(bufs[9][31] << 24)
r.srand(sr)
c=0
for i in range(32):
rands.append(r.rand()&0xffff)
c+=1
c-=1
flags=[70, 157, 250, 50, 81, 226, 101, 244]
flags+=[128, 198, 190, 179, 198, 110, 126, 60]
flags+=[101, 193, 53, 224, 17, 25, 13, 134]
flags+=[46, 147, 254, 234, 214, 103, 215, 177]
flags+=[205, 236, 82, 228, 83, 62, 59, 225]
flags+=[10, 253, 80, 126, 180, 248, 208, 67]
flags+=[70, 157, 250, 50, 81, 226, 101, 244]
def dec():
count=0
for i in range(0,8,2):
flags[count]=(flags[i+1]<<8) | flags[i]
count+=1
for i in range(4):
flags[i]=(flags[i]>>8)|((flags[i]&0xff)<<8)
g1(0);
z(1, 0);
g0(1);
z(2, 1);
g4(2);
z(3, 2);
g3(3);
z(0, 3);
g2(0);
z(1, 0);
g1(1);
z(2, 1);
g0(2);
z(3, 2);
g4(3);
z(0, 3);
z(3, 0);
g3(0);
z(0, 1);
g2(1);
z(1, 2);
g1(2);
z(2, 3);
g0(3);
z(3, 0);
g4(0);
z(0, 1);
g3(1);
z(1, 2);
g2(2);
z(2, 3);
g1(3);
g0(0);
z(1, 0);
g4(1);
z(2, 1);
g3(2);
z(3, 2);
g2(3);
z(0, 3);
g1(0);
z(1, 0);
g0(1);
z(2, 1);
g4(2);
z(3, 2);
g3(3);
z(0, 3);
z(3, 0);
g2(0);
z(0, 1);
g1(1);
z(1, 2);
g0(2);
z(2, 3);
g4(3);
z(3, 0);
g3(0);
z(0, 1);
g2(1);
z(1, 2);
g1(2);
z(2, 3);
g0(3);
for i in range(4):
flags[i]=(flags[i]>>8)|((flags[i]&0xff)<<8)
for k in range(6):
c=31
dec()
for i in range(4):
print(chr(flags[i]&0xff)+chr(flags[i]>>8),end='')
flags=flags[8:]
print("")