본문 바로가기
Security/CTF

[LA CTF 2024 Write UP] 두번째로 참가해본 CTF

by 고간디 2024. 2. 19.

Write Up 보러 왔으면 다른 사람이 쓴 거 보기를 추천
본인은 쌩초짜이며 Write Up이 뭔지도 잘 모름

LA CTF

저번에 DiceCTF 2024로 처음 CTF에 입문을 해봤었다
제대로 푼 문제는 한 문제 밖에 없었는데 그 이후로 따로 공부를 하지는 않았다
 
백수짓하다가 13일부터 16일까지 2박 3일로 일본에 다녀왔는데 그때 정말 많은 생각을 했다
일본 여행은 나에 대해서, 나의 과거 그리고 미래에 대해서 찬찬히 성찰해보고 그려볼 수 있는 계기가 되었다
익숙하지 않은 낯선 환경과 간섭하는 사람도 전혀 없는 공간이 나로 하여금 더 자신에게 몰입할 수 있도록 했다
 
수많은 생각거리를 안고 뭐라도 해보자는 마음에 들어간 CTF Time에서 바로 다음날 열리는 LA CTF에 참가하기로 했다
인천으로 돌아오는 비행기에서 저번 DiceCTF와 같은 팀네임으로 싱글팀을 신청했다
 
태어나서 두번째로 참가해보는 CTF였는데 결과는 생각보다 좋아서 만족스럽다
한 문제 밖에 풀지 못하던 지난 번과는 다르게 10문제나 풀어냈다
꽁으로 주는 welcome 문제 3개도 풀긴 했다 
 
아직 웹해킹만 좀 배웠고 리버싱은 기초만 깔짝 들은 수준이었다
경험이 많이 없는 워게임조차도 웹해킹만 풀었봤다
 
그래서 저번 DiceCTF에서도 웹해킹 문제만 시도했었는데 이번에는 다른 분야도 접해보자는 생각으로 도전해보았다
결과적으로 웹해킹 말고도 암호학, 리버싱, misc 등의 문제들을 풀어낼 수 있었다
웹 2개, 리버싱 2개, 암호학 3개, misc 3개, welcome 3개를 풀어 총 13문제를 풀었다
 
이번 CTF는 개인적으로 내가 성장한 느낌이 들어서 만족스러웠다
혹시나 난이도가 비교적 쉬워서 그렇게 느껴지는 것일 수도 있겠지만 다른 분야의 문제들도 풀어보며 새로운 경험을 할 수 있게 되었음에 의의를 두기로 했다
이제 앞으로의 공부 방향도 조금씩 잡혀가는 듯한 느낌이다
 


web/terms-and-conditions

Welcome to LA CTF 2024! All you have to do is accept the terms and conditions and you get a flag!
 


terms-and-conditions.chall.lac.tf

무지개색 배경에 CTF 이용 약관이 적혀있고 I Accept 버튼이 있다
개발자 도구를 통해 script 태그를 살펴보면 저 I Accept 버튼을 눌러야 FLAG를 획득할 수 있다
 
사실 개발자 도구를 열면 'No Console Allowed'라는 문구가 등장하지만 개발자 도구가 열려있는 상태로 새로고침을 해주면 개발자 도구를 사용할 수 있다
 
I Accept 버튼을 누르려고 포인터를 옮기면 저 버튼이 움직여버려서 클릭을 할 수 없다
 


I Accept 버튼의 id가 accept인 것을 확인했다면 콘솔을 이용해 간단하게 버튼을 클릭할 수 있다
 


이렇게 바로 FLAG를 획득할 수 있다
 
 


 
 

web/flaglang

Do you speak the language of the flags?
 


flaglang.chall.lac.tf

자신의 나라와 다른 나라를 선택해서 서로의 국기, 언어를 확인할 수 있다
countries.yaml 파일에서도 볼 수 있듯이 한국을 포함한 각 나라의 언어로 Hello world 가 저장되어있다
 


Flagistan:
  iso: FL
  msg: "<REDACTED>"
  password: "<REDACTED>"
  deny: 
    ["AF","AX","AL", ~~~ ]

플래그는 Flagistan이라는 이름으로 저장되어 있는데 다른 나라들과는 다르게 password 항목이 있다


