Description

Category: Crypto

Source: TokyoWesterns CTF 2018

Points: 154

Author: Jisoon Park(js00n.park)

Description:

(None)

Write-up

별도의 문제 설명은 주어지지 않았다. 주어진 ruby 코드를 살펴보자.

require 'openssl'

e = 65537
while true
  p = OpenSSL::BN.generate_prime(1024, false)
  q = OpenSSL::BN.new(e).mod_inverse(p)
  next unless q.prime?
  key = OpenSSL::PKey::RSA.new
  key.set_key(p.to_i * q.to_i, e, nil)
  File.write('publickey.pem', key.to_pem)
  File.binwrite('flag.encrypted', key.public_encrypt(File.binread('flag')))
  break
end

RSA-2048 keypair를 생성하는데, 오리지널 알고리즘과는 다르게 p와 q 사이에 의존성이 있다.
이후의 코드에 별다른 특이점이 보이지 않는 것으로 보아, 이 의존성을 이용하는 문제인것 같다.

p는 1024bit 난수이고, q는 e의 p에 대한 역수이므로, 다음과 같이 쓸 수 있다.

q = e-1 mod p
=> eq = 1 mod p
=> eq = kp + 1

p와 q가 비슷한 크기(bit length)의 수라고 하면, k와 e도 비슷한 크기를 가질 것이다. e가 65537로 17 bit의 수이니, k도 brute force를 이용해서 찾을 수 있을 정도의 크기라고 생각해 볼 수 있다.

식을 좀 더 정리해보자.

eq = kp + 1
=> eqq = kpq + q
=> eqq - q - kpq = 0
=> eq2 - q - kN = 0

q에 대한 2차 방정식을 얻었다. 2차 방정식 ax2 + bx + c = 0 의 해는 근의 공식으로 구할 수 있다. 기억이 가물가물하니 잠시 추억을 되살려보자.

얻어진 식을 근의 공식에 대입해보면, a = e, b = -1, c = -kN 이다.

q는 정수이므로, 근의 공식에서 제곱근 계산 결과가 정수이어야 하고, 분자 부분은 분모의 배수이어야 한다. 이 성질을 이용해서 k를 찾기 위해 아래와 같은 exploit을 작성하였다.

((a, b) = iroot(c, d)는 c의 d 제곱근을 구하는 함수이며, 계산 결과a와 제곱근이 정수인지 나타내는 boolean 값인 b를 리턴한다.)

from Crypto.PublicKey import RSA
from gmpy2 import iroot
from Crypto.Util.number import *

keypair_pem = open("publickey.pem", "r").read()
keypair = RSA.importKey(keypair_pem)

ct = open("flag.encrypted", "rb").read()

t = 4 * keypair.e * keypair.n
for k in range(1, 1000000):
  (y, b) = iroot(1 + t * k, 2)
  if b and (1 + y) % (keypair.e * 2) == 0:
    print k
    q = (1 + y) // (keypair.e * 2)
    break

p = keypair.n // q
piN = (p - 1) * (q - 1)

d = inverse(keypair.e, piN)

'''
pt = pow(bytes_to_long(ct), d, keypair.n)
print long_to_bytes(pt)
'''
#same as above
priv = RSA.construct((keypair.n, keypair.e, long(d)))
print priv.decrypt(ct)

#RSAES-PKCS1_v1_5 padding example
from Crypto.Cipher import PKCS1_v1_5
cipher = PKCS1_v1_5.new(priv)
print cipher.decrypt(ct, 0)

간단한 brute forcing 만으로 k는 쉽게 찾을 수 있었다.
(p에 대한 2차 방정식으로 정리해도 동일하게 풀 수 있다.)

plain RSA를 이용해서 decryption 했을 때 앞에 지저분한(?) 것들이 보이고 뒤에 flag 같은게 보인다. hex 값을 찍어보면 0x02로 시작하는 걸로 보아 ruby에서 RSAES-PKCCS#1_v1.5 padding을 붙인 것 같다.
그냥 flag만 발췌해도 되지만 나중에 참고용으로 RSAES-PKCCS#1_v1.5 decryption code도 exploit에 포함했다.

Flag : TWCTF{9c10a83c122a9adfe6586f498655016d3267f195}

'writeups > Crypto' 카테고리의 다른 글

Decode This  (0) 2019.11.25
:)  (0) 2019.11.25
drinks  (0) 2019.11.25
RSAaaay  (0) 2019.11.23
Very Smooth  (0) 2019.11.23

Description

Start(KST): 2019/01/31 17:00

Duration: 85 hours

Final Rank: 84


WebCipher

Category: Scripting/Coding

Points: 300

Description:

To verify that only computers can access the website, you must reverse the Caesar cipher There are a list of possible words that the cipher may be here

https://challenges.neverlanctf.com:1160

Point

  • Caesar cipher를 이해하고 복호화 할 수 있는가

Write-up

문제 사이트로 들어가보면, Caesar Cipher를 이용해서 암호화된 문자열이 주어져 있다.

Caesar Cipher는 subsitutuin cipher 중의 하나로, 일반적으로 알파벳 n번째 문자를 n + k % 26번째 문자로 치환하는 방법으로 사용한다.

주어진 암호문은 jllnunajcxa인데, k를 모르니 적당한 문자가 나올 때까지 k를 바꿔가며 평문을 구해보자.

연습삼아 손으로 해보는 것도 좋겠지만 조금만 검색해보면 수많은 온라인 도구들을 찾아볼 수 있다.

k값에 따라 다양하게 decryption 된 문자열들 중에 하나를 골라보자. 문제에서 list of possible words가 있다고 했으니 뭔가 의미가 있을 것 같은 값으로 accelerator를 골라서 제출하면 flag를 얻을 수 있다.

Flag : flag{H3llo_c4es3r}

References

  • Caesar Cipher Online Decryptor: https://www.dcode.fr/caesar-cipher
  • Online en/decoder: https://cryptii.com/pipes/caesar-cipher



Unexpected intruder

Category: Recon

Points: 50

Description:

occurring in Chicago, Illinois, United States, on the evening of November 22. There was an interruption like nothing we had ever seen before.

What was the name of the Intruder?

Point

  • 인터넷 검색을 할 수 있는가?

Write-up

11월 22일 저녁에 시카고에서 interruption이 일어났었다고 한다. 침입자의 이름을 알아보자.

Chicago November 22 intruder로 검색해보면 1987년 11월 22일에 있었던 Max Headroom의 공중파 방송 탈취 사건에 대한 내용을 쉽게 찾을 수 있다.

Max Headroom을 submit 해보았더니 flag가 아니어서, MaxHeadroom으로 붙여서 넣었더니 포인트를 획득할 수 있었다.

Flag : MaxHeadroom

References

  • Max Headroom on wiki: https://en.wikipedia.org/wiki/Max_Headroom_broadcast_signal_intrusion



purvesta

Category: Recon

Points: 75

Description:

I love Github. Use it all the time! Just wish they could host a webpage...

Point

  • GitHub에서 특성에 맞는 repository를 찾을 수 있는가

Write-up

CTF에서 잘 찾아보기 힘든 Recon 카테고리의 문제이다. Recon은 reconnaissance(정찰)의 약자로, 사회망 등의 검색을 통해 얻은 정보를 이용하여 푸는 문제인 것 같다.

출제자는 깃허브를 매우 좋아한다고 한다. 별다른 힌트가 없는 것 같으니 일단 깃허브에서 문제 제목인 purvesta를 검색해 보자.

purvesta라는 사람의 계정이 검색되는데, 들어가 보면 본인이 NeverLAN CTF의 cofounder라고 친절하게 소개하고 있다.

이 계정에는 public repository 8개가 있는데, 문제에서 webpage 언급이 있으니 그 중에 purvesta.github.io로 들어가본다. ([account].github.io 저장소는 http webserver 기능을 제공한다.)

purvesta.github.io 저장소에는 파일이 달랑 3개가 있는데, 가장 수상해 보이는 lol 파일을 열어보면 flag를 발견할 수 있다.

Flag : flag{Th1s_g1thub_l00ks_a_l1l_sparc3}

References

  • (없음)



Filling a need

Category: Recon

Points: 100

Description:

This organizations creation was announced Mon Sep 24 2001

What is the full name of the organization?

Point

  • 인터넷 검색을 할 수 있는가?

Write-up