app.get('/switch', (req, res) => {
  if (!req.query.to) {
    res.status(400).send('please give something to switch to');
    return;
  }
  if (!countries.has(req.query.to)) {
    res.status(400).send('please give a valid country');
    return;
  }
  const country = countryData[req.query.to];
  if (country.password) {
    if (req.cookies.password === country.password) {
      res.cookie('iso', country.iso, { signed: true });
    }
    else {
      res.status(400).send(`error: not authenticated for ${req.query.to}`);
      return;
    }
  }
  else {
    res.cookie('iso', country.iso, { signed: true });
  }
  res.status(302).redirect('/');
});

app.js 파일의 일부 코드다
 
처음 시도한 것은 /switch?to=Flagistan 으로 요청을 보내봤다
하지만 password를 알 수 없어 error 메세지가 출력되서 다른 방법을 찾아보기로 했다
 


app.get('/view', (req, res) => {
  if (!req.query.country) {
    res.status(400).json({ err: 'please give a country' });
    return;
  }
  if (!countries.has(req.query.country)) {
    res.status(400).json({ err: 'please give a valid country' });
    return;
  }
  const country = countryData[req.query.country];
  const userISO = req.signedCookies.iso;
  if (country.deny.includes(userISO)) {
    res.status(400).json({ err: `${req.query.country} has an embargo on your country` });
    return;
  }
  res.status(200).json({ msg: country.msg, iso: country.iso });
});

Flagistan에서 deny에 있던 국가가 감지되면 err와 함께 플래그가 출력되지 않는다
 
하지만 deny 국가 감지는 쿠키로 하기 때문에 쿠키를 변조해주면 될 것이라 생각했다
/view?country=Flagistan에 요청을 보내면 err가 등장하는데 쿠키 변조 확장 프로그램을 이용해 iso를 아무 값으로 수정해주면 플래그가 출력된다
 


웹해킹은 이렇게 두 문제만 풀 수 있었다
다른 문제는 할 수 있을 것 같으면서도 어려워서 못 하겠더라..
 


rev/shatterd-memories

I swear I knew what the flag was but I can't seem to remember it anymore... can you dig it out from my inner psyche?
 


그냥 IDA에 돌려서 main 함수 disassemble 했는데 그냥 봐도 답이 보인다
 
다만, 순서가 왜 바뀌는지는 이해 못 했지만 어찌됐건 말이 맞으니 잘 풀었다
 
처음 리버싱 기초 끄적일 때 머리 싸매고 어려워했던 기억이 있어서 리버싱에 거부감이 좀 느껴졌었는데 한번 도전해보길 잘했다
앞으로 좋아하는 한 가지 분야만 하려 하지 말고 다양한 도전을 하고 여러 경험들과 지식들을 축적해나가야겠다
 


 

rev/aplet321

Unlike Aplet123, Aplet321 might give you the flag if you beg him enough.
 


v3 = s;
  setbuf(stdout, 0LL);
  puts("hi, i'm aplet321. how can i help?");
  fgets(s, 512, stdin);
  v4 = strlen(s);
  if ( v4 <= 5 )
    goto LABEL_10;
  v5 = 0;
  v6 = 0;
  v7 = &s[(unsigned int)(v4 - 6) + 1];
  do
  {
    v6 += strncmp(v3, "pretty", 6uLL) == 0;
    v5 += strncmp(v3++, "please", 6uLL) == 0;
  }
  while ( v3 != v7 );
  if ( v5 )
  {
    if ( strstr(s, "flag") )
    {
      if ( v6 + v5 == 54 && v6 - v5 == -24 )
      {
        puts("ok here's your flag");
        system("cat flag.txt");
      }
      else
      {
        puts("sorry, i'm not allowed to do that");
      }
    }
    else
    {
      puts("sorry, i didn't understand what you mean");
    }
  }
  else
  {
LABEL_10:
    puts("so rude");
  }
  return 0;

이것도 IDA에 바로 돌려봤다
연립해서 방정식 풀면 v6 = 15와 v5 = 39를 만들어서 FLAG를 얻어내야 한다
근데 v5와 v6의 값은 어떻게 바꾸고, v3 != v7 조건은 또 어떻게 만족시키고 v7 값 변화도 신경써야 할 것 같아서 시작부터 머리가 아파왔다
 


일단 v6는 pretty, v5는 please를 인식하는 것 같으니까 각각 15번, 39번씩 써보자 생각했다
이유는 모르지만 어떻게 v5와 v6가 바뀌었는지 FLAG가 출력됐다
 
문제 설명에서 enough하게 beg하면 된다는데 이쁜이한테 싹싹 빌어서 FLAG가 나온 걸 보니 적절한 설명인 것 같다
 
뭔가 리버싱을 할 때에는 엄밀하게 하나하나 분석하려 하면 오히려 힘들어지는 것 같다
대충 훑으면서 프로그램의 흐름을 파악하는 것이 제일 중요한 것 같다
 
리버싱도 이렇게 2문제로 끝냈다
 


 

crypto/valentines-day

Happy Valentine's Day! I'm unfortunately spending my Valentine's Day working on my CS131 homework. I'm getting bored so I wrote something for my professor. To keep it secret, I encrypted it with a Vigenere cipher with a really long key (161 characters long!)

As a hint, I gave you the first part of the message I encrypted. Surely, you still can't figure it out though?

Flag format is lactf{xxx} with only lower case letters, numbers, and underscores between the braces.
 


비즈네르 암호로 메세지를 작성했다고 한다
암호학 분야는 한번도 접해본 적이 없어 이름만 보고 어려울 줄 알고 시도조차 안 해왔었다
 
그런데 비즈네르 암호라는 단어를 보니 2년 전 학교에서 배웠던 기억이 나서 한 번 도전해보기로 했다
내신은 말아먹었지만 컴공 생기부를 챙겨놓기 잘 한 것 같다
막상 생각해보면 생기부 만들면서 공부하고 활동했던 것들이 유용하게 쓰이는 일이 많다
 
구글에 그냥 vigenere cipher decoder를 검색해서 나온 사이트를 사용해봤는데 결과가 바로 나온다ㄷㄷ
첫 도입부의 원문을 제시해서 그런지 빠르게 답이 나온 것 같다

심지어 161 글자인 키까지 알려준다..
성능 확실하네
 


crypto/very-hot

I didn't think that using two primes for my RSA was sexy enough, so I used three.
 


from Crypto.Util.number import getPrime, isPrime, bytes_to_long
from flag import FLAG

FLAG = bytes_to_long(FLAG.encode())

p = getPrime(384) # 116자리수
print(p)
while(not isPrime(p + 6) or not isPrime(p + 12)):
    p = getPrime(384) # 2657
q = p + 6
r = p + 12

n = p * q * r
e = 2**16 + 1 # 65537
ct = pow(FLAG, 65537, n)

print(f'n: {n}')
print(f'e: {e}')
print(f'ct: {ct}')

n, e, ct는 txt 파일로 주어져있었다
RSA에 소수 3개를 사용했다는데 p만 찾으면 FLAG를 구할 수 있을 것 같다
 
그래서 p, p+6, p+12가 모두 소수이면서 384비트의 소수를 찾기 위해 코드를 짰다
한참 걸려서 소수 3개를 겨우 찾아냈지만 n값과 맞지 않는 소수였다
이에 다른 방법을 찾아보기로 했다
 


소수 데이터베이스가 있다길래 혹시나 해서 n값을 입력해봤는데 이게 웬 걸
p 값을 바로 찾을 수 있었다ㄷㄷ
이렇게 푸는 게 맞나 싶지만 일단 풀어봤다
 


from Crypto.Util.number import inverse
from Crypto.Util.number import getPrime, isPrime, bytes_to_long, long_to_bytes

# 주어진 값
n = 1056511174...  # 모듈러 값
ct = 9953835612...  # 암호문
e = 65537  # 공개 지수

p=2194276565

# 공개 지수 e의 모듈러 역원 계산
phi_n = (p - 1) * (p+6 - 1) * (p+12 - 1)  # Euler's totient function
d = inverse(e, phi_n)

# 암호문 해독
pt = pow(ct, d, n)

# 해독된 평문 출력
print("해독된 평문:", long_to_bytes(pt))

이제 p 값을 찾았으니 FLAG를 역연산해서 구해보자
사실 ChatGPT의 도움을 받아가며 코드를 짜긴 했다
 
모듈러 역원이나 오일러 피 함수라는 것은 처음 들어봤다
이래도 되나 싶지만 다음 달부터 대학가면 배우겠지 생각하며 그냥 넘어갔다
 


 

crypto/selamat pagi

If you talk in another language, nobody can understand what you say! Check out this message I sent in Indonesian. To add some extra security, I also applied a monoalphabetic substitution cipher on it!
 