2001년 9월 24일에 설립이 발표된 기관은 무엇인지 찾아보자.

처음엔 그날 설립된 기관으로 알고 검색하여 INTERPOL을 submint 했으나 아니었다.

문제를 다시 읽어보니, 설립된 날이 아니라 설립이 발표된 날이었다.

Mon Sep 24 2001 announced organization으로 검색한 결과, OWASP history 페이지가 나왔는데 OWASP도 flag가 아니었다.

잘못짚은건가 싶어서 내용을 좀 더 들여다 보았더니 아래와 같은 내용이 있었다.

Date: Mon Sep 24 2001 - 01:52:35 CDT
(...중략...)
So we are pleased to announce the creation of the "Open Web Application Security Project" known as OWASP.

OWASP 대신 full name인 OpenWebApplicationSecurityProject을 submit 했더니 포인트를 줬다.

Flag : OpenWebApplicationSecurityProject

References

  • History of OWASP: https://www.owasp.org/index.php/History_of_OWASP



It's to KeyZ

Category: Recon

Points: 250

Description:

It looks like N30 has been keeping passwords secret with some software he wrote, but he should know better than to rely on proprietary software for security.

It looks like he left the repo public too!

passwords.keyz

Point

  • 주어진 정보로부터 추가적인 개인 정보를 수집할 수 있는가
  • C++ 코드를 읽을 수 있는가

Write-up

잘 모르겠으니 일단 주어진 파일을 좀 살펴보자.

맨 앞에 있는 것은 매직 코드와 헤더인것 같고, 00이 한참을 이어지다가 0x04, 0x1f, 그리고 flag라는 문자열과 정체모를 31 바이트 데이터가 있다.

데이터 파일의 전형적인 Length - Type(Header or Key) - Value 구조인 것 같다. 그 말은 "flag" 이후에 나오는 31 바이트를 어떻게든 풀어야 한다는 뜻이다.

이리저리 xor를 시도해 보았으나 성공적인 결과는 얻지 못했다. 역시 카테고리가 Recon인 만큼, 검색을 통한 추가 데이터 입수가 필수인 듯 하다.

문제 지문을 유심히 읽어보면 N30이라는 사람의 public repo를 찾아야 하는 것 같다.

구글신의 힘을 빌려 N30 keyz를 키워드로 검색해 보았으나 별다른 소득이 없었다.

이리저리 사이트를 뒤적이다가 NeverLAN CTF의 홈페이지에 가보니 About - THE CREATORS 메뉴에 N30에 대한 정보가 있었다.

Linked in의 정보에 따르면 N30의 이름은 Zane Durkin이라고 한다. Zane Durkin KeyZ로 다시 한번 검색을 해보면 구글이 KeyZ를 Key로 마음대로 수정해서 검색하는 바람에 제대로 된 결과를 찾기 어렵다.

Zane Durkin Github로 다시 한번 검색해보면 N30의 Github 계정을 찾을 수 있다. 감사한 마음으로 들어가보면 KeyZ라는 저장소를 볼 수 있고, key.cpp 파일을 통해 .keyz 파일의 구성을 비로소 확인할 수 있다.

key.cpp 코드를 쭉 살펴 보면 passwords.keyz 파일의 마지막 31 바이트는 특별히 암호화 된 것이 아니라 그냥 smazz라는 것을 이용해서 압축해둔 것인걸 알 수 있다.

같은 저장소에서 smazz.cpp 파일을 받아 우리가 궁금한 데이터를 얻어내는 main() 함수를 아래와 같이 추가하여 컴파일한 후 실행해보자.

int main(void)
{
    char *comp = "\x2c\x89\x3b\xfe\x7b\x5a\x26\xff\x02\x37\x5f\x31\x03\x0a\xfe\x5f\x3c\x0c\xfe\x30\x3c\x0c\xfe\x31\x02\xc8\x0c\x53\xfe\x7d\x31";
    char decompressed[1024];
    int l = smaz_decompress(comp, 31, decompressed, 1024);

    printf("%s\n", decompressed);

    return 0;
}

압축 해제 결과, 25 byte의 flag를 얻을 수 있었다. 나머지 6 byte는 그냥 쓰레기 데이터인가보다.

Flag : flag{bu7_1ts_pr0pr1etary}

References

  • (없음)



Binary 1

Category: Binary

Points: 100

Description:

A user accidentally installed malware on their computer and now the user database is unavailable. Can you recover the data and the flag?

Flag is All Caps

users_db

Point

  • 문자열 인코딩을 다룰 수 있는가

Write-up

제공된 파일을 열어서 살펴보자.

한 줄에 60개쌕의 문자열이 있는데, [0-9a-f]로만 이루어진 hexa string이다.

두 글자씩 모아서 파싱해보면 ascii code가 나오고, 그에 맞는 문자열들을 다시 모아보면 base64 문자열인 것을 알 수 있다.

base64 디코딩을 하려고 하면 제대로 된 base64 문자열이 아니라고 하는데, 모든 라인들을 하나로 모아서 다시 디코딩을 시도하면 정상적으로 디코딩이 되고, 이번엔 json 문자열을 얻을 수 있다.

이 json 문자열을 파싱해보면 에러가 발생하는데, 처음에 빈 콤마(,)가 존재하기 때문이다. 이 콤마를 삭제하고 다시 json 파싱을 하면 제대로된 tree형 문자열 데이터를 얻을 수 있다.

간단한 python 코드를 작성해서 이 tree를 순회해보면 "flag"가 포함된 value를 발견할 수 있다.

Flag : flag{ENC0D1NG_D4TA_1S_N0T_ENCRY7I0N}

References

  • (없음)



Binary 2

Category: Binary

Points: 200

Description:

Our lead Software Engineer recently left and deleted all the source code and changed the login information for our employee payroll application. Without the login information none of our employees will be paid. Can you help us by finding the login information?

***Flag is all caps

Employee_Payroll.exe

Point

  • .Net decompile을 할 수 있는가

Write-up

주어진 exe 파일의 정보를 간단히 확인해보자.

32bit .Net executable이다.

.Net executable은 IDA 보다는 .Net전용 디컴파일러를 사용하면 훨씬 깔끔하게 볼 수 있다.

적당한 .Net 디컴파일러 중의 하나인 JetBrains dotPeek을 이용해서 디컴파일을 시도해 보았다. (그냥 파일을 열고 네비게이터에서 여기저기 살펴보면 바로 코드를 볼 수 있다.)

employee_payroll.cs 파일을 보면 login을 클릭했을 때 Username과 Password를 검사하는 함수로 각각 checkUsername()과 checkPassword() 함수를 호출하도록 되어있다.