Efe kqkbkx czwkf akfs kdkf qzfskf wzdcjtfk
Ieqku kqk akfs ikxj kck akfs wkak ukikukf :Q
Lzfqztk ukdj kqk qe wefe: bkvim{wzbkdki_ckse_kckukx_ukdj_wjuk_kfkbewew_mtzujzfwe}

무슨 인도네시아어로 메세지를 작성하고 암호화한 것이라고 한다
풀고 느낀 건데 인도네시아어를 구사하는 사람들에게는 너무 유리한 것 같다..ㅋㅋ
 
그리고 제목의 selemat pagi는 인도네시아어로 아침 인사라고 한다


신기한 사이트를 찾아서 여기에서 플래그를 얻어보기로 했다
당연히 lactf 형태를 가질테니 다섯 개의 대응되는 알파벳은 알아냈다
 
이제 다른 알파벳들도 모두 알아내야 한다 하..
 


문제 제목에서 힌트를 얻을 수 있었다
FLAG에 selamat_pagi와 글자수와 일부 알파벳이 일치하는 것을 보고 대응되는 알파벳을 몇 개 더 알아낼 수 있었다
 


이 다음 과정이 굉장히 힘들었다
인도네시아어를 전혀 몰라도 어찌저찌 구글과 파파고의 도움을 받아서 FLAG에 들어가는 모든 알파벳을 유추해낼 수 있다
 
예를 들어 i [] i 에서 []가 무엇인지 모를 때 아무 알파벳을 먼저 넣어본다
난 문재인 대통령 이니가 먼저 떠올라서 '인도네시아어 ini' 를 검색했는데 '이것'이라는 뜻이라고 한다
운 좋게 시작부터 n에 대응되는 알파벳을 찾았다
 
그 다음으로도 adala[]에서 '인도네시아어 adalan'을 검색하면 '수정된 검색어의 결과: adlah' 이런 식으로 알아서 보정 검색이 되서 알파벳들을 충분히 유추해낼 수 있었다
 
이렇게 암호학 문제는 3문제를 풀어냈다
 


 

misc/infiniet loop

I found this google form but I keep getting stuck in a loop! Can you leak to me the contents of form and the message at the end so I can get credit in my class for submitting? Thank you!
 


구글 폼에서 푸는 문제인데 이렇게 두 번째 문제에서 넘어가지지 않는다
혹시 넌센스인가 싶어서 3, couple, twice, sex, 0, baby, 1, 창문, window 등등 생각나는 건 다 해봤는데 안 됐다
 


혹시나 해서 개발자도구를 열고 lactf를 검색해봤는데 이게 뭐노..!!
폼 정답을 찾을 수 있었다
 
중학생 때 다니던 학원에서 매일 숙제로 내주던 온라인 듣기가 새록새록 떠오른다
그때도 개발자 도구를 이용해서 이런 식으로 문제 정답을 볼 수 있었다
하지만 난 그 당시 이런 식으로 숙제할 바에는 안 하는 게 낫다는 생각을 했어서 숙제를 날로 먹은 적은 없다
 
근데 듣기 생각하니까 갑자기 이번 수능에 듣기 하나를 틀려 79점을 맞아 대학이 바뀐 게 개빡친다
이러면 지금 0.5점 차이로 발발 기다리고 있는 서강대 추합을 기다리지 않고 낭낭하게 최초합격하는 건데 말이다
 


misc/mixed signals

NOTE: Unfortunately we goofed up and uploaded the wrong file. As it's too late into the CTF to fix, we will be leaving the challenge as-is. Yes, you can just hear the flag in the audio file directly.
I can't figure out what my friend is trying to tell me. They sent me this recording and told me that the important stuff is at 40 kHz (??? what does that even mean).

This may be useful. Flag format is lactf{xxx} with only lower case letters, numbers, and underscores between the braces.
 


 
출제 실수로 미완성된 문제가 올라갔다고 한다
난 그것도 문제 설명에 있는 하나의 기믹인 줄 알고 무시하고 40kHz로 진폭을 어떻게 변조하는지만 찾았다
 
어릴 때 보던 미스테리 영상에 나왔던 것처럼 알 수 없는 소리의 파형을 분석하면 의미심장한 문구와 그림들이 나타나는 것을 떠올리며 주어진 wav 파일을 3D 파형으로도 보고 온갖 할 수 있는 것은 다 해봤다
 
뭔가 글자 모양 같은게 보이긴 하는데 전혀 감이 오지 않는다
 