private void btnLogin_Click(object sender, EventArgs e)
{
  if (this.checkUsername() && this.checkPassword())
  {
    StringBuilder stringBuilder = new StringBuilder();
    char ch = Convert.ToChar(this.r1);
    stringBuilder.Append(ch.ToString());
    ch = Convert.ToChar(this.r2);
    stringBuilder.Append(ch.ToString());

(...중략...)

private bool checkUsername()
{
  return this.txtUsername.Text == "admin";
}

private bool checkPassword()
{
  return this.txtPassword.Text == "dGhpc19pc19ub3RfdGhlX2ZsYWdfeW91X3NlZWtfa2VlcF9sb29raW5n";
}

이 함수들을 확인해보면 로그인에 필요한 id와 password를 바로 알 수 있고, 알아낸 값들로 로그인을 시도해보면 flag를 얻을 수 있다.

Flag : flag{ST0RING_STAT1C_PA55WORDS_1N_FIL3S_1S_N0T_S3CUR3}

References

  • JetBrains dotPeek: https://www.jetbrains.com/decompiler/



Binary 3

Category: Binary

Points: 300

Description:

Another day, another disgruntled engineer. It seems that the login is working fine, but some portions of the application are broken. Do you think you could fix the the code and retrieve the flag?

get_flag

Point

  • ELF binary decompile 및 분석을 할 수 있는가
  • debugger를 이용해서 임의의 함수를 실행할 수 있는가

Write-up

file 명령을 이용하면 ELF 64bit executable인 것을 알 수 있다. 바로 실행 시켜보면 username과 password를 받는데, username이 정확해야 password를 넣을 수 있지만 두어번 실행해보면 username은 admin인 것을 알 수 있다.

더이상 알아낼만한 것이 없으니 리버싱을 시작해보자.

IDA로 열어서 decompile 후 main함수부터 따라들어가 보면 c(), u(), b() 정도의 함수들을 호출하고 username/password를 체크하는데, 로그인 후에 딱히 하는 행동이 없다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax

  c();
  if ( (unsigned int)u() )
  {
    if ( (unsigned int)b() )
      puts("you are now logged in...");
    else
      puts("password incorrect.");
    result = 0;
  }
  else
  {
    puts("username incorrect.");
    result = 0;
  }
  return result;
}

그러고 보면 c(), u(), b() 함수 외에도 함수들이 있는데, 어디서 호출되는건지 잘 모르겠다.

f() 함수부터 살펴보면, 뭔가 값을 만드는 함수인 것 같은데, x()라는 함수가 호출한다. x() 함수는 순서대로 f(), l(), a(), g(), s() 함수를 호출한다.(!!)
x() 함수는 d() 함수에서 호출하는데, d() 함수는 따로 호출해주는 함수가 없다.

d() 함수의 코드를 살펴보면, 특정한 url 주소를 만들어서 http request를 날리고, response를 파싱해서 결과를 출력해주는 함수로 보인다.

별달리 argument를 받는 것도 아닌 것 같으니, gdb를 이용해서 d() 함수를 호출해 보자.

바로 flag를 얻을 수 있었다.

Flag : flag{AP1S_SH0ULD_4LWAYS_B3_PR0T3CTED}

References

  • (없음)



Binary 4

Category: Binary

Points: 500

Description:

It appears we're not doing a very good job at employee retention and statisfaction. Now our Embedded Firmawre Engineer has left and removed the source code for our latest IoT firmware. Please help us to recover the source code for the following hex file. We would prefer C, but assembly would work as well. I also think there's a flag somewhere in the sorce for you.

embedded_db.hex

Point

  • hex 형식 파일을 이해하고 파싱할 수 있는가
  • 실행 바이너리에 대한 target을 식별하고 분석할 수 있는가

Write-up

사람을 얼마나 갈궜으면 선량한 개발자가 소스코드를 다 지워버리고 도망갔을까

악의 무리를 돕는 것은 내키지 않지만, 배점이 꽤 높으니 분석을 시작해보자.

주어진 파일을 열어보면, 의미는 둘째 치고 일단 읽을 수 있는 ascii text 파일이다.

딱 봐도 뭔가 형식에 맞춰 쓰여진 데이터 파일인 것 같다. ".hex 파일 형식"으로 검색을 해보면 인텔 HEX에 대한 내용이 가장 먼저 나온다.

ASCII 텍스트 형식으로 이진 정보를 전달하는 파일 형식이다. 라고 하니, 주어진 파일의 형식이 이게 맞는 것 같다.

파싱하는 구조에 대해서 설명이 나와있으니, 간단히 파싱하는 코드를 만들어서 바이너리 파일을 만들어 보자

hex 파일이 특별히 변조되거나 한 부분도 없고, 파싱도 제대로 된 것 같은데, file 명령을 이용해도 별달리 파일 형식이 인식되지 않는다.

혹시나 싶으니 strings를 이용해서 어떤 문자열들이 있는지 확인해보자.

Base64 인코딩 된 것으로 보이는 문자열이 있다. 이런건 바로 디코딩 해봐야 한다.

이거 아니랜다.

그 아래로는 Arduino라는 문자열도 보인다. Arduino용 이미지였나 보다.

Arduino에 대해서 간단히 검색해보면 Wiki 페이지Atmel AVR CPU를 사용한다는 정보가 나온다.

IDA를 이용해 바이너리를 열면서 CPU로 Atmel AVR를 지정해서 로딩해보자.

함수 몇개가 발견된 것을 알고 있는데, 쭉 훑어 내려가다 보면 sub_30E 함수에서 한 글자씩 메모리에 집어넣는(것 같은) 아래와 같은 코드가 있다. 순서대로 보면 마지막에 "="를 두개 넣는다. NeverLAN CTF 출제자들은 Base64를 굉장히 좋아하는 것 같다.

ROM:0390 loc_390:                                ; CODE XREF: sub_30E+18A↓j
ROM:0390                 ldi     r24, 0x5A ; 'Z'
ROM:0391                 ldi     r25, 0
ROM:0392                 call    sub_1A1
ROM:0394                 ldi     r24, 0x6D ; 'm'
ROM:0395                 ldi     r25, 0
ROM:0396                 call    sub_1A1

(...중략...)

ROM:0444                 ldi     r24, 0x51 ; 'Q'
ROM:0445                 ldi     r25, 0
ROM:0446                 call    sub_1A1
ROM:0448                 ldi     r24, 0x3D ; '='
ROM:0449                 ldi     r25, 0
ROM:044A                 call    sub_1A1
ROM:044C                 ldi     r24, 0x3D ; '='
ROM:044D                 ldi     r25, 0
ROM:044E                 call    sub_1A1

복사해 넣는 값들을 하나하나 모아서 Base64 디코딩을 해보니 flag를 얻을 수 있었다.

Flag : flag{ST1LL_US1N6_ST47IC_P455WORDS}

References

  • 인텔 HEX: https://ko.wikipedia.org/wiki/%EC%9D%B8%ED%85%94_HEX
  • 아두이노: https://ko.wikipedia.org/wiki/%EC%95%84%EB%91%90%EC%9D%B4%EB%85%B8



Cover the BASEs

Category: Crypto

Points: 25

Description:

ZmxhZ3tEMWRfeTB1X2QwX3RoM19QcjNfQ1RGfQ==

Point

  • base64 인코딩된 문자열을 인식하고 디코딩할 수 있는가

Write-up

base64 인코딩 된 문자열이 주어진다.

일단 다른거 제쳐놓고 == 로 끝나는 시점에서 99%라고 보면 된다.

온라인 base64 en/coding 도구들도 널려있지만 아까 리눅스 터미널을 열어둔게 있으니 간단하게 python으로 확인해보자.

한번 디코딩 했더니 바로 flag가 나왔다. 보통은 그래도 두번은 인코딩 하던데

Flag : flag{D1d_y0u_d0_th3_Pr3_CTF}

References

  • (없음)



Alphabet Soup

Category: Crypto

Points: 125

Description:

MKXU IDKMI DM BDASKMI NLU XCPJNDICFQ! K VDMGUC KW PDT GKG NLKB HP LFMG DC TBUG PDTC CUBDTCXUB. K'Q BTCU MDV PDT VFMN F WAFI BD LUCU KN KB WAFI GDKMINLKBHPLFMGKBQDCUWTMNLFMFMDMAKMUNDDA

Point

  • 알파벳에 대한 전치암호를 이해하고 풀 수 있는가

Write-up

제목이 Alphabet Soup인걸 보니 알파벳을 마구 섞어놨나보다. A-Z to A-Z의 매칭 관계를 알아내야 이 문장을 풀어낼 수 있을 것이다. 쉽지 않겠지만 하나씩 한번 풀어보자.

문장을 잘 보면 혼자있는 KK'Q를 찾을 수 있다. 높은 확률로 K는 i로 매칭되고, Q는 m으로 매칭될 것 같다. (매칭된 문자들을 구분하기 위해 변환된 알파벳은 소문자로 썼다.)

매칭을 적용하여 아래 문자열을 얻었다.

MiXU IDiMI DM BDASiMI NLU XCPJNDICFm! i VDMGUC iW PDT GiG NLiB HP LFMG DC TBUG PDTC CUBDTCXUB. i'm BTCU MDV PDT VFMN F WAFI BD LUCU iN iB WAFI GDiMINLiBHPLFMGiBmDCUWTMNLFMFMDMAiMUNDDA

마지막에 좀 긴 문자열이 flag일 거라고 추측할 수 있고, 그 앞에 iN iBit is일 것 같아서 N:t와 B:s의 매칭을 추가했다.

MiXU IDiMI DM sDASiMI tLU XCPJtDICFm! i VDMGUC iW PDT GiG tLis HP LFMG DC TsUG PDTC CUsDTCXUs. i'm sTCU MDV PDT VFMt F WAFI sD LUCU it is WAFI GDiMItLisHPLFMGismDCUWTMtLFMFMDMAiMUtDDA

그 다음 부터는 조금 막막해서 검색을 통해 영단어 데이터를 다운로드 받았다.

지금까지 얻어진 문자열 중에서 tLis가 4글자 중에 1글자만 알아내면 되는거라서 영단어 데이터에서 4글자이고 t?is와 매칭되는 단어들을 찾았는데, this가 가장 그럴듯해 보여 L:h의 매칭을 추가했다.

MiXU IDiMI DM sDASiMI thU XCPJtDICFm! i VDMGUC iW PDT GiG this HP hFMG DC TsUG PDTC CUsDTCXUs. i'm sTCU MDV PDT VFMt F WAFI sD hUCU it is WAFI GDiMIthisHPhFMGismDCUWTMthFMFMDMAiMUtDDA

이렇게 해두고 보니 F WAFI sD hUCU 부분이 눈에 띈다.

F는 혼자 있는걸 보니 I 또는 a 인것 같은데, I는 아까 나왔으니 F:a 매칭을 추가했다. hUCU는 한글자 밖에 모르지만 U가 반복되는 패턴때문에 영단어 패턴 검색 결과 here로 간주, U:e, C:r 매칭도 추가하였다.

MiXe IDiMI DM sDASiMI the XrPJtDIram! i VDMGer iW PDT GiG this HP haMG Dr TseG PDTr resDTrXes. i'm sTre MDV PDT VaMt a WAaI sD here it is WAaI GDiMIthisHPhaMGismDreWTMthaMaMDMAiMetDDA

here it is 앞에 sD는 so가 되는게 자연스러울 것 같으니 D:o를 추가하고, sTre는 s?re에 맞는 단어 중 i'm 뒤에 오는게 자연스러운 sure로 추측하여 T:u를 추가하였다.

MiXe IoiMI oM soASiMI the XrPJtoIram! i VoMGer iW Pou GiG this HP haMG or useG Pour resourXes. i'm sure MoV Pou VaMt a WAaI so here it is WAaI GoiMIthisHPhaMGismoreWuMthaMaMoMAiMetooA

그러고 나서 i VoMGer iW Pou GiG 부분을 봤는데, 이 부분이 I wonder if you did인것 같은 감이 강하게 와서, V:w, M:n, G:d, W:f, P:y를 추가하였다.

niXe IoinI on soASinI the XryJtoIram! i wonder if you did this Hy hand or used your resourXes. i'm sure now you want a fAaI so here it is fAaI doinIthisHyhandismorefunthananonAinetooA

이제 어느 정도 쉽게 유추가 가능한데, niXe에서 X:c를, Hy hand에서 H:b를 추가했다.

nice IoinI on soASinI the cryJtoIram! i wonder if you did this by hand or used your resources. i'm sure now you want a fAaI so here it is fAaI doinIthisbyhandismorefunthananonAinetooA

doinIthis에서 I:g를 추가하면 첫 단락이 nice going on soASing the cryJtogram!이 되는데, 여기서 A:l, S:v, J:p를 추가할 수 있다.

여기까지 추가하면 모든 문자열을 변환할 수 있게 되어 아래와 같은 문장을 얻는다.

nice going on solving the cryptogram! i wonder if you did this by hand or used your resources. i'm sure now you want a flag so here it is flag doingthisbyhandismorefunthananonlinetool

Flag : doingthisbyhandismorefunthananonlinetool

flag를 읽고 보니 onlinetool을 이용했으면 더 쉽게 찾았을텐데..하는 생각이 들었다.

변환 코드는 첨부해 두었다.

References

  • english words: https://github.com/dwyl/english-words



Category: Web

Points: 20

Description:

It's a classic https://challenges.neverlanctf.com:1110

Point

  • cookie를 확인하고 변조할 수 있는가
  • 수수께끼를 잘 풀 수 있는가

Write-up

문제 사이트에 들어가보면 He's my favorite Red guy라는 메세지만 표시되고 페이지 소스를 확인해봐도 별다른 것이 보이지 않는다.

문제 이름에 cookie가 들어가 burp suite를 이용해서 request와 response의 cookie를 확인해보자

request에 Set-Cookie 항목이 있고, 이로 인해 cookie에 Red_Guy's_name=NameGoesHere가 추가된 것을 확인할 수 있다.

여기에 내이름이나 문제명 등을 넣어서 request를 날려보았지만 별다른 변화를 확인할 수 없었다.

한참을 고민하다가 문제 제목인 Cookie Monster를 검색해 보았더니 동명의 어린이 애니메이션이 있었다.

여기서 붉은 캐릭터의 이름이 엘모(elmo)라서 Red_Guy's_name=elmo로 request를 날렸더니 flag를 얻을 수 있었다.

Flag : flag{YummyC00k13s}

References

  • (없음)



React To This

Category: Web

Points: 50

Description:

It looks like someone set up their react site wrong...

https://challenges.neverlanctf.com:1145

Point

  • javascript 코드를 읽고 이해할 수 있는가

Write-up

문제 사이트를 열어보자.

별다른 입력창 같은것도 없고 특별한 것들도 잘 보이지 않는다.

소스 코드를 보면 아래와 같이 javascript를 include 하는 부분이 있는 것을 알 수 있다.

<script type="text/javascript" src="/static/js/main.237378d2.js"></script>

chrome의 개발자 도구를 열어서 소스코드를 확인해보자. main.237378d2.js 외에도 몇가지 js들을 더 볼 수 있는데, 그 중 app.js파일에 import Admin from './Pages/Admin.js' 라는 명령이 있는 것이 보인다.

source tree에 보면 Pages 디렉토리가 있고 그 아래 Admin.js가 있길래 코드를 확인해 보았더니 flg 변수와 함께 flag를 출력해주는 루틴을 확인할 수 있었다.

Flag : flag{s3cur3_y0ur_s3ss10ns}

References

  • (없음)



Dirty Validate

Category: Web

Points: 50

Description:

To keep my server from doing a lot of work, I made javascript do the heavy lifting of checking a user's password

https://challenges.neverlanctf.com:1135

Point

  • javascript 코드를 읽고 이해할 수 있는가

Write-up

문제 사이트에 들어가보면 로그인 할 수 있는 화면이 보인다.

Username에 대충 아무거나 넣어보려고 하는데, 다 넣지도 않았는데 Username is incorrect라는 메세지가 나온다.

코드 소스를 확인해보자.

Username 쪽에 key 입력 event가 발생할 때마다 특정 동작을 하도록 되어있다. 좀 더 자세히 들여다보자.

// For element with id='name', when a key is pressed run this function
$('#name').on('keypress',function(){
   // get the value that is in element with id='name'
   var that = $('#name');
   // make an ajax request to get the expected username
   $.ajax('webhooks/get_username.php',{})
    .done(function(data)
    { // once the request has been completed, run this function
      data = JSON.parse(data);
            if( data.usernames.indexOf(that.val()) != -1 ){ // see if the username is in the list
 
              that.css('border', '1px solid green'); // if it matches turn the border green
              $('#output').html('Username is correct'); // state that the user was correct

            }else{ // if the user typed in something incorrect

              that.css('border', ''); // set input box border to default color
              $('#output').html('Username is incorrect'); // say the user was incorrect

            }

키 입력이 발생하면 webhooks/get_username.php를 호출하고 response로 받아온 json파일에 있는 이름인지 확인하도록 되어있다.

https://challenges.neverlanctf.com:1135/webhooks/get_username.php 에 브라우저로 접속해 보면 아래와 같은 응답을 확인할 수 있다.

{"usernames":[ "AdamsFamily","Mr. Clean","Dr. Whom","JimmyOneShoe" ] }

이 네 가지 username 중의 하나로 로그인 하면 되는 것 같다.

Username에 AdamsFamily를 넣고 Password에 아무거나 넣어보면 이번에도 Password is incorrect 메세지가 나온다.

이 부분도 코드를 확인해보자.

// For the password input now
// This is a BAD idea, never validate sensitive data in javascript
   $('#pass').on('keypress', function(){
    // get value for element with id='pass'
    var that = $('#pass');
    // make an ajax request to get the expected password for the given username
    $.ajax('webhooks/get_password.php?user='+encodeURIComponent($('#name').val()),{})
    .done(function(data)
    {// once the request has completed, run this function
      // remove whitespace from data
            data = data.replace(/(\r\n|\n|\r)/gm,"");
      // check if the data matches the given value
            if(window.atob(data) == that.val()){

        // if value is correct, show a green border
        that.css('border', '1px solid green');
        $('#output').html(window.atob(data));

            }else{

        // if value is false, remove border
              that.css('border', '');
              $('#output').html('Password is incorrect');

      }

이번에는 내가 입력한 username을 이용하여 get_password.php에 request를 던진다.

받아온 데이터는 특수문자를 거르고, atob() 함수를 이용해서 base64 decoding을 한 후, 내가 입력한 password와 비교하도록 되어있다.

password 정보가 client까지 날아오니 이걸 받아서 확인해보자.

https://challenges.neverlanctf.com:1135/webhooks/get_password.php?user=AdamsFamily 와 같은 방법으로 각각의 username에 대한 response를 받아보면 4가지의 base64 문자열을 얻을 수 있고, 이를 디코딩하여 다음과 같은 값들을 얻을 수 있다.

치사하게 하나만 flag고 나머지 3개는 꽝이다.

Flag : flag{D0n't_7rus7_JS}

References

  • (없음)



Things are not always what they seem

Category: Web

Points: 50

Description:

if you can't find it you're not looking hard enough

https://challenges.neverlanctf.com:1165/hello.html

Point

  • 웹페이지의 소스코드를 확인할 수 있는가

Write-up

문제 사이트에 들어가보아도 별다른 특이점이 보이지 않는다.

사이트의 소스코드를 살펴보자.

flag를 얻었다.

문제 사이트에서 전체선택(ctrl + a)을 해봐도 flag를 확인할 수 있었다.

Flag : flag{Whale_w0u1d_y0u_l00k3y_th3r3}

References

  • (없음)



SQL Fun 1

Category: Web

Points: 75

Description:

REPORT: 'My Customer forgot his Password. His Fname is Jimmy. Can you get his password for me? It should be in the users table'

https://challenges.neverlanctf.com:1150

Point

  • select, from, where 등을 이용해 sql문을 구성할 수 있는가
  • Web 문제의 함정에 빠지지 않을 수 있는가

Write-up

문제 페이지에 들어가보면 무엇인가 텍스트를 제출할 수 있도록 되어있다.

admin' or '1'='1 등을 이용해서 sql injection을 시도해 보았으나 동작하지 않았다.

WHERE 절이 반드시 포함된다고 하여 where 절을 넣어봤으나 별다른 특이점을 찾지 못했고, 이런저런 시도를 하다가 select 1 from users #where를 넣어봤을 때 sql injection이 아니라 그냥 sql문을 작성하면 되는 문제인 것을 알았다.

그래서 문제에 주어진대로 아래와 같은 SQL query를 작성해서 입력했더니 flag를 얻을 수 있었다.

select Password from users where Fname="Jimmy"

Flag : flag{SQL_F0r_Th3_W1n}

References

  • (없음)



SQL Fun 2

Category: Web

Points: 75

Description:

REPORT: A Client forgot his Password... again. Could you get it for me? He has a users account and his Lname is Miller if that helps at all. Oh! and Ken was saying something about a new table called passwd; said it was better to separate things

https://challenges.neverlanctf.com:1155

Point

  • join을 이용하여 sql query를 작성할 수 있는가

Write-up

users 테이블 외에 passwd 테이블도 이용해서 password를 찾으라고 한다. 이럴때 join 문을 쓰는거라고 배웠으니 의식의 흐름대로 join문을 검색해서 query를 작성하자.

우선 select * from users를 이용해서 users 테이블의 구조를 확인한다.

마찬가지 방법으로 구한 passwd table의 구조는 다음과 같다.

(여기서 그냥 user_id 5번의 passwd를 base64 디코드해도 되지만 연습을 위해 일단 그냥 진행하자..)

양 테이블에 id가 공통이니 아래와 같이 sql query를 보내면 password를 얻을 수 있다.

select Password from users join passwd on users.id=passwd.user_id where Lname="Miller"

획득한 Password를 base64 디코딩하면 flag를 얻을 수 있다.

Flag : flag{W1ll_Y0u_J01N_M3?}

References

  • (없음)



Console

Category: Web

Points: 75

Description:

You control the browser

https://challenges.neverlanctf.com:1120

Point

  • browser의 개발자 도구를 이용하여 javascript를 실행할 수 있는가

Write-up

무엇인가 텍스트를 입력받을 수 있는 박스가 주어진다.

아무거나 넣어보면 Nope, try again이라는 메세지를 보게 된다.

페이지 소스를 확인해 보자.

var foo = document.getElementById("p"); 

    function what(){
        var input = document.getElementById("pass").value;
        if( md5(input) == "7b1ece53a46f4a5a2995b9cf901bf457" ){
           getThat('Y');
        }else{getThat('N')}
    }
    function getThat(strg){
        if(strg == 'Y'){
  // Note: There is no data sent to the key.php file...
  // jquery ajax reference: https://api.jquery.com/jQuery.ajax/
            $.ajax({
                type: 'GET',
                url: '1/key.php',
                success: function (file_html) {
                    // success
                   foo.innerHTML=(file_html)
                }
            });
        }else{
            foo.innerHTML = "Nope, try again";
        }

입력한 텍스트의 md5 해쉬가 7b1ece53a46f4a5a2995b9cf901bf457이면 getThat('Y') 호출에 의해 key를 찍어주는 코드인 것 같다.

우선 online md5 decryptor를 이용해서 7b1ece53a46f4a5a2995b9cf901bf457에 해당하는 원본 메세지를 찾으려고 했으나 찾을 수 없었고, 브라우저를 통해 1/key.php 파일에 직접 http request를 해보냈으나 결과를 확인할 수 없었다.

그래서 chrome의 개발자 도구를 이용하여 getThat('Y') 함수를 호출한 결과, flag를 확인할 수 있었다.

Flag : flag{console_controls_js}

References

  • (없음)



Das Blog

Category: Web

Points: 125

Description:

Word on the street, Johnny's got a blog. Seems he doesn't know how to escape his inputs.

https://challenges.neverlanctf.com:1125

Point

  • sql injection을 할 수 있는가

Write-up

문제 페이지에 들어가보면 login페이지로 가는 링크를 볼 수 있다.

아무거나 넣어서 로그인을 시도해보면 Username / Password가 잘못되었다고 나온다.

Username에 ' 를 넣어보면 user를 찾는데 문제가 생겼다는 메세지를 볼 수 있다.

개발자의 입장에서 생각했을 때, 사용자 정보 DB에 대한 query가 실패한 케이스임을 가정해 볼 수 있을 것 같다. query는 성공했으나 적절한 사용자 정보를 확인하지 못한 경우가 먼저의 케이스 였을 것 같다.

' 를 넣었을 때 query가 실패한걸 보면 이건 진짜 SQL injection 문제인걸로 보인다.

일단 admin' or '1'='1을 넣어보자. 쿼리 자체는 성공한 듯 싶으나 admin이라는 이름의 계정이 없는 것 같다. 혹시나 싶어 admin 대신에 flag나 neverlan 같은 것을 넣어 보았지만 여전히 유효하지 않은 계정이었다.

계정 정보를 어디서 얻을 수 있을까 싶어 문제 사이트를 다시 살펴보다가 다시 들른 메인 페이지에 이름이 있어서 그걸 이용하여 Johnny' or '1'='1을 넣어봤더니 로그인이 되었다.

주어지는 링크를 따라 메인페이지로 돌아갔더니 flag를 얻을 수 있었다.

Flag : flag{3sc4pe_Y0ur_1npu7s}

References

  • (없음)



Das Blog 2

Category: Web

Points: 150

Description:

Well, we really showed Johnny. It looks like he made some changes... But he still isn't escaping his inputs. Teach him a lesson.

https://challenges.neverlanctf.com:1130

Point

  • 서버의 sql query 구성을 유추할 수 있는가
  • 서버의 sql query 구성에 따라 union select를 이용한 sql injection 공격을 할 수 있는가

Write-up

Das Blog 문제와 동일한 취약점이 있는지 확인하기 위해서 Johnny' or '1'='1을 다시 한번 넣어보자.

정상적으로 로그인이 되지만, You are now logged in as Johnny with permissions user라고 나오고, 메인페이지로 가보아도 아까와는 달리 ADMIN 권한이 아니라 DEFAULT 권한으로 로그인 했다고 나온다.

그렇다면 admin이 있겠지 하는 생각에 admin' or '1'='1을 시도해 보았으나 로그인 할 수 없었다. 오류 메세지를 보면 admin 계정이 따로 있지는 않은 것 같다.

로그인 메세지와 admin 계정이 없는 점을 감안하면 사용자 계정 정보에 권한 정보가 딸려있는 것 같다. 그렇다면 사용자 테이블에 id와 permission이 있어서 각각 'Jhonny'와 'user'로 들어있고, sql query를 이용해서 (id, permission) 형태로 받아온게 아닐까 하는 생각이 든다.

이 생각이 맞다면 아마 서버의 sql 쿼리문은 아래와 같이 구성되어 있을 것이다.

select id, permission from users where id='{id}' and pwd='{pwd}'

이 쿼리의 결과로 permission에 'admin'이 들어가게 sql injection을 하면 될것 같다.

union select를 이용해서 아래와 같이 간단하게 작성해보자.

a' union select 'b', 'admin' #

union select는 select의 결과를 앞의 결과에 붙여주는 역할을 한다. (잘 붙이기 위해서는 column의 구성이 앞의 select 문과 동일해야 한다.)

맨 앞의 a는 users 테이블에 없을 것 같은 사용자 이름이고, b는 아무거나 넣어도 상관없다. 이 입력이 들어간 쿼리의 결과는 (b, admin)이 될 것이고, 그 결과 아래와 같은 로그인 메세지를 받을 수 있다.

링크를 따라 메인 페이지로 돌아가보면 flag를 확인할 수 있다.

Flag : •flag{Pwn3d_W1th_SQL}

References

  • (없음)



Trivia 문제들

Category: Trivia

Points: 20 for each

Description:

Trivia 문제는 간단한 Quiz들로 구성되어있다. 부담없이 풀어보자.

Point

  • 간단한 보안 상식 및 검색 능력을 갖고 있는가

Write-up

1. SQL Trivia 1
    * Q: The oldest SQL Injection Vulnerability. The flag is the vulnerability ID.
    * A: CVE-2000-1233
    * Sol: 가장 오래된 SQL Injection 타입의 CVE ID를 찾아보자         (cvedetails.com - SQL Injection 검색 - CVE Number Ascending)
2. SQL Trivia 2
    * Q: In MSSQL Injection Whats the query to see what version it is?
    * A: SELECT @@VERSION
3. Sea Quali
    * Q: A domain-specific language used in programming and designed for managing data held in a relational database management system, or for stream processing in a relational data stream management system.
    * A: sql
4. 64 Characters
    * Q: A group of similar binary-to-text encoding schemes that represent binary data in an ASCII string format by translating it into a radix-64 representation.
    * A: base64
5. With Some Milk
    * Q: A small piece of data sent from a website and stored on the user's computer by the user's web browser while the user is browsing.
    * A: cookie
6. Beep Boop
    * Q: A standard used by websites to communicate with web crawlers and other web robots. The standard specifies how to inform the web robot about which areas of the website should not be processed or scanned
    * A: robots.txt

References

  • 검색은 구글: https://google.com



Description

Category: Crypto

Source: Insomni'Hack Teaser 2019

Points: 137

Author: Jisoon Park(js00n.park)

Description:

Use this API to gift drink vouchers to yourself or your friends!

http://drinks.teaser.insomnihack.ch

http://146.148.126.185 <- 2nd instance if the first one is too slow

Vouchers are encrypted and you can only redeem them if you know the passphrase.
Because it is important to stay hydrated, here is the passphrase for water: WATER_2019.
Beers are for l33t h4x0rs only.

Write-up

암호화된 음료 voucher에 대한 비밀번호를 알아내라고 한다.

flask를 이용한 python web server 코드가 주어졌으니 천천히 살펴보자.

couponCodes = {
    "water": "WATER_2019",
    "beer" : "�댿뻽�댿뻽�댿뻽�댿뻽�댿뻽�댿뻽�댿뻽�댿뻽�댿뻽�댿뻽�댿뻽�댿뻽�댿뻽�댿뻽�댿뻽�댿뻽��" # REDACTED
}

물은 마음대로 마실 수 있지만 beer에 대한 쿠폰코드는 삭제되어 있다. 알아내라는 뜻이겠지..ㅜㅠ

voucher는 아래와 같이 만들어진다.

@app.route("/generateEncryptedVoucher", methods=['POST'])
def generateEncryptedVoucher():

    content = request.json
    (recipientName,drink) = (content['recipientName'],content['drink'])

    encryptedVoucher = str(gpg.encrypt(
        "%s||%s" % (recipientName,couponCodes[drink]),
        recipients  = None,
        symmetric   = True,
        passphrase  = couponCodes[drink]
    )).replace("PGP MESSAGE","DRINK VOUCHER")
    return encryptedVoucher

이름과 어떤걸 마실지 json형태로 요청하면 (이름||쿠폰코드)를 쿠폰코드로 암호화 해서 voucher로 만들어 보내준다.

voucher는 PGP symmetric encrypted message 형태이다.

voucher를 사용하는 코드도 살펴보자.

@app.route("/redeemEncryptedVoucher", methods=['POST'])
def redeemEncryptedVoucher():

    content = request.json
    (encryptedVoucher,passphrase) = (content['encryptedVoucher'],content['passphrase'])
    
    # Reluctantly go to the fridge...
    time.sleep(15)

    decryptedVoucher = str(gpg.decrypt(
        encryptedVoucher.replace("DRINK VOUCHER","PGP MESSAGE"),
        passphrase = passphrase
    ))
    (recipientName,couponCode) = decryptedVoucher.split("||")

    if couponCode == couponCodes["water"]:
        return "Here is some fresh water for %s\n" % recipientName
    elif couponCode == couponCodes["beer"]:
        return "Congrats %s! The flag is INS{% raw %}{%s}{% endraw %}\n" % (recipientName, couponCode)
    else:
        abort(500)

voucher를 사용할 때는 voucher와 passphrase를 보내야 하는데, passphrase가 쿠폰코드이다.

(beer 쿠폰 코드는 모르니까)적당히 water에 대한 request를 만들어서 voucher를 요청해보면 암호화된 voucher를 받을 수 있다.

-----BEGIN DRINK VOUCHER-- jA0ECQMCAskQB8770R7/0kYBv/uvapVocJXq92CCLIC1EBmDu7NXS3Mt0RKz0PDi QQ3oDAPo4SalXyNSx98ZkRpaeS8Fvu+mbUst06MvQdTHqyAXMLmX =XhxQ -----END DRINK VOUCHER--

PGP는 데이터를 인코딩할 때 ASCII Armor를 사용한다고 한다.

ASCII Armor는 다음과 같이 구성된다.

  • An Armor Header Line, appropriate for the type of data
  • Armor Headers
  • A blank (zero-length, or containing only whitespace) line
  • The ASCII-Armored data
  • An Armor Checksum
  • The Armor Tail, which depends on the Armor Header Line

voucher에서는 Armor Headers는 생략된것 같고, =XhxQ는 Checksum이니 중간쯤의 base64 문자열이 데이터인가보다.
(정확히는 base64 인코딩된 문자열과 Checksum을 합친 Radix-64로 인코딩 되어있다.)

base64 문자열을 디코딩 해보면 raw byte 데이터를 얻을 수 있다. PGP 스펙을 들여다보고 바로 참조해도 되지만, PGP 데이터라는걸 알고 있으니 간단하게 pgpdump를 먼저 사용해보자.
(pgpdump를 사용하기 전에 voucher의 "DRINK VOUCHER" 문자열을 "PGP MESSAGE"로 바꿔줘야 한다.)

pgpdump를 이용해서 데이터를 해석해 보면, 두 개의 PGP 패킷으로 구성된 것을 알 수 있다.

먼저 Symmetric-Key Encrypted Session Key Packet 쪽을 살펴보자.

PGP 스펙5.3절을 살펴보면, 이 패킷을 이용해서 passphrase로부터 Session Key를 계산할 수 있는 방법이 적혀 있다.

pgpdump가 알려준 값들로 Iterated and slated s2k를 이용하면 되는데, 간단히 코드를 만들어 보면 아래와 같다.

def getKey(passphrase, salt, cnt, keyLen):
  iterate = salt + passphrase
  niter = cnt / len(iterate) + 1

  m = hashlib.sha1()
  m.update((iterate * niter)[:cnt])
  r = m.digest()
  if (len(r) < keyLen):
    m = hashlib.sha1()
    m.update('\x00' + (iterate * niter)[:cnt])
    r = r + m.digest()

  return r[:keyLen]

PGP 스펙에는 구현에 필요한 부분들이 명료하게 나와있지 않아서, 자세한 설명과 함께 test vector가 있는 사이트를 참조했다.

pgpdump가 session key는 AES256 알고리즘용 key라고 알려줬기 때문에 여기서 keyLen은 32를 주면 된다.

자 이제 session key로 Symmetrically Encrypted and MDC Packet을 까보자.

이 패킷에 대한 내용은 PGP 스펙5.14절에 나와있지만, 마찬가지로 구현에 충분한 내용은 아니다.
마찬가지로, 인터넷을 열심히 뒤져 이 문서를 찾아내었다.

주저리주저리 설명 보다는 아래 그림과 함께 이 문서의 2.2절을 한번 읽어보면 금방 이해할 수 있다.

이제 패킷 구조는 알았으니, session key를 이용해서 복호화를 시도해보자.

PGP는 symmetric 암호화를 할 때, 기본적으로 null-iv와 함께 CFB 모드를 사용한다고 한다. CFB 모드는 CTR 모드와 유사하게 stream cipher처럼 동작할 수 있어서 별도의 padding이 필요하지 않다.

AES256인 것도 알고, Key, IV, 운용모드까지 알았으니 간단하게 복호화를 시도해보면... 실패한다. PGP 스펙을 비롯한 온갖 문서에 따르면 decryption 했을 때 처음의 16byte는 랜덤 벡터이고, 다음 두 byte는 랜덤 벡터의 마지막 두 바이트의 반복이라고 하는데 그런 데이터가 나오지 않았다.

이 부분을 넘어가기 위해 스펙을 다시 보고, 놓친 곳이 없나 살펴보며 8~9시간 정도를 소요했다. passphrase에서 key를 유도하는 부분을 설명하는 사이트를 한 곳밖에 찾지 못해서 S2K 구현체를 찾아 그 부분을 다시 확인하는데 특히 많은 시간을 소모했으나 다행히(?) 아니었다.

한땀한땀 구현체와 데이터를 비교해보니, 문제는 늘 그렇듯 생각도 못했던 AES256-CFB decryption에 있었는데 python의 Crypto.Cipher에서 제공하는 CFB는 일반적인 CFB가 아니라 CFB8이라는 알고리즘이기 때문이었다.

Crypto.Cipher의 CFB가 일반적인 CFB처럼 동작하기 위해서는 segment_size를 조정해줘야 한다고 하는데, 그렇게 되면 AES block size에 맞는 padding이 필수라고 하여 decryption 함수를 간단히 구현하였다.

def decrypt(key, ct, iv):
  padlen = 16 - (len(ct) % 16)
  aes = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
  return aes.decrypt(ct + "#"*padlen)[:len(ct)]

이제 이걸로 복호화 해보면 PGP 복호화가 완료되어 (이름||쿠폰코드)가 보이면 좋겠으나.. ASCII 문자열이 안보인다.
복호화된 데이터는 [16 byte random][2 byte 반복][plain packet][2 byte 헤더][SHA1 hash]의 모양인데, 다시 PGP 스펙을 이용해서 plain packet의 첫 byte를 한 bit씩 해석해 보면 Compressed Data Packet인 것을 알 수 있다.

PGP의 Compression은 0(Uncompressed), 1(ZIP), 2(ZLIB), 3(BZip2) 등을 지원하는데, 이 패킷은 ZIP을 사용한다. ZLIB이나 PKZIP과는 다르게 RFC1951에 정의된 DEFLATE를 이용해 압축을 풀으란 뜻이란다.

검색결과, python에서는 이렇게 하면 된다:

m = zlib.decompress(compressed, -zlib.MAX_WBITS)

자 이제 진짜 끝일거라고 생각했는데, 압축을 푼 데이터를 보면 (이름||쿠폰코드) 앞에 뭔가가 많이 붙어 있다. 찝찝하니 다시 첫 byte부터 확인해보면 Literal Data Packet이란걸 알 수 있다. (제발 이제 그만 ㅜㅠ)

다행히 앞쪽에 붙은건 header와 date 정도의 잡다한 데이터이고, payload 쪽을 추출해보면 비로소 깔끔한 모양의 원본 데이터를 만날 수 있다.

머나먼 여행을 통해 PGP 대칭키 암호화가 어떻게 진행되는지 알았으니, 이제 문제를 풀어보자. (...)

지금까지 온 과정을 돌이켜 보면, PGP 스펙에 충실하게 따라왔고 별달리 passpharse나 session key가 노출될만한 취약점은 없었던 것 같다. (있었으면 CTF는 중단하고 PGP 취약점 보고서를 작성했겠지)

beer의 쿠폰 코드를 모르기 때문에 beer voucher의 암호화를 풀 수 없는 상황이니, 가진 걸 일단 정리해보자.

  • 암호화되는 평문 데이터는 [내가 넣은 값||알아내야 할 값]이 압축된 모양이다.
  • 복호화는 일부에 대해서라도 할 수 없다.
  • voucher 검증 API에 대한 반복 시도 방법은 아니다.(서버 코드에 sleep(15)가 있으므로)
  • 암호화는 계속 시도해볼 수 있으나, 난수가 들어가므로 동일한 입력에도 매번 결과값이 바뀐다.
  • CFB 모드를 이용하므로, compressed data의 대한 길이만 알 수 있다.

첫번째와 마지막 항목을 보면 공격 포인트가 compression임을 알 수 있다.

작년 9월의 CSAW CTF에서 compression을 공격하는 유사한 flatcrypto 문제가 있었기 때문에 금방 알 수 있었다.

압축을 할 때 동일한 문자열이 반복적으로 나타나면 압축 결과의 길이가 짧아지는 특성을 이용해서 공격하면 된다.

flatcrypto 문제의 writeup을 찾아서 exploit을 적용시켜 보았다.

후후훗 "G1MME_B33RY_TH1RSTY"라는 그렇듯한 flag를 얻었다.

..하지만 인증에는 실패했다.

알고리즘을 좀 살펴본 결과, 의도치 않은 pattern으로 인해 길이가 짧아지는 부분을 대충 무시해서 그런 것 같았다. 다른 writeup을 찾아서 exploit을 적용시켜 보았다. 이 exploit은 압축 후 길이가 짧아지는 것들을 candidate으로 계속 모으는 알고리즘을 사용했다.

이 알고리즘도 flag를 명확하게 찾아주지는 못했지만, 아까의 exploit이 놓쳤던 후보 문자열들을 많이 찾아내 주었다. 이 후보 문자열들을 적당히 조합한 결과, 아래의 문자열을 얻을 수 있었다.
(FLAG가 대문자와 숫자, 언더스코어로만 구성되어있다는 힌트가 있었으면 더 예쁜 출력이 나왔겠지만, 어쨌든 찾았다.)

G1MME_B33R_PLZ_1M_S0_V3RY_TH1RSTY

이 값을 쿠폰코드로 하여 서버에 voucher 인증을 요청한 결과, 옳은 값임을 확인 할 수 있었다.

Flag : INS{G1MME_B33R_PLZ_1M_S0_V3RY_TH1RSTY}

PGP 메세지를 parsing 하면서 작성한 메모복호화 코드, 그리고 exploit을 resource에 첨부해 두었다.

'writeups > Crypto' 카테고리의 다른 글

:)  (0) 2019.11.25
Revolutional Secure Angou  (0) 2019.11.25
RSAaaay  (0) 2019.11.23
Very Smooth  (0) 2019.11.23
Simon and Speck Block Ciphers  (0) 2019.11.23

Description

Category: Crypto

Source: TAMUctf 2019

Points: 478

Author: Jisoon Park(js00n.park)

Description:

Hey, you're a hacker, right? I think I am too, look at what I made!

-------------------------------> (2531257, 43)

My super secret message: 906851 991083 1780304 2380434 438490 356019 921472 822283 817856 556932 2102538 2501908 2211404 991083 1562919 38268

-------------------------------> Problem is, I don't remember how to decrypt it... could you help me out?

Difficulty: easy

Write-up

RSA 관련 문제이다. 가장 먼저 주어진 것은 public key pair인 것 같다.

modulus가 그리 크지 않으니 bruteforce로 해도 금방 인수분해를 할 수 있을 것 같고, 실제로도 그렇다. 2531257 = 509 * 4973임을 금세 얻을 수 있다.

modulus를 인수분해 한 다음, phi(n)을 구하고 private exponent를 계산하자. 그런 다음 문제에서 주어진 값들을 복호화 해보면 아래와 같은 값들을 얻을 수 있다.

[103, 105103, 101109, 12383, 97118, 97103, 10195, 83105, 12095, 70108, 121105, 110103, 9584, 105103, 101114, 115125]

거의 다 풀은 걸로 생각했다가 여기서 풀이가 막혔는데, 첫번째 값은 ASCII로 해석이 되지만, 두번째 값 부터는 어떻게 flag로 변환시켜야 할지 감이 오지 않았다. 그래서 일단 여기서 접어두고 다른 문제를 풀었는데, 다른 문제의 flag 형식이 gigem{}으로 시작해서 이 문제도 같은 형식이지 않을까 싶어서 gigem을 ascii decimal로 바꿔 봤더니 [103, 105, 103, 101, 109] 였다.

이로 미루어 이 문제에서 주어진 값들은 flag를 두 글자씩 묶어서 ascii decimal을 연접(concatenation) 한 것임을 유추할 수 있었다.

문제에서 주어진 값들을 모두 연접한 후 printable ascii code들이 나올때마다 chr로 변환하여 출력하는 코드를 작성하여 실행하였더니 flag를 얻을 수 있었다.

Flag : gigem{Savage_Six_Flying_Tigers}

'writeups > Crypto' 카테고리의 다른 글

Revolutional Secure Angou  (0) 2019.11.25
drinks  (0) 2019.11.25
Very Smooth  (0) 2019.11.23
Simon and Speck Block Ciphers  (0) 2019.11.23
Ps and Qs  (0) 2019.11.23

Description

Category: Misc/Secure Coding

Source: TAMUctf 2019

Points: 478

Author: Jisoon Park(js00n.park)

Description:

https://gitlab.tamuctf.com/root/pwn

Difficulty: easy

Write-up

특이하게 Secure Coding을 확인하는 문제이다. 문제에서 주어진 gitlab 페이지로 가보면 (팀 계정으로 로그인 할 수 있다.) 문제 코드가 있고, 이를 fork해서 수정한 후 commit을 하면 된다는 안내가 있다.

Project 코드를 fork 한 후, 문제 코드를 살펴보자.

#include <stdio.h>
#include <stdlib.h>

void echo()
{
  printf("%s", "Enter a word to be echoed:\n");
  char buf[128];
  gets(buf);
  printf("%s\n", buf);
}

int main()
{
  echo();
}

printf()의 format에는 constant string만 들어가서 별다른 특이점이 없어 보이는데, gets() 함수를 이용해서 사용자 입력을 받고 있는 부분이 거슬린다.

이 부분을 아래와 같이 수정하고 commit 해보자

  fgets(buf, 128, stdin);

문제 안내에서 알려준대로 commit한 후 CI/CD 메뉴에 가보면 flag를 확인할 수 있다.

Flag : gigem{check_that_buffer_size_baby}

'writeups > Coding|misc.' 카테고리의 다른 글

plz variable  (0) 2019.11.25
input  (0) 2019.11.25
algo-auth  (0) 2019.11.25
Run me!  (0) 2019.11.23
JPEG file  (0) 2019.11.23

Description

Category: Pwnable

Source: TAMUctf 2019

Points: 470

Author: Jisoon Park(js00n.park)

Description:

nc pwn.tamuctf.com 4325

Difficulty: medium

pwn5

Write-up

Pwn4 문제와 비슷한 구성이다. 다만, 입력한 데이터의 앞의 몇 바이트만 ls 뒤에 적용되어서 뭔가 다른 명령어를 써넣기도 힘들도록 되어있다.

다른 방법으로 접근해보자. 여전히 gets() 함수를 통해 입력을 받고 있는데 checksec으로 확인해보면 NX가 enable 되어 있다. stack에 쉘코드를 올리는 방식은 아닐 것 같고, canary가 없는 것을 보니 ROP 또는 RTL 종류의 공격을 시도하면 될 것 같다.

우선 system 함수를 호출하는 곳과 "/bin/sh"라는 문자열이 있는지 확인해 보자. 라이브러리를 뒤질 것도 없이 system()이라는 함수가 존재하고, "/bin/sh"라는 문자열도 어렵지 않게 찾을 수 있다.

system("/bin/sh")가 실행되도록 Buffer overflow를 이용하여 stack을 구성해 보자.

우선, ebp까지의 버퍼를 더미데이터로 채우고, ebp의 자리에도 더미데이터를 쓴다. return address 자리에 system 함수의 주소를 넣고나면 system()함수가 호출될텐데, system 함수는 호출 당시의 stack 구조가 [return address][arg1]이라고 간주할 것이다. 여기서 return address는 뭘로 채우던 관심 없으니 이것도 더미 데이터로 채우고, 마지막으로 arg1 자리에 "/bin/sh" 문자열의 주소를 써주면 공격을 위한 payload가 완성된다.

위와 같은 동작을 하는 exploit을 작성하여 실행시키면 서버의 쉘을 얻을 수 있고 flag를 확인할 수 있다.

Flag : gigem{r37urn_0r13n73d_pr4c71c3}

'writeups > Pwnable' 카테고리의 다른 글

mini converter  (0) 2019.11.25
20000  (0) 2019.11.25
Pwn4  (0) 2019.11.23
Pwn3  (0) 2019.11.23
Pwn2  (0) 2019.11.23

Description

Category: Pwnable

Source: TAMUctf 2019

Points: 412

Author: Jisoon Park(js00n.park)

Description:

nc pwn.tamuctf.com 4324

Difficulty: medium

pwn4

Write-up

일단 주어진 주소에 접속해 보자. ls의 argument로 무엇을 넣을 것인지 물어본다.

간단하게 . 하나를 넣어보면 현재 디렉토리의 파일 목록을 보여주는데, 그 중에 flag.txt 가 보인다.

필터링 되겠지 하는 가벼운 마음으로 .;cat flag.txt 를 입력했더니 flag를 얻을 수 있었다.

Flag : gigem{5y573m_0v3rfl0w}

이게 뭐지 라는 생각으로 코드를 잠시 살펴봤더니, 필터링을 하긴 하는데 슬래시(/) 하나만 하고 있었다. 무슨 의미일까...

'writeups > Pwnable' 카테고리의 다른 글

20000  (0) 2019.11.25
Pwn5  (0) 2019.11.23
Pwn3  (0) 2019.11.23
Pwn2  (0) 2019.11.23
Pwn1  (0) 2019.11.23

Description

Category: Pwnable

Source: TAMUctf 2019

Points: 469

Author: Jisoon Park(js00n.park)

Description:

nc pwn.tamuctf.com 4323

Difficulty: easy

pwn3

Write-up

사용자 입력만 받고는 별달리 하는 일이 없는 바이너리가 주어졌다. 하는 일은 없지만, 내가 입력하는 값이 들어갈 버퍼의 주소를 알려주기는 한다.

굳이 스택의 주소를 알려주는 것으로 보아 스택에서 쉘코드를 실행하라가거나 뭐 그런 문제 같다. checksec으로 확인해보자.

역시나 NX가 disable 되어있다. 스택에서 쉘코드 실행이 가능하다는 이야기다.

인터넷에서 간단한 쉘코드를 받아서 쓰도록 하자. i386에서는 ebp+4의 위치에 return address가 있으므로, 여기를 쉘코드의 주소로 덮어써주면 된다.

s 변수의 버퍼 크기는 0x12A 바이트이므로, 쉘코드를 먼저 써주고 나머지 부분은 대충 아무 데이터로 덮어써준다. 그리고 스택 상의 ebp 주소에도 4 바이트 더미 데이터를 써주고 마지막으로 return address 주소에 s 변수의 주소를 써주면 echo() 함수 실행 후에 return 명령이 호출될 때 s 변수 주소에 있는 쉘코드를 수행하게 될것이다.

이대로 동작하도록 exploit를 써주고 실행시키면 flag를 얻을 수 있다.

Flag : gigem{r3m073_fl46_3x3cu710n}

'writeups > Pwnable' 카테고리의 다른 글

Pwn5  (0) 2019.11.23
Pwn4  (0) 2019.11.23
Pwn2  (0) 2019.11.23
Pwn1  (0) 2019.11.23
fd  (0) 2019.11.23

+ Recent posts