그런데 한번 wav 파일을 재생해보니 뭘 말하는 소리가 명확하게 들린다
난 지지직 소리만 날 줄 알고 들어볼 생각도 안 했는데 옆에 내팽겨쳐둔 이어폰을 귀에 갖다 대니 말소리가 들리는 것이었다
잘 들어보니 Lima, Alpha, Charlie, Tango, Fox... 앞 글자만 따면 lactf이다
게다가 Open Brase, UnderScore?? FLAG를 구성하는 알파벳들로 시작하는 단어들을 나열하고 있었다
 
처음 입력한 플래그가 틀렸다고 나와서 정확한 단어가 무엇인지 보려고 자동 음성 텍스트 변환기를 사용해봤다
Fox를 Box로 내뱉은 것 빼고는 숫자도 잘 듣고 만족스럽게 알려줘서 FLAG를 잘 얻어낼 수 있었다
 
만약 이거 안 쓰고 못 찾았으면 멘탈 나갔을 듯
 


 

misc/one by one

One..... by.......... one............... whew I'm tired, this form is waaaaaaay too long.

Note: the flag does have random characters at the end - that is intentional.
 


아까 Infinite Loop를 풀었다면 이 문제도 어렵지 않게 풀 수 있을 것이다
다만, 시간이 매우 오래 걸린다는 점..
 


이것도 개발자 도구 열면 어지럽게 막 있는데 보면 556692759라는 숫자들이 반복해서 등장한다
그런데 눈을 부릅 뜨고 보면 알파벳 l 부분에 1540876785라는 숫자가 꼽사리로 껴있다
그렇다면 0번 문제의 답은 l 이라는 것이다
 
당연히 눈치가 빠르면 lactf{ 의 l 인 것을 알 수 있다
이런 식으로 혼자 다른 숫자들을 하나하나 찾아서 flag를 찾아내야 한다
이 과정을 27번 정도 해야 한다
 
더군다나 반복해서 등장하는 수는 계속 바뀌어서 그때마다 업데이트를 해줘야 하는데 난 투박하게나마 프로그램을 하나 짜서 하긴 했다
너무 투박해서 이것도 시간이 좀 걸리긴 했지만 하나하나 눈으로 대조하는 것보다는 빠르다
 
만약 진행하다가 숫자가 안 보이고 false로만 가득차 있다면 true를 찾으려 하지 말고 이전 문제로 돌아가서 재확인을 한다
이전 문제가 틀렸으면 숫자가 사라지고 false로 도배된다
 
그리고 26번 마지막 문제는 당연히 FLAG 마지막 글자니까 } 기호일텐데 false로 나온다
문제 설명에서도 나오듯 의도적인 것이니 그냥 중괄호 닫고 제출하면 Correct!라는 문구가 등장한다
참고로 이때 FLAG 완문이 나오지 않으니 문제 넘어갈 때마다 FLAG를 완성시키면서 넘어가기를 추천한다
 


 
이렇게 LA CTF에서 푼 10문제들을 모두 복기해봤다
두 문제 이상만 풀어도 감사할 판에 이렇게 많이 풀 줄은 몰랐기 때문에 아주 기분이 좋은 상태이다
 
13문제 솔브하고 1990점으로 339등 마무리했다
어제 자기 전까지는 300등 안에 들었었는데 나 자는 동안 많은 팀들이 문제를 풀었나보다
저번에도 300등대였는데 이번에는 저번보다 참여 인원이 더 많은 것 같다
 
그리고 문제들도 너무 재밌었다
고뇌 끝에 얻어내는 플래그의 뽕맛은 언제나 잊을 수 없다
특히 misc 문제는 처음 풀어봐서 신기한 것들이 너무 많았고 흥미로웠던 것 같다
 
다른 사람들 Write Up 보면 어떤 문제를 설명할 때 재밌는 문제라고 하는 경우가 많은데 그걸 볼 때마다 나는 그런 거에 재미를 느끼는 변태들만 저런 문제를 풀 수 있구나 라는 생각을 했었다
하지만 이젠 나도 변태가 되어버린 것 같다
 
앞으로도 더 열심히 공부해서 100등 안에 드는 목표를 먼저 달성해보고 더 노력해서 순위권에도 들고 더 나아가서는 상금도 타보는 막대한 꿈을 갖고 열심히 살아봐야겠다

728x90
반응형

댓글