<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>computer noraboja</title>
    <link>https://orangecalculator.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 8 Jul 2026 02:54:25 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>orangecalculator</managingEditor>
    <item>
      <title>pwnable.kr을 마치며...</title>
      <link>https://orangecalculator.tistory.com/19</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이틀 전 2022년 5월 19일 새벽 5시 34분 KST에 마지막 pwnable.kr 챌린지인 crcgen을 완료했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 모든 pwnable.kr의 모든 챌린지를 완주했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜 시간 공을 들여서 완주를 했다는 생각을 하니 뿌듯하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인은 굉장히 어렵게 문제들을 다 풀었는데 이게 DEFCON이나 CTF 대회들보다 훨씬 쉽다니 참 놀라울 따름이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;947&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YxBTl/btrCLAcvtpp/iMHhjNtyynDihPM91xfyPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YxBTl/btrCLAcvtpp/iMHhjNtyynDihPM91xfyPk/img.png&quot; data-alt=&quot;pwnable.kr 인트로 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YxBTl/btrCLAcvtpp/iMHhjNtyynDihPM91xfyPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYxBTl%2FbtrCLAcvtpp%2FiMHhjNtyynDihPM91xfyPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;947&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;947&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;pwnable.kr 인트로 페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pwnable.kr은 &lt;a href=&quot;https://daehee87.github.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;장대희 교수(현 성신여대)&lt;/a&gt;님이 (아마도) 대학원 시절에 만든 사이트이다. 컴퓨터 기술이 빨리 바뀌고 업데이트되는 것에 비하면 꽤 오래된 사이트라고 할 수도 있을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;1175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1rDEy/btrCNNI13HO/tpqLKcYmYlliDCPeakPTYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1rDEy/btrCNNI13HO/tpqLKcYmYlliDCPeakPTYK/img.png&quot; data-alt=&quot;pwnable.kr credit&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1rDEy/btrCNNI13HO/tpqLKcYmYlliDCPeakPTYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1rDEy%2FbtrCNNI13HO%2FtpqLKcYmYlliDCPeakPTYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;1175&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;1175&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;pwnable.kr credit&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인 앞에 이미 수많은 분들이 이미 완주를 하고 떠났었다. 역시 취약점에 대해 다루는 사이트이다 보니 문제의 취약점에 대해 제보하신 분들이 많고, 시스템 자체의 취약점에 대한 부분들에 제보하신 분들도 계신다. 현재 랭킹 상단에 안 계신 분들이 많은 것으로 봐서는 완주하고 사이트에 방문한 지 오래되었는데 문제가 업데이트되면서 랭킹이 바뀐 것이&amp;nbsp;아닐까 싶다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교수님 CV에 보면 사이트는 2014년에 개설되었다고 되어있다. 본인은 2020년에 시작했기 때문에 거의 문제가 추가되거나 삭제되는 걸 못 보긴 했지만, 옛날에 다른 사람들이 해당 사이트 챌린지 하는 동영상 보면 문제가 시간이 지나면서 꽤 많이 업데이트되었던 걸로 보인다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지나가는 말로 장 교수님 굉장히 경력이 화려한 느낌이 든다. 적어도 본인에 비해서는 그렇다... 2014년도에 CTF 입상하셨다고 되어 있는 것을&amp;nbsp;보면 그 이전에 꽤 오래전에 시작하셨을 것 같다. 열정을 가지고 공부를 하면서 이 사이트도 만들었겠거니 싶다. 본인이 이 사이트에서 챌린지 처음 시작할 때는 조지아텍에서 박사 후 과정에 있으셨는데, 학교 생활로 바빠서 한동안 안 하다가 최근 들어서 다시 마무리하려고 들어와 보니 어느새 교수님이 되어서 한국에 들어 계셨다. 실제로 본인이 Grotesque 문제들을 풀 때만 해도 운영자 이메일이 gatech.edu 도메인이었는데 어느새 sungshin.ac.kr 로 바뀌어 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1047&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVipoV/btrCMbQ7Ct9/KkMNjFm95Mh1ovCKo50520/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVipoV/btrCMbQ7Ct9/KkMNjFm95Mh1ovCKo50520/img.png&quot; data-alt=&quot;기록된 챌린지 목록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVipoV/btrCMbQ7Ct9/KkMNjFm95Mh1ovCKo50520/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVipoV%2FbtrCMbQ7Ct9%2FKkMNjFm95Mh1ovCKo50520%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1047&quot; height=&quot;690&quot; data-origin-width=&quot;1047&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기록된 챌린지 목록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인은 사진에 보이는 것처럼 챌린지를 풀 때마다 기록해놓았다. 이 사이트 문제를 풀기 시작한 게 이 블로그 개설하기 이전이라서 그냥 메모장으로 기록을 했다. 맨 처음부터 기록을 한 건 아니고, 지금 보니까 Rookiss의 syscall 문제부터 기록이 되어있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;663&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MU2nE/btrCLkUCifF/TMORO7EkYgfsnw28tQiTo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MU2nE/btrCLkUCifF/TMORO7EkYgfsnw28tQiTo1/img.png&quot; data-alt=&quot;본인이 기록한 첫 챌린지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MU2nE/btrCLkUCifF/TMORO7EkYgfsnw28tQiTo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMU2nE%2FbtrCLkUCifF%2FTMORO7EkYgfsnw28tQiTo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;964&quot; height=&quot;663&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;663&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;본인이 기록한 첫 챌린지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사진에 보이는 것처럼 챌린지를 풀 때마다 기록해놓았는데 항상 문제를 풀 때마다 시작한 시간과 끝낸 시간을 기록해놓았다. 위에 사진을 보면 syscall 문제는 2020년 4월 4일 오후 3시 5분에 시작했다고 나와있는 걸로 보인다. 사진에는 안 나왔지만 5시 50분에 끝낸 걸로 나와있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 수정시간으로 보면 2020년 1월에 시작한 걸로 보인다. 아마 겨울방학에 심심해서 시작한 것 같다. 처음에 친구가 추천해주어서 시작했었는데 어려운 걸 풀어내고 느끼는 성취감 같은 게 있어서 계속하다 보니 끝까지 할 수 있었던 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;663&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nqOAp/btrCJSSvIHf/McfCKeUmOLbzf8zynkI2K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nqOAp/btrCJSSvIHf/McfCKeUmOLbzf8zynkI2K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nqOAp/btrCJSSvIHf/McfCKeUmOLbzf8zynkI2K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnqOAp%2FbtrCJSSvIHf%2FMcfCKeUmOLbzf8zynkI2K1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;964&quot; height=&quot;663&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;663&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 해결한 챌린지는 crcgen인데 새벽 5시 34분에 해결했다. 밤에 문제 열어보고 얼마 안 걸리겠지 싶어서 계속하다가 밤을 새버린 듯하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20220519053558.png&quot; data-origin-width=&quot;1835&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cd682F/btrCI2uADVf/R5pVOX1ZMNJxlHjhfRZwaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cd682F/btrCI2uADVf/R5pVOX1ZMNJxlHjhfRZwaK/img.png&quot; data-alt=&quot;Hacker's Secret 완료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cd682F/btrCI2uADVf/R5pVOX1ZMNJxlHjhfRZwaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcd682F%2FbtrCI2uADVf%2FR5pVOX1ZMNJxlHjhfRZwaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1835&quot; height=&quot;640&quot; data-filename=&quot;20220519053558.png&quot; data-origin-width=&quot;1835&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Hacker's Secret 완료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;crcgen을 해결하고 나서 사이트를 보니까 모든 문제에 초록불이 떠서 사진 한방 찍었다. 이름부터 Hacker's Secret이라서 시작할 때 겁을 좀 먹었었는데, 역시 말 그대로 어렵더라는 이야기다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caIgD5/btrCNNCgHhT/kkkiAOhdQxukRandXUMAMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caIgD5/btrCNNCgHhT/kkkiAOhdQxukRandXUMAMk/img.png&quot; data-alt=&quot;가장 기억에 남는 챌린지 3인방&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caIgD5/btrCNNCgHhT/kkkiAOhdQxukRandXUMAMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaIgD5%2FbtrCNNCgHhT%2FkkkiAOhdQxukRandXUMAMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;603&quot; height=&quot;278&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가장 기억에 남는 챌린지 3인방&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인이 생각하기로 몇 가지 챌린지를 꼽아보자면, tiny랑 towelroot가 도전할 당시 체감으로는 제일 어려웠던 것 같다. 물론 객관적으로 보면 그 뒤에 챌린지들이 더 어려울지도 모르겠지만, 그 후에는 공부를 더 하기도 했고 그 이전에 챌린지를 해결하면서 실력이 좀 더 쌓여서 그랬던 게 아닐까 싶다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기억에 남는 챌린지는 softmmu이다. 세부적인 사항은 이야기하지 못하겠지만, 정말 특이하고 흥미로운 문제라고 말하고 싶다. 물론 본인은 CTF를 제대로 공부한다던가 대회를 나간다던가 이런 건&amp;nbsp;안 하는 사람이고, 그냥 이 사이트는 취미로 한 것이라서&amp;nbsp;이런 문제가 흔할지는 잘 모르겠다. 그런데 이 문제를 처음 접하고 풀 때는 정말 흥미롭고 재밌는 문제라는 생각이 들고, 지금도 기억에 많이 남는 문제로 꼽히는 것 같다. 다행히 이 문제에 사용되는 지식들은 이미 알고 있는 것들이라서 그리 어렵지 않게 풀었던 것 같다. 다만, 이러한 사항이 이렇게 소프트웨어 단계에서 큰 영향을 미친다는 게 참 신선했던 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20220519053627.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1050&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IpvvD/btrCKWGWf9r/RJH7zXSCiq3A35Kmfm0m21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IpvvD/btrCKWGWf9r/RJH7zXSCiq3A35Kmfm0m21/img.png&quot; data-alt=&quot;pwnable.kr 완료 후 랭킹보드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IpvvD/btrCKWGWf9r/RJH7zXSCiq3A35Kmfm0m21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIpvvD%2FbtrCKWGWf9r%2FRJH7zXSCiq3A35Kmfm0m21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1050&quot; data-filename=&quot;20220519053627.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1050&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;pwnable.kr 완료 후 랭킹보드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 다 풀고 나니 점수가 11301점으로 현재 시점 기준으로 1위부터 25위까지 동일한 점수이고, 이 안에서 순위는 완료한 시간 순서이다. 본인 아이디는 뭘까~요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 25명 중에 맨 밑에 있는 ocal이란 아이디지요. 본인 위로 태극기가 가장 낮은 순위에 계신 분이 14위인걸 보면, 생각보다 여러 나라에서 많은 분들이 이 사이트를 이용하고 계신 걸 알 수 있다. 완주하신 분들 국가를 보면 미국, 대한민국, 그리고 잘 몰랐는데 이스라엘 분들이 되게 많다. 본인이 기독교라서 익히 알고는 있었는데 역시 이스라엘이라는 국가는 참 대단한 국가라는 걸 다시 한번 느낀다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 감사한 분들 이야기를 하고 끝마치고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 이런 재밌는 사이트를 만들어주신 장대희 교수님께 감사드린다. 많이 배웠습니다, 교수님. 그런데 엿도 가끔 먹이시던데, 뭐... 그런 부분들에서도 많이 배웠습니다. 감사합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째로, 이런 좋은 사이트를 소개해준 친구에게 고맙다고 하고 싶다. 고맙다 친구야.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, 직접적으로 관련이 있지는 않지만, 이러한 시스템 관련된 지식들을 처음으로 배웠고 어려운 지식을 심도 있게&amp;nbsp;잘 가르쳐주신 김장우 교수님께 감사드린다는 말을 하고 싶습니다. 감사합니다. 비록 한 학기 수업밖에 안됐지만, 정말 많이 배웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Misc</category>
      <category>pwnable.kr</category>
      <author>orangecalculator</author>
      <guid isPermaLink="true">https://orangecalculator.tistory.com/19</guid>
      <comments>https://orangecalculator.tistory.com/19#entry19comment</comments>
      <pubDate>Sat, 21 May 2022 23:48:20 +0900</pubDate>
    </item>
    <item>
      <title>Inspecting the Implementation of QEMU: How QEMU Handles Self-modifying Code</title>
      <link>https://orangecalculator.tistory.com/18</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Some weeks ago, I was trying to solve malware in &lt;a title=&quot;pwnable.kr&quot; href=&quot;https://pwnable.kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;pwnable.kr&lt;/a&gt;. I basically already knew that QEMU basically uses compiler technology called TCG and kind of easily inferred that there could be some timing issues related to code modification at runtime, so I merely &quot;easily&quot; solved it. However, I wanted to find out why. Saying that again, I wanted to check with my own eyes and clearly find out how it works in real QEMU code.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;With that in mind, I quickly searched for the qemu repository on google. I opened the repository and enthusiastically started analyzing the code of QEMU... was the plan, but my laziness yelled at me to immediately close the repository and open twitch.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Now, several weeks passed, but the curiosity seems to have been waiting for the time to show up and settle for the thirst of knowledge. The time had finally came, this weekend.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0. Synopsis&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;So QEMU is an emulator that emulates a software based on the target architecture so that the software can be ran even if the target architecture is different from the host architecture. QEMU is so powerful that it can not only emulate a single executable, but also a full operating system kernel and of course, applications executed on top of the emulated operating system.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QEMU can basically analyze the assembly inside the software and interpret it into the host architecture assembly or in some special cases just interpret it. To do this, QEMU uses an older technology called &lt;a href=&quot;https://github.com/qemu/qemu/tree/v4.2.0/tcg&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Tiny Code Generator(TCG)&lt;/a&gt;. The overall picture is to, first, translate the target assembly to TCG Intermediate Representation(IR), and then translate it again to host assembly, if it is the case. They say that TCG began with the aim of becoming the backend of the gcc compiler&lt;sup class=&quot;footnote&quot;&gt;&lt;a href=&quot;#footnote_18_1&quot; id=&quot;footnote_link_18_1&quot; onmouseover=&quot;tistoryFootnote.show(this, 18, 1)&quot; onmouseout=&quot;tistoryFootnote.hide(18, 1)&quot; style=&quot;color:#f9650d; font-family: Verdana, Sans-serif; display: inline;&quot;&gt;&lt;span style=&quot;display: none;&quot;&gt;[각주:&lt;/span&gt;1&lt;span style=&quot;display: none;&quot;&gt;]&lt;/span&gt;&lt;/a&gt;&lt;/sup&gt;. I guess that is why the overall picture of this technology is so silmilar to the overall picture of modern compilers.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wd3Af/btrzKbUSRDd/cIoZmc3ckvBpH44HAaQwH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wd3Af/btrzKbUSRDd/cIoZmc3ckvBpH44HAaQwH1/img.png&quot; data-alt=&quot;Overall process of how TCG works&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wd3Af/btrzKbUSRDd/cIoZmc3ckvBpH44HAaQwH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwd3Af%2FbtrzKbUSRDd%2FcIoZmc3ckvBpH44HAaQwH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;840&quot; height=&quot;157&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;157&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Overall process of how TCG works&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;In case of most of the applications, or maybe most developers, after the code is compiled to a binary, the code does not change. In this case, it may also be possible for QEMU to just translate all the code into host assembly and the TCG may never be triggered again until the process dies. However, in case of self-modifying code, the memory value that stores the code may change. That means the code changed. If QEMU tries to execute code on that region, it definitely must retranslate it to maintain the correct behavior of the emulated program. This point is the origin of the question. How does QEMU handle this in real code?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;qemu_self_modifying_code_tcg_trigger.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEMOPz/btrzI00mxzP/lOzsRSUUrkJ3AV2kxzhwK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEMOPz/btrzI00mxzP/lOzsRSUUrkJ3AV2kxzhwK0/img.png&quot; data-alt=&quot;Changed code must be retranslated in self-modifying code&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEMOPz/btrzI00mxzP/lOzsRSUUrkJ3AV2kxzhwK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEMOPz%2FbtrzI00mxzP%2FlOzsRSUUrkJ3AV2kxzhwK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-filename=&quot;qemu_self_modifying_code_tcg_trigger.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Changed code must be retranslated in self-modifying code&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. QEMU Introduction&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QEMU is a software that basically emulates various kinds of software. Since it emulates software, there are advantages and disadvantages about it. A big advantage of using QEMU is that a more broader range of applications can be executed on the same machine because it can let you come over the hurdle of architecture differences. For example, you can execute ARM applications on an intel or amd cpu. However, this view is only partial. Since it needs to interpret over the architectural differences, it intrinsically has to be slower than just using native architecture instructions. This disadvantage applies even when the target architecture is the same as the host architecture. So, for example, you can think of this as if some you hired a interpreter to come over the hurdle of language differences, like the situations where two countries are having a summit, but they speak different languages. Saying again, this performance penalty applies even when the architecture is the same. So if possible, you might as well just want to directly execute the application directly on your machine.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;In this article, we are going to take a look at the actual implementation of QEMU. This is possible because QEMU is gratefully open source, based basically on GPL2, though the licensing of the specific files can vary. Here, we will use the code in the &lt;a href=&quot;https://github.com/qemu/qemu&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;QEMU github repository&lt;/a&gt;. The version that we will use is &lt;a href=&quot;https://github.com/qemu/qemu/tree/v4.2.0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;v4.2.0&lt;/a&gt;. If you would like, you can browse the files by opening the commit tagged as &lt;a href=&quot;https://github.com/qemu/qemu/tree/v4.2.0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;v4.2.0&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. QEMU Tiny Code Generator&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QEMU Tiny Code Generator, shortly QEMU TCG, is the main interpreter that is the core part of overcoming the hurdle of architectural differences. As the picture above, through the TCG, the target architecture assembly is interpreted into the host architecture assembly. Now, we are not going to thoroughly go through the TCG internal code, but maybe we can just take a glimpse through it later in this article.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The QEMU TCG tries to minimize the overhead of translating code by using self-modifying code. Self-modifying code is a code that modifies other code to execute. This means that the code itself can generate another code or function to execute at runtime.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Let's look at an example by taking a quick look at the QEMU by actually executing it. &lt;span&gt;I won't show you how to obtain the binary. Just get the source code on&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://github.com/qemu/qemu&quot;&gt;QEMU github repository&lt;/a&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;or maybe you can install it using your linux distro package manager. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QEMU has a functionality to emit logs as specified by the user. If you take a look at the qemu help message below, we can see that qemu provides a '-d' option that logs items specified. It says that we can get more help by the '-d help' option. Let's do it right away.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651500362549&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;user@DESKTOP-4E0H5D7:~/qemu_analysis$ ./qemu-x86_64 -h
usage: qemu-x86_64 [options] program [arguments...]
Linux CPU emulator (compiled for x86_64 emulation)

Options and associated environment variables:

Argument             Env-variable      Description
-h                                     print this help
-help
-g port              QEMU_GDB          wait gdb connection to 'port'
-L path              QEMU_LD_PREFIX    set the elf interpreter prefix to 'path'
-s size              QEMU_STACK_SIZE   set the stack size to 'size' bytes
-cpu model           QEMU_CPU          select CPU (-cpu help for list)
-E var=value         QEMU_SET_ENV      sets targets environment variable (see below)
-U var               QEMU_UNSET_ENV    unsets targets environment variable (see below)
-0 argv0             QEMU_ARGV0        forces target process argv[0] to be 'argv0'
-r uname             QEMU_UNAME        set qemu uname release string to 'uname'
-B address           QEMU_GUEST_BASE   set guest_base address to 'address'
-R size              QEMU_RESERVED_VA  reserve 'size' bytes for guest virtual address space
-d item[,...]        QEMU_LOG          enable logging of specified items (use '-d help' for a list of items)
-dfilter range[,...] QEMU_DFILTER      filter logging based on address range
-D logfile           QEMU_LOG_FILENAME write logs to 'logfile' (default stderr)
-p pagesize          QEMU_PAGESIZE     set the host page size to 'pagesize'
-singlestep          QEMU_SINGLESTEP   run in singlestep mode
-strace              QEMU_STRACE       log system calls
-seed                QEMU_RAND_SEED    Seed for pseudo-random number generator
-trace               QEMU_TRACE        [[enable=]&amp;lt;pattern&amp;gt;][,events=&amp;lt;file&amp;gt;][,file=&amp;lt;file&amp;gt;]
-version             QEMU_VERSION      display version information and exit&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;It again shows lots of options to log. Since we want to look at the translate process, we will give four options: in_asm, op, op_opt, out_asm. We need in_asm and out_asm to look at the translation input and output. In addition, QEMU uses intermediate representation as I described before. Those intermediate representations, shortly IRs, are called &quot;micro ops&quot; here and can be logged out by using the op option. In addition, we will also take a look at the IRs after optimization by using the op_opt option, which shows the IR sequence right before it gets translated back to the output assembly.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651501914944&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;user@DESKTOP-4E0H5D7:~/qemu_analysis$ ./qemu-x86_64 -d help
Log items (comma separated):
out_asm         show generated host assembly code for each compiled TB
in_asm          show target assembly code for each compiled TB
op              show micro ops for each compiled TB
op_opt          show micro ops after optimization
op_ind          show micro ops before indirect lowering
int             show interrupts/exceptions in short format
exec            show trace before each executed TB (lots of logs)
cpu             show CPU registers before entering a TB (lots of logs)
fpu             include FPU registers in the 'cpu' logging
mmu             log MMU-related activities
pcall           x86 only: show protected mode far calls/returns/exceptions
cpu_reset       show CPU state before CPU resets
unimp           log unimplemented functionality
guest_errors    log when the guest OS does something invalid (eg accessing a
non-existent register)
page            dump pages at beginning of user mode emulation
nochain         do not chain compiled TBs so that &quot;exec&quot; and &quot;cpu&quot; show
complete traces
trace:PATTERN   enable trace events

Use &quot;-d trace:help&quot; to get a list of trace events.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I will compile this helloworld example and execute.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651500671494&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

int main(){
	printf(&quot;Hello, World!\n&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Now let's really execute it! Note that this emits lots of logs, so you may want to pipe the output somewhere in the shell.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651500793811&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;user@DESKTOP-4E0H5D7:~/qemu_analysis$ ./qemu-x86_64 -d in_asm,cpu,exec -D hello.qemulog hello
Hello, World!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I used the -D option to redirect the log to the file &quot;hello.qemulog&quot;. Let's look at the file. We don't need to look at all of it. We will just try to find the main function. To find the main function accurately, we first dump the emulated binary and take a look at how the main function is compiled.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651500970613&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;user@DESKTOP-4E0H5D7:~/qemu_analysis$ objdump -d hello

hello:     file format elf64-x86-64


Disassembly of section .init:

0000000000001000 &amp;lt;_init&amp;gt;:
    1000:       f3 0f 1e fa             endbr64
    1004:       48 83 ec 08             sub    $0x8,%rsp
    1008:       48 8b 05 d9 2f 00 00    mov    0x2fd9(%rip),%rax        # 3fe8 &amp;lt;__gmon_start__&amp;gt;
    100f:       48 85 c0                test   %rax,%rax
    1012:       74 02                   je     1016 &amp;lt;_init+0x16&amp;gt;
    1014:       ff d0                   callq  *%rax
    
...
    
0000000000001149 &amp;lt;main&amp;gt;:
    1149:       f3 0f 1e fa             endbr64
    114d:       55                      push   %rbp
    114e:       48 89 e5                mov    %rsp,%rbp
    1151:       48 8d 3d ac 0e 00 00    lea    0xeac(%rip),%rdi        # 2004 &amp;lt;_IO_stdin_used+0x4&amp;gt;
    1158:       e8 f3 fe ff ff          callq  1050 &amp;lt;puts@plt&amp;gt;
    115d:       b8 00 00 00 00          mov    $0x0,%eax
    1162:       5d                      pop    %rbp
    1163:       c3                      retq
    1164:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
    116b:       00 00 00
    116e:       66 90                   xchg   %ax,%ax

...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Taking a look at the main function, we can see that the assembly sequence is straight forward. First of all, since the format string does not contain a format specifier, i.e., formatting doesn't happen, the function is substituted to the puts function. This is a really basic optimization these days. Honestly, I didn't even give any optimization options, but this optimization happens automatically. Secondly, we can see that the argument is loaded using the lea instruction.&amp;nbsp; Since it is a constant string, it would just be a pointer to a part of the binary file, so relative addressing by the lea instruction is sufficient. Thirdly, the main function return code, 0, is loaded into the return value register using the mov instruction. Lastly, the rest of the code on the start and end seems to be the stack frame setup and cleanup code.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Now let's really look at what the log from QEMU. We will look at the movq instruction at the second line of the main function assembly below.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651502636109&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;----------------
IN: main

...

0x400000114d:  55                       pushq    %rbp
0x400000114e:  48 89 e5                 movq     %rsp, %rbp
0x4000001151:  48 8d 3d ac 0e 00 00     leaq     0xeac(%rip), %rdi
0x4000001158:  e8 f3 fe ff ff           callq    0x4000001050&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;We can easily find what the input assembly was because the QEMU log kindly tells us the input assembly address. It says that instead of moving the value directly from rsp to rbp, the value goes through a temporary register tmp0.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651502999997&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;OP:

...

---- 000000400000114e 0000000000000000
 mov_i64 tmp0,rsp
 mov_i64 rbp,tmp0

...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;We can see that after optimization, it is optimized to just directly move the value from rsp to rbp.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651503022656&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;OP after optimization and liveness analysis:

...

---- 000000400000114d 0000000000000000
 mov_i64 tmp0,rbp
 movi_i64 tmp13,$0x8
 sub_i64 tmp2,rsp,tmp13
 qemu_st_i64 tmp0,tmp2,leq,0
 mov_i64 rsp,tmp2

---- 000000400000114e 0000000000000000
 mov_i64 rbp,rsp                          sync: 0  dead: 0  pref=0xffff
 
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Now, we need to think a bit to find what instruction is the output of the IR above. I can tell you that the value of rsp is stored in the output assembly register rbx. How do I know that? Well, to explain this, I added the IR from the former instruction above. It loads an immediate value 8 and then subtracts it from the rsp register. This subtraction corresponds to the subq instruction in the output assembly. So the rsp value is in the rbx register. Now, we can see that two mov_i64 instructions store the same value to rsp and rbp consecutively in the IR above. So it is in the output assembly below, represented as two consecutive movq instructions from the rbx register. So that part is the final output. You can also see that the instructions are interleaved. It seems that some additional optimizations took place on the process of outputting host assembly.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651503031592&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;OUT: [size=117]

...

0x557ff4fb1b4b:  48 8b 5d 20              movq     0x20(%rbp), %rbx
0x557ff4fb1b4f:  48 83 eb 08              subq     $8, %rbx
0x557ff4fb1b53:  4c 8b 65 28              movq     0x28(%rbp), %r12
0x557ff4fb1b57:  4c 89 23                 movq     %r12, (%rbx)
0x557ff4fb1b5a:  48 89 5d 20              movq     %rbx, 0x20(%rbp)
0x557ff4fb1b5e:  48 89 5d 28              movq     %rbx, 0x28(%rbp)
0x557ff4fb1b62:  49 bc 04 20 00 00 40 00  movabsq  $0x4000002004, %r12
0x557ff4fb1b6a:  00 00

...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Finding the QEMU Emulation Main Loop&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Now that we understood how the IR translation works briefly, we need to see where it actually is called in the QEMU implementation. To find the main function, I will use a little hack using objdump. The objdump tool has a '-l' option that shows the source code line information where the assembly originated from. Finding the main function, we can see that the main function is in the &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/linux-user/main.c#L617&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;linux-user/main.c&lt;/a&gt; file. Now we can take a look at the source code.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651503853726&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;user@DESKTOP-4E0H5D7:~/qemu_analysis/qemu/build$ objdump -xd -l qemu-x86_64

...

0000000000072ed0 &amp;lt;main&amp;gt;:
main():
/home/user/qemu_analysis/qemu/linux-user/main.c:618
   72ed0:       f3 0f 1e fa             endbr64
   72ed4:       41 57                   push   %r15
   72ed6:       41 56                   push   %r14
   72ed8:       41 55                   push   %r13
   72eda:       41 54                   push   %r12
   72edc:       49 89 f4                mov    %rsi,%r12
   72edf:       55                      push   %rbp
   72ee0:       53                      push   %rbx
   72ee1:       48 81 ec 48 06 00 00    sub    $0x648,%rsp
   72ee8:       89 7c 24 08             mov    %edi,0x8(%rsp)

...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Finally, we can look at C code, which is much more friendly than assembly. We can easily see that QEMU won't just exit until the program requests it, so it will have some main loop that it will keep executing, perhaps forever if the program doesn't exit forever. Keeping that in mind, the cpu_loop function call seems interesting.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651504151530&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main(int argc, char **argv, char **envp)
{
    struct target_pt_regs regs1, *regs = &amp;amp;regs1;
    struct image_info info1, *info = &amp;amp;info1;
    struct linux_binprm bprm;
    TaskState *ts;
    CPUArchState *env;
    CPUState *cpu;
    int optind;
    char **target_environ, **wrk;
    char **target_argv;
    int target_argc;
    int i;
    int ret;
    int execfd;
    unsigned long max_reserved_va;

...

    cpu_loop(env);
    /* never exits */
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;We continue using the same kind of hack to find the source code.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651504345617&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;00000000000eb510 &amp;lt;cpu_loop&amp;gt;:
cpu_loop():
/home/user/qemu_analysis/qemu/linux-user/x86_64/../i386/cpu_loop.c:85
   eb510:       f3 0f 1e fa             endbr64
   eb514:       41 55                   push   %r13
   eb516:       49 89 fd                mov    %rdi,%r13
   eb519:       41 54                   push   %r12
   eb51b:       55                      push   %rbp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;We can see that this function lives at &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/linux-user/i386/cpu_loop.c#L84&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;linux-user/x86_64/../i386/cpu_loop.c&lt;/a&gt; which is &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/linux-user/i386/cpu_loop.c#L84&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;linux-user/i386/cpu_loop.c&lt;/a&gt;. Taking a look at the source code, there is loop in it. Now, we want to find where the TCG related actions happen, so we want to get in further. The cpu_exec function seems interesting in that TCG may happen on execution. Let's take a look at that function.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651504500100&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void cpu_loop(CPUX86State *env)
{
    CPUState *cs = env_cpu(env);
    int trapnr;
    abi_ulong pc;
    abi_ulong ret;
    target_siginfo_t info;

    for(;;) {
        cpu_exec_start(cs);
        trapnr = cpu_exec(cs);
        cpu_exec_end(cs);
        process_queued_cpu_work(cs);

        switch(trapnr) {

...

        }
        process_pending_signals(env);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Looking at objdump, the cpu_exec function is located in &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/cpu-exec.c#L659&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;accel/tcg/cpu-exec.c&lt;/a&gt;. Let's take a look at it.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651504627951&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;00000000000b7960 &amp;lt;cpu_exec&amp;gt;:
cpu_exec():
/home/user/qemu_analysis/qemu/accel/tcg/cpu-exec.c:662
   b7960:       f3 0f 1e fa             endbr64
   b7964:       41 57                   push   %r15
   b7966:       41 56                   push   %r14
   b7968:       41 55                   push   %r13&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;It kindly says in the comment, &quot;main execution loop&quot;. Jackpot! It is highly likely what we were looking for. We can see the struct type TranslationBlock, which sounds like a code compiling related type, like a basic block in the control flow graph. There are two interesting functions, tb_find and cpu_loop_exec_tb, which both contain the acronym &quot;tb&quot;, which would be the acronym for &quot;TranslationBlock&quot;. So it looks like we found the right place.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651504685227&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* main execution loop */

int cpu_exec(CPUState *cpu)
{
    CPUClass *cc = CPU_GET_CLASS(cpu);
    int ret;
    SyncClocks sc = { 0 };

...

    /* if an exception is pending, we execute it here */
    while (!cpu_handle_exception(cpu, &amp;amp;ret)) {
        TranslationBlock *last_tb = NULL;
        int tb_exit = 0;

        while (!cpu_handle_interrupt(cpu, &amp;amp;last_tb)) {
            uint32_t cflags = cpu-&amp;gt;cflags_next_tb;
            TranslationBlock *tb;

            /* When requested, use an exact setting for cflags for the next
               execution.  This is used for icount, precise smc, and stop-
               after-access watchpoints.  Since this request should never
               have CF_INVALID set, -1 is a convenient invalid value that
               does not require tcg headers for cpu_common_reset.  */
            if (cflags == -1) {
                cflags = curr_cflags();
            } else {
                cpu-&amp;gt;cflags_next_tb = -1;
            }

            tb = tb_find(cpu, last_tb, tb_exit, cflags);
            cpu_loop_exec_tb(cpu, tb, &amp;amp;last_tb, &amp;amp;tb_exit);
            /* Try to align the host and virtual clocks
               if the guest is in advance */
            align_clocks(&amp;amp;sc, cpu);
        }
    }

    cc-&amp;gt;cpu_exec_exit(cpu);
    rcu_read_unlock();

    return ret;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Constructing a Self-modifying Code&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Now that we found the entrance to code translation and execution, we need to get on the main goal of this article. By this time, you may have forgot what the main goal of the article. The title says &quot;How QEMU Handles Self-modifying Code&quot;. So how does it? To try to find that, I am not going to just do more static analysis, blindly searching for another jackpot. So what will we do now? We will do some dynamic analysis smartly. ​&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;To do a dynamic analysis, we will need to create one. Since really compiling something is overly complex for a simple payload, we will just create a simple assembly function sequence and just copy that.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;We make a simple assembly function that does a syscall to print a string. It can be written as follows.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651667728848&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mov edx, 6
mov esi, 0x20000
mov edi, 1
mov eax, 1
syscall
ret&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;This can be equivalently writen in C code as follows. It specifically issues the write system call that writes 6 bytes on the address 0x20000 to stdout. Also, I didn't put position dependent instructions, so this function can be copied to any address without any issues.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651667797597&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;long foo() {
	return write(fd_stdout, (void *)0x20000, 6);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;To use this code properly, we will have to acquire memory accessible at location 0x20000. So we will mmap a page at address 0x20000 and copy &quot;Hello!&quot;, which is 6 bytes.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;To embed this code into C code, we will get the byte string which encodes this assembly code using an &lt;a href=&quot;https://defuse.ca/online-x86-assembler.htm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;online assembly tool&lt;/a&gt;. It kindly gives the byte string in C array initializer format, so we will use it right away.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Now we embed it in C code. We will mmap a writable and executable page and copy the assembly code there, and then call the function there.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651668683739&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;assert.h&amp;gt;
#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;time.h&amp;gt;

/*
 * assuming string literal at 0x20000
 * return write(stdout, &quot;hello\n&quot;, 6);
 */
uint8_t shellcode[] = { 
    0xBA, 0x06, 0x00, 0x00, 0x00, 
    0xBE, 0x00, 0x00, 0x02, 0x00, 
    0xBF, 0x01, 0x00, 0x00, 0x00, 
    0xB8, 0x01, 0x00, 0x00, 0x00, 
    0x0F, 0x05, 
    0xC3 
};

int main(){
        int ret;
        void *p = NULL;
        void (*shellcode_copied)(void);

        p = mmap((void *)0x20000UL, 0x1000, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_ANONYMOUS|MAP_SHARED, -1, 0);
        if(p == MAP_FAILED){
                perror(&quot;mmap&quot;);
                return 0;
        }
        assert(p == (void *)0x20000UL);

        memcpy(p, &quot;hello\n&quot;, 6);

        shellcode_copied = (void *)(p + 0x100);

        for(int k=0;k&amp;lt;0x10;++k){
                memcpy(shellcode_copied, shellcode, sizeof(shellcode));
                shellcode_copied();
        }

        ret = munmap(p, 0x1000);
        if(ret){
                perror(&quot;munmap&quot;);
                return 0;
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Now we execute it. Seems good. Let's use gdb to look at it thoroughly.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651668824766&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;user@DESKTOP-4E0H5D7:~/qemu_analysis$ cc -o shellcode_exec shellcode_exec.c
user@DESKTOP-4E0H5D7:~/qemu_analysis$ ./shellcode_exec
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;What? Why SIGSEGV? Well, in the first place I thought I stepped onto a rare bug of QEMU. The truth was that it wasn't a bug. The QEMU uses the SIGSEGV signal to efficiently handle self-modifying code.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651668920643&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;user@DESKTOP-4E0H5D7:~/qemu_analysis$ gdb -q qemu-x86_64
Reading symbols from qemu-x86_64...
(gdb) r shellcode_exec
Starting program: /home/krlee/qemu_analysis/qemu-x86_64 shellcode_exec
[Thread debugging using libthread_db enabled]
Using host libthread_db library &quot;/lib/x86_64-linux-gnu/libthread_db.so.1&quot;.
[New Thread 0x7ffff7a93700 (LWP 237)]
hello

Thread 1 &quot;qemu-x86_64&quot; received signal SIGSEGV, Segmentation fault.
0x0000555555a2677e in static_code_gen_buffer ()
(gdb)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;If you look at the signal manual, we can see that, surprisingly, the SIGSEGV signal is catchable. The manual says signals SIGKILL and SIGSTOP are not catchable. Then SIGSEGV can be? Yes, it is catchable. The QEMU uses this facility to efficiently detect changes of already translated code and invalidate it if so. In fact, without gdb, it executes without any notice.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651669023852&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;user@DESKTOP-4E0H5D7:~/qemu_analysis$ man 7 signal
...
       SIGQUIT      P1990      Core    Quit from keyboard
       SIGSEGV      P1990      Core    Invalid memory reference
       SIGSTKFLT      -        Term    Stack fault on coprocessor (unused)
...
       SIGWINCH       -        Ign     Window resize signal (4.3BSD, Sun)

       The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
...

user@DESKTOP-4E0H5D7:~/qemu_analysis$ ./qemu-x86_64 shellcode_exec
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;So now we can see that we have constructed a correctly running self-modifying code sample and it seems to be executed in QEMU without any faults.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Translation Block eviction on SIGSEGV handler in QEMU&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;This is the climax of this article. We are not looking at the implementation of how self-modifying code gets evicted. On SIGSEGV, the execution stops at static_code_gen_buffer. It looks like it stopped at it, but this is where SIGSEGV was issued. If we do a little stepi command once and then see the backtrace, it says that it is currently at the signal handler function named &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/linux-user/signal.c#L651&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;host_signal_handler&lt;/a&gt;.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651669791691&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Thread 1 &quot;qemu-x86_64&quot; received signal SIGSEGV, Segmentation fault.
0x0000555555a2677e in static_code_gen_buffer ()
(gdb) si
host_signal_handler (host_signum=11, info=0x7fffffffd170, puc=0x7fffffffd040)
    at /home/krlee/qemu_analysis/qemu/linux-user/signal.c:653
653     {
(gdb) bt
#0  host_signal_handler (host_signum=11, info=0x7fffffffd170, puc=0x7fffffffd040)
    at /home/krlee/qemu_analysis/qemu/linux-user/signal.c:653
#1  &amp;lt;signal handler called&amp;gt;
#2  0x0000555555a2677e in static_code_gen_buffer ()
#3  0x000055555560bce0 in cpu_tb_exec (itb=&amp;lt;optimized out&amp;gt;, cpu=0x555555a27000 &amp;lt;static_code_gen_buffer+607808&amp;gt;)
    at /home/krlee/qemu_analysis/qemu/accel/tcg/cpu-exec.c:172
#4  cpu_loop_exec_tb (tb_exit=&amp;lt;synthetic pointer&amp;gt;, last_tb=&amp;lt;synthetic pointer&amp;gt;, tb=&amp;lt;optimized out&amp;gt;,
    cpu=0x555555a27000 &amp;lt;static_code_gen_buffer+607808&amp;gt;) at /home/krlee/qemu_analysis/qemu/accel/tcg/cpu-exec.c:618
#5  cpu_exec (cpu=cpu@entry=0x5555579cba20) at /home/krlee/qemu_analysis/qemu/accel/tcg/cpu-exec.c:731
#6  0x000055555563f558 in cpu_loop (env=0x5555579d3cf0)
    at /home/krlee/qemu_analysis/qemu/linux-user/x86_64/../i386/cpu_loop.c:94
#7  0x00005555555c746d in main (argc=&amp;lt;optimized out&amp;gt;, argv=0x7fffffffe338, envp=&amp;lt;optimized out&amp;gt;)
    at /home/krlee/qemu_analysis/qemu/linux-user/main.c:865&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;On the same time, gdb kindly tells us where the source code is. If we look at the implementation, the comment again confesses that the CPU emulator uses some host signals to detect exceptions. So I guess we are on the right track. Let's get into &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/user-exec.c#L304&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;cpu_signal_handler&lt;/a&gt; to continue.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651669972001&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void host_signal_handler(int host_signum, siginfo_t *info,
                                void *puc)
{
    CPUArchState *env = thread_cpu-&amp;gt;env_ptr;
    CPUState *cpu = env_cpu(env);
    TaskState *ts = cpu-&amp;gt;opaque;

    int sig;
    target_siginfo_t tinfo;
    ucontext_t *uc = puc;
    struct emulated_sigtable *k;

    /* the CPU emulator uses some host signals to detect exceptions,
       we forward to it some signals */
    if ((host_signum == SIGSEGV || host_signum == SIGBUS)
        &amp;amp;&amp;amp; info-&amp;gt;si_code &amp;gt; 0) {
        if (cpu_signal_handler(host_signum, info, puc))
            return;
    }

...

    /* interrupt the virtual CPU as soon as possible */
    cpu_exit(thread_cpu);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Again using gdb, we locate the &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/user-exec.c#L304&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;source code&lt;/a&gt;.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651670094489&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) n
654         CPUArchState *env = thread_cpu-&amp;gt;env_ptr;
(gdb)
656         TaskState *ts = cpu-&amp;gt;opaque;
(gdb)
665         if ((host_signum == SIGSEGV || host_signum == SIGBUS)
(gdb)
667             if (cpu_signal_handler(host_signum, info, puc))
(gdb) s
cpu_x86_signal_handler (host_signum=11, pinfo=pinfo@entry=0x7fffffffd170, puc=0x7fffffffd040)
    at /home/krlee/qemu_analysis/qemu/accel/tcg/user-exec.c:306
306     {&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The main part seems to be in the function &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/user-exec.c#L57&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;handle_cpu_signal&lt;/a&gt;. Let's keep moving.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651670201706&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int cpu_signal_handler(int host_signum, void *pinfo,
                       void *puc)
{
    siginfo_t *info = pinfo;
    unsigned long pc;
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
    ucontext_t *uc = puc;
#elif defined(__OpenBSD__)
    struct sigcontext *uc = puc;
#else
    ucontext_t *uc = puc;
#endif

    pc = PC_sig(uc);
    return handle_cpu_signal(pc, info,
                             TRAP_sig(uc) == 0xe ? (ERROR_sig(uc) &amp;gt;&amp;gt; 1) &amp;amp; 1 : 0,
                             &amp;amp;MASK_sig(uc));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Using dynamic analysis, we can see that the function &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/translate-all.c#L2600&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;page_unprotect&lt;/a&gt; is triggered.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651670967600&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;handle_cpu_signal (old_set=0x7fffffffd168, is_write=1, info=0x7fffffffd170, pc=93824997287806) at /home/krlee/qemu_analysis/qemu/accel/tcg/user-exec.c:64
64          CPUState *cpu = current_cpu;
(gdb) n
69          switch (helper_retaddr) {
(gdb)
97              pc += GETPC_ADJ;
(gdb)
98              break;
(gdb)
126         if (!cpu || !cpu-&amp;gt;running) {
(gdb)
147         if (is_write &amp;amp;&amp;amp; info-&amp;gt;si_signo == SIGSEGV &amp;amp;&amp;amp; info-&amp;gt;si_code == SEGV_ACCERR &amp;amp;&amp;amp;
(gdb)
149             switch (page_unprotect(h2g(address), pc)) {&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The comment on function page_unprotect says that it invalidates code if the page is unprotected. It seems to find the page information and invalidates all the translation blocks, and then finally turn on the real page write permission.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651671308370&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* called from signal handler: invalidate the code and unprotect the
 * page. Return 0 if the fault was not handled, 1 if it was handled,
 * and 2 if it was handled but the caller must cause the TB to be
 * immediately exited. (We can only return 2 if the 'pc' argument is
 * non-zero.)
 */
int page_unprotect(target_ulong address, uintptr_t pc)
{

...

    p = page_find(address &amp;gt;&amp;gt; TARGET_PAGE_BITS);
    if (!p) {
        mmap_unlock();
        return 0;
    }

...

            for (addr = host_start; addr &amp;lt; host_end; addr += TARGET_PAGE_SIZE) {
                p = page_find(addr &amp;gt;&amp;gt; TARGET_PAGE_BITS);
                p-&amp;gt;flags |= PAGE_WRITE;
                prot |= p-&amp;gt;flags;

                /* and since the content will be modified, we must invalidate
                   the corresponding translated code. */
                current_tb_invalidated |= tb_invalidate_phys_page(addr, pc);
#ifdef CONFIG_USER_ONLY
                if (DEBUG_TB_CHECK_GATE) {
                    tb_invalidate_check(addr);
                }
#endif
            }
            mprotect((void *)g2h(host_start), qemu_host_page_size,
                     prot &amp;amp; PAGE_BITS);

...

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;At this point, it seems sufficient to stop, but let's just go a few steps further. Let's look at function &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/translate-all.c#L2071&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tb_invalidate_phys_page&lt;/a&gt;. It seems to have a for loop that iterates over all the translation blocks and invalidates it. This may be a source of inefficiency if lots of jumping happens on self-modifying code in the same page, though it might not be the common case. Let's step further and see the function &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/translate-all.c#L1481&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tb_phys_invalidate&lt;/a&gt;.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651671591204&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static bool tb_invalidate_phys_page(tb_page_addr_t addr, uintptr_t pc)
{
    TranslationBlock *tb;
    PageDesc *p;
    int n;

...

    PAGE_FOR_EACH_TB(p, tb, n) {
#ifdef TARGET_HAS_PRECISE_SMC
        if (current_tb == tb &amp;amp;&amp;amp;
            (tb_cflags(current_tb) &amp;amp; CF_COUNT_MASK) != 1) {
                /* If we are modifying the current TB, we must stop
                   its execution. We could be more precise by checking
                   that the modification is after the current PC, but it
                   would require a specialized function to partially
                   restore the CPU state */

            current_tb_modified = 1;
            cpu_restore_state_from_tb(cpu, current_tb, pc, true);
            cpu_get_tb_cpu_state(env, &amp;amp;current_pc, &amp;amp;current_cs_base,
                                 &amp;amp;current_flags);
        }
#endif /* TARGET_HAS_PRECISE_SMC */
        tb_phys_invalidate(tb, addr);
    }

...

    return false;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The comment says it invalidates one TB. The main implementation seems to be in function &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/translate-all.c#L1417&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;do_tb_phys_invalidat&lt;/a&gt;e. Let's get into it.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651671772102&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* invalidate one TB
 *
 * Called with mmap_lock held in user-mode.
 */
void tb_phys_invalidate(TranslationBlock *tb, tb_page_addr_t page_addr)
{
    if (page_addr == -1 &amp;amp;&amp;amp; tb-&amp;gt;page_addr[0] != -1) {
        page_lock_tb(tb);
        do_tb_phys_invalidate(tb, true);
        page_unlock_tb(tb);
    } else {
        do_tb_phys_invalidate(tb, false);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I copied the whole function because it contains a lot to observe. It removes the translation block from several lists that it may possibly be in. I think this is a good place to stop, since we found the location where the page permission is changed and the cache is invalidated.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651672157592&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void do_tb_phys_invalidate(TranslationBlock *tb, bool rm_from_page_list)
{
    CPUState *cpu;
    PageDesc *p;
    uint32_t h;
    tb_page_addr_t phys_pc;

    assert_memory_lock();

    /* make sure no further incoming jumps will be chained to this TB */
    qemu_spin_lock(&amp;amp;tb-&amp;gt;jmp_lock);
    atomic_set(&amp;amp;tb-&amp;gt;cflags, tb-&amp;gt;cflags | CF_INVALID);
    qemu_spin_unlock(&amp;amp;tb-&amp;gt;jmp_lock);

    /* remove the TB from the hash list */
    phys_pc = tb-&amp;gt;page_addr[0] + (tb-&amp;gt;pc &amp;amp; ~TARGET_PAGE_MASK);
    h = tb_hash_func(phys_pc, tb-&amp;gt;pc, tb-&amp;gt;flags, tb_cflags(tb) &amp;amp; CF_HASH_MASK,
                     tb-&amp;gt;trace_vcpu_dstate);
    if (!(tb-&amp;gt;cflags &amp;amp; CF_NOCACHE) &amp;amp;&amp;amp;
        !qht_remove(&amp;amp;tb_ctx.htable, tb, h)) {
        return;
    }

    /* remove the TB from the page list */
    if (rm_from_page_list) {
        p = page_find(tb-&amp;gt;page_addr[0] &amp;gt;&amp;gt; TARGET_PAGE_BITS);
        tb_page_remove(p, tb);
        invalidate_page_bitmap(p);
        if (tb-&amp;gt;page_addr[1] != -1) {
            p = page_find(tb-&amp;gt;page_addr[1] &amp;gt;&amp;gt; TARGET_PAGE_BITS);
            tb_page_remove(p, tb);
            invalidate_page_bitmap(p);
        }
    }

    /* remove the TB from the hash list */
    h = tb_jmp_cache_hash_func(tb-&amp;gt;pc);
    CPU_FOREACH(cpu) {
        if (atomic_read(&amp;amp;cpu-&amp;gt;tb_jmp_cache[h]) == tb) {
            atomic_set(&amp;amp;cpu-&amp;gt;tb_jmp_cache[h], NULL);
        }
    }

    /* suppress this TB from the two jump lists */
    tb_remove_from_jmp_list(tb, 0);
    tb_remove_from_jmp_list(tb, 1);

    /* suppress any remaining jumps to this TB */
    tb_jmp_unlink(tb);

    atomic_set(&amp;amp;tcg_ctx-&amp;gt;tb_phys_invalidate_count,
               tcg_ctx-&amp;gt;tb_phys_invalidate_count + 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. Then Where Does Page Permission Get Discarded?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;So if the page is marked not writable, then it should have been discarded because the guest process writes code into that page before any translation blocks are made, i.e., is executed. Where would that be?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Again, we use dynamic analysis. Now we know that the page permission is added by mprotect call, it is highly likely that the same permission is discarded by the same mprotect call. So we put a breakpoint on the mprotect call and see where it proceeds in gdb.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;If you keep continuing several times, you can see that the backtrace relays between two different traces. One is called within the signal handlers, but the other one is called at function &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/cpu-exec.c#L395&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tb_find&lt;/a&gt; which is called at function &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/cpu-exec.c#L659&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;cpu_exec&lt;/a&gt;, which is the main loop that we found. So this seems to be what we are looking for.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651673116774&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) b mprotect
Breakpoint 4 at 0x7ffff7c249d0: file ../sysdeps/unix/syscall-template.S, line 78.
(gdb) c
Continuing.

Thread 1 &quot;qemu-x86_64&quot; hit Breakpoint 4, mprotect () at ../sysdeps/unix/syscall-template.S:78
78      ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) bt
#0  mprotect () at ../sysdeps/unix/syscall-template.S:78
#1  0x000055555560e583 in page_unprotect (address=&amp;lt;optimized out&amp;gt;, pc=pc@entry=93824997287808) at /home/krlee/qemu_analysis/qemu/accel/tcg/translate-all.c:2658
#2  0x000055555560ed7d in handle_cpu_signal (old_set=0x7fffffffd168, is_write=&amp;lt;optimized out&amp;gt;, info=0x7fffffffd170, pc=93824997287808) at /home/krlee/qemu_analysis/qemu/accel/tcg/user-exec.c:149
#3  cpu_x86_signal_handler (host_signum=&amp;lt;optimized out&amp;gt;, pinfo=pinfo@entry=0x7fffffffd170, puc=0x7fffffffd040) at /home/krlee/qemu_analysis/qemu/accel/tcg/user-exec.c:318
#4  0x0000555555638c6a in host_signal_handler (host_signum=11, info=0x7fffffffd170, puc=0x7fffffffd040) at /home/krlee/qemu_analysis/qemu/linux-user/signal.c:667
#5  &amp;lt;signal handler called&amp;gt;
#6  0x0000555555a2677e in static_code_gen_buffer ()
#7  0x000055555560bce0 in cpu_tb_exec (itb=&amp;lt;optimized out&amp;gt;, cpu=0x555555a26dc0 &amp;lt;static_code_gen_buffer+607232&amp;gt;) at /home/krlee/qemu_analysis/qemu/accel/tcg/cpu-exec.c:172
#8  cpu_loop_exec_tb (tb_exit=&amp;lt;synthetic pointer&amp;gt;, last_tb=&amp;lt;synthetic pointer&amp;gt;, tb=&amp;lt;optimized out&amp;gt;, cpu=0x555555a26dc0 &amp;lt;static_code_gen_buffer+607232&amp;gt;) at /home/krlee/qemu_analysis/qemu/accel/tcg/cpu-exec.c:618
#9  cpu_exec (cpu=cpu@entry=0x5555579cba20) at /home/krlee/qemu_analysis/qemu/accel/tcg/cpu-exec.c:731
#10 0x000055555563f558 in cpu_loop (env=0x5555579d3cf0) at /home/krlee/qemu_analysis/qemu/linux-user/x86_64/../i386/cpu_loop.c:94
#11 0x00005555555c746d in main (argc=&amp;lt;optimized out&amp;gt;, argv=0x7fffffffe338, envp=&amp;lt;optimized out&amp;gt;) at /home/krlee/qemu_analysis/qemu/linux-user/main.c:865
(gdb) c
Continuing.

Thread 1 &quot;qemu-x86_64&quot; hit Breakpoint 4, mprotect () at ../sysdeps/unix/syscall-template.S:78
78      in ../sysdeps/unix/syscall-template.S
(gdb) bt
#0  mprotect () at ../sysdeps/unix/syscall-template.S:78
#1  0x000055555560daaf in tb_page_add (tb=0x555555a26ec0 &amp;lt;static_code_gen_buffer+607488&amp;gt;, n=0, p=&amp;lt;optimized out&amp;gt;, p=&amp;lt;optimized out&amp;gt;, page_addr=131072) at /home/krlee/qemu_analysis/qemu/accel/tcg/translate-all.c:1567
#2  tb_page_add (p=0x555557a1e410, p=0x555557a1e410, page_addr=131072, n=0, tb=0x555555a26ec0 &amp;lt;static_code_gen_buffer+607488&amp;gt;) at /home/krlee/qemu_analysis/qemu/accel/tcg/translate-all.c:1530
#3  tb_link_page (phys_page2=&amp;lt;optimized out&amp;gt;, phys_pc=131328, tb=0x555555a26ec0 &amp;lt;static_code_gen_buffer+607488&amp;gt;) at /home/krlee/qemu_analysis/qemu/accel/tcg/translate-all.c:1622
#4  tb_gen_code (cpu=cpu@entry=0x5555579cba20, pc=pc@entry=131328, cs_base=cs_base@entry=0, flags=flags@entry=4243635, cflags=&amp;lt;optimized out&amp;gt;, cflags@entry=0) at /home/krlee/qemu_analysis/qemu/accel/tcg/translate-all.c:1866
#5  0x000055555560befc in tb_find (cf_mask=0, tb_exit=0, last_tb=0x0, cpu=0x0) at /home/krlee/qemu_analysis/qemu/accel/tcg/cpu-exec.c:406
#6  cpu_exec (cpu=cpu@entry=0x5555579cba20) at /home/krlee/qemu_analysis/qemu/accel/tcg/cpu-exec.c:730
#7  0x000055555563f558 in cpu_loop (env=0x5555579d3cf0) at /home/krlee/qemu_analysis/qemu/linux-user/x86_64/../i386/cpu_loop.c:94
#8  0x00005555555c746d in main (argc=&amp;lt;optimized out&amp;gt;, argv=0x7fffffffe338, envp=&amp;lt;optimized out&amp;gt;) at /home/krlee/qemu_analysis/qemu/linux-user/main.c:865&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Since the backtrace tells us all the source code locations, let's just follow it. This is function &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/translate-all.c#L1663&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tb_gen_code&lt;/a&gt;, and it calls &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/translate-all.c#L1583&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tb_link_page&lt;/a&gt;. This is a long function, and it deserves some observing. It does what we think was doing. It allocates a buffer to write a translation block, generates intermediate code, and then finally generates assembly code. Then it saves it in the cache by &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/translate-all.c#L1583&quot;&gt;tb_link_page&lt;/a&gt;. Let's see more in function &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/translate-all.c#L1583&quot;&gt;tb_link_page.&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651673618557&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Called with mmap_lock held for user mode emulation.  */
TranslationBlock *tb_gen_code(CPUState *cpu,
                              target_ulong pc, target_ulong cs_base,
                              uint32_t flags, int cflags)
{
    CPUArchState *env = cpu-&amp;gt;env_ptr;
    TranslationBlock *tb, *existing_tb;
    tb_page_addr_t phys_pc, phys_page2;
    target_ulong virt_page2;
    tcg_insn_unit *gen_code_buf;
    int gen_code_size, search_size, max_insns;
#ifdef CONFIG_PROFILER
    TCGProfile *prof = &amp;amp;tcg_ctx-&amp;gt;prof;
    int64_t ti;
#endif

...

    tb = tcg_tb_alloc(tcg_ctx);

...

    tcg_func_start(tcg_ctx);

    tcg_ctx-&amp;gt;cpu = env_cpu(env);
    gen_intermediate_code(cpu, tb, max_insns);
    tcg_ctx-&amp;gt;cpu = NULL;

...

    gen_code_size = tcg_gen_code(tcg_ctx, tb);
    if (unlikely(gen_code_size &amp;lt; 0)) {
        switch (gen_code_size) {

...

        default:
            g_assert_not_reached();
        }
    }

...

    existing_tb = tb_link_page(tb, phys_pc, phys_page2);

...

    tcg_tb_insert(tb);
    return tb;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;This is function &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/translate-all.c#L1583&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tb_link_page&lt;/a&gt;, calling &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/translate-all.c#L1525&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tb_page_add&lt;/a&gt;. The comment says that it adds the tb to the page.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651673648238&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* add a new TB and link it to the physical page tables. phys_page2 is
 * (-1) to indicate that only one page contains the TB.
 *
 * Called with mmap_lock held for user-mode emulation.
 *
 * Returns a pointer @tb, or a pointer to an existing TB that matches @tb.
 * Note that in !user-mode, another thread might have already added a TB
 * for the same block of guest code that @tb corresponds to. In that case,
 * the caller should discard the original @tb, and use instead the returned TB.
 */
static TranslationBlock *
tb_link_page(TranslationBlock *tb, tb_page_addr_t phys_pc,
             tb_page_addr_t phys_page2)
{

...

    /*
     * Add the TB to the page list, acquiring first the pages's locks.
     * We keep the locks held until after inserting the TB in the hash table,
     * so that if the insertion fails we know for sure that the TBs are still
     * in the page descriptors.
     * Note that inserting into the hash table first isn't an option, since
     * we can only insert TBs that are fully initialized.
     */
    page_lock_pair(&amp;amp;p, phys_pc, &amp;amp;p2, phys_page2, 1);
    tb_page_add(p, tb, 0, phys_pc &amp;amp; TARGET_PAGE_MASK);
    if (p2) {
        tb_page_add(p2, tb, 1, phys_page2);
    } else {
        tb-&amp;gt;page_addr[1] = -1;
    }

...

    return tb;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;This is function &lt;a href=&quot;https://github.com/qemu/qemu/blob/v4.2.0/accel/tcg/translate-all.c#L1525&quot;&gt;tb_page_add&lt;/a&gt;. We can see that it calls mprotect, and clearly can see that it masks out the write permission bit. So we have successfully found where the write permission is discarded, and it is after some translation block is created on the page.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1651674291159&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* add the tb in the target page and protect it if necessary
 *
 * Called with mmap_lock held for user-mode emulation.
 * Called with @p-&amp;gt;lock held in !user-mode.
 */
static inline void tb_page_add(PageDesc *p, TranslationBlock *tb,
                               unsigned int n, tb_page_addr_t page_addr)
{

...

#if defined(CONFIG_USER_ONLY)
    if (p-&amp;gt;flags &amp;amp; PAGE_WRITE) {
        target_ulong addr;
        PageDesc *p2;
        int prot;

        /* force the host page as non writable (writes will have a
           page fault + mprotect overhead) */
        page_addr &amp;amp;= qemu_host_page_mask;
        prot = 0;
        for (addr = page_addr; addr &amp;lt; page_addr + qemu_host_page_size;
            addr += TARGET_PAGE_SIZE) {

            p2 = page_find(addr &amp;gt;&amp;gt; TARGET_PAGE_BITS);
            if (!p2) {
                continue;
            }
            prot |= p2-&amp;gt;flags;
            p2-&amp;gt;flags &amp;amp;= ~PAGE_WRITE;
          }
        mprotect(g2h(page_addr), qemu_host_page_size,
                 (prot &amp;amp; PAGE_BITS) &amp;amp; ~PAGE_WRITE);
        if (DEBUG_TB_INVALIDATE_GATE) {
            printf(&quot;protecting code page: 0x&quot; TB_PAGE_ADDR_FMT &quot;\n&quot;, page_addr);
        }
    }
#else

...

#endif
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. Overall Execution Flow&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;So in summary, when normal code is emulated, as the guest binary is executed, instructions are translated if not cached in advance, which means not executed before, and then the translated instructions are executed. In case of self-modifying code, it needs a mechanism to ensure that the translated block matches with the current instruction on the emulated process memory. QEMU uses SIGSEGV to detect that the instruction written in memory might be changed. When a page includes an instruction that is translated and cached, the page is marked as not writable in host process. However, if the guest tries to overwrite the instructions, SIGSEGV is triggered on the host process and the QEMU process knows that the page is overwritten and that the translated information now might be garbage. At this point, QEMU just invalidates all the translation blocks on that page and marks that page writable, and then just proceeds. This is how self-modifying code is handled specially.&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol class=&quot;footnotes&quot;&gt;
    &lt;li id=&quot;footnote_18_1&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/QEMU#Tiny_Code_Generator&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://en.wikipedia.org/wiki/QEMU#Tiny_Code_Generator&lt;/a&gt; &lt;a href=&quot;#footnote_link_18_1&quot;&gt;[본문으로]&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</description>
      <category>Research</category>
      <category>Qemu</category>
      <category>qemu implementation</category>
      <category>self-modifying code</category>
      <author>orangecalculator</author>
      <guid isPermaLink="true">https://orangecalculator.tistory.com/18</guid>
      <comments>https://orangecalculator.tistory.com/18#entry18comment</comments>
      <pubDate>Tue, 19 Apr 2022 00:22:30 +0900</pubDate>
    </item>
    <item>
      <title>[윈도우] 프로그램이 입력에 즉각 반응하지 않거나 잘못 반응하는 문제</title>
      <link>https://orangecalculator.tistory.com/17</link>
      <description>&lt;p&gt;요즘 시대에는 컴퓨터 기법, 그 중 텍스트와 관련된 기법이 많이 발전해서 텍스트처럼 입력하는 경우에도 특이한 입력이 들어오면 특정 동작을 수행해주는 경우가 있다. 필자는 컴퓨터 전공이라 프로그램을 많이 다루기 때문에 이러한 기능을 자주 애용한다. 이러한 동작의 예시로는 크롬 주소표시줄의 구글 검색 모드 전환 기능, 또는 코딩 텍스트 편집기에서 주석 처리 기능 등이 있을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래 스크린샷은 이러한 기능의 예시를 보여주고 있다. 해당 기능들은 필자가 굉장히 많이 사용하는 기능 두가지를 가져온 것이다. 구글 크롬 주소표시줄에서 ?로 입력을 시작하면 아래와 같이 검색 모드로 자동으로 전환되며, 검색어를 입력하고 엔터를 누르면 해당 검색어로 구글 검색엔진에 검색이 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;20201019222244.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;74&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xRkJG/btqLkKYN25U/OBznLSNRw8qu8EjaGJ0ad1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xRkJG/btqLkKYN25U/OBznLSNRw8qu8EjaGJ0ad1/img.png&quot; data-alt=&quot;구글 크롬 주소표시줄 검색 모드 전환&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xRkJG/btqLkKYN25U/OBznLSNRw8qu8EjaGJ0ad1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxRkJG%2FbtqLkKYN25U%2FOBznLSNRw8qu8EjaGJ0ad1%2Fimg.png&quot; data-filename=&quot;20201019222244.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;74&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구글 크롬 주소표시줄 검색 모드 전환&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;그 다음 스크린샷은 필자가 자주 사용하는 jdoodle.com이라는 온라인으로 즉석으로 코드를 실행해볼 수 있는 서비스이다. 해당 서비스에서는 간단한 텍스트 입력기를 제공한다. 코드 텍스트 편집기는 대부분 주석 처리 기능을 제공하는데, 아래의 경우에도 Ctrl + / 를 입력하면 하이라이트된 줄이 주석 처리된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bunYWa/btqLeqtLTo5/1lCPKbenx5IttUDxSYZeoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bunYWa/btqLeqtLTo5/1lCPKbenx5IttUDxSYZeoK/img.png&quot; data-filename=&quot;20201019222033.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;200&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bunYWa/btqLeqtLTo5/1lCPKbenx5IttUDxSYZeoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbunYWa%2FbtqLeqtLTo5%2F1lCPKbenx5IttUDxSYZeoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHK5Wt/btqLblNdIU4/YkIIuhCh5BaiYmSHorWwek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHK5Wt/btqLblNdIU4/YkIIuhCh5BaiYmSHorWwek/img.png&quot; data-filename=&quot;20201019222051.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;200&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHK5Wt/btqLblNdIU4/YkIIuhCh5BaiYmSHorWwek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHK5Wt%2FbtqLblNdIU4%2FYkIIuhCh5BaiYmSHorWwek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;jdoodle.com 코드 입력기에서 주석 처리 단축키의 올바른 작동&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;필자는 최근 컴퓨터를 사용하면서 아래와 같이 구글 크롬이 즉각적으로 ?(물음표)에 반응하지 않거나, 주석 처리 단축키를 인식하지 않고 /(슬래시)만 인식하여 입력이 되는 문제에 직면하였다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;20201019221442.png&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;75&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6KUyt/btqLf6u3Ar3/7eoMrK2YkO801ArRTWKdC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6KUyt/btqLf6u3Ar3/7eoMrK2YkO801ArRTWKdC0/img.png&quot; data-alt=&quot;구글 크롬 주소표시줄 검색 모드 전환이 이루어지지 않는 문제&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6KUyt/btqLf6u3Ar3/7eoMrK2YkO801ArRTWKdC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6KUyt%2FbtqLf6u3Ar3%2F7eoMrK2YkO801ArRTWKdC0%2Fimg.png&quot; data-filename=&quot;20201019221442.png&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;75&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구글 크롬 주소표시줄 검색 모드 전환이 이루어지지 않는 문제&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;20201019222017.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;200&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhAmRW/btqLmrErSY8/QstQnt4ZWnHqFBRqp8dPSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhAmRW/btqLmrErSY8/QstQnt4ZWnHqFBRqp8dPSK/img.png&quot; data-alt=&quot;jdoodle.com 코드 입력기에서 주석 처리 단축키의 잘못된 작동&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhAmRW/btqLmrErSY8/QstQnt4ZWnHqFBRqp8dPSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhAmRW%2FbtqLmrErSY8%2FQstQnt4ZWnHqFBRqp8dPSK%2Fimg.png&quot; data-filename=&quot;20201019222017.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;200&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;jdoodle.com 코드 입력기에서 주석 처리 단축키의 잘못된 작동&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이와 관련된 부분에 불만이 생겨서 이를 해결하기 위해 구글을 검색해보았다. 그런데 아무리 검색해도 나오지 않는 것이었다. 이러한 현상이 구글 크롬 뿐만 아니라 혹시나 해서 실험해본 윈도우 엣지 브라우저, 그리고 필자가 자주 사용하는 텍스트 편집기인 notepad++에서도 이와 같은 현상이 발생하는 것을 목격할 수 있었다. 따라서 필자는 이 문제가 시스템 전반에 관련된 문제라고 생각하였고, 아마도 운영체제에서 발생하는 문제일 수도 있다는 생각이 들었다. 그래서 윈도우 10과 관련된 논의들을 구글 검색을 통해 뒤져보았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그런데 아무리 검색해도 결과가 나오지 않는 것이었다. 이 때부터 조금 이상했다. 우리나라 뿐만 아니라, 전세계적으로 사용하는 운영체제인 윈도우 10과 관련된 문제인데 이렇게 자료가 없다는 것부터가 굉장히 이상한 부분이었다. 그러다가 윈도우 IME와 관련된 논의를 보게 되었다. 혹시 내 컴퓨터의 IME 문제인가라는 생각과 함께 화면 오른쪽 밑의 작업표시줄 알림영역의 입력기 관련된 메뉴를 여는 순간...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;20201019223530.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;219&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wXBNG/btqLeoW1WHW/9NsP6fTXOQ23zU2IRCry51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wXBNG/btqLeoW1WHW/9NsP6fTXOQ23zU2IRCry51/img.png&quot; data-alt=&quot;문제 상황에서 입력기 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wXBNG/btqLeoW1WHW/9NsP6fTXOQ23zU2IRCry51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwXBNG%2FbtqLeoW1WHW%2F9NsP6fTXOQ23zU2IRCry51%2Fimg.png&quot; data-filename=&quot;20201019223530.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;219&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;문제 상황에서 입력기 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;저번에 한글이 필요해져서 설치했었는데, 어느 순간 보니 한컴 입력기가 기본 입력기를 밀어내고 자리를 차지하고 있었다. 필자는 한컴이 쓴 버그에 호되게 당한 경험이 여러번 있었기 때문에, 바로 기본 입력기로 전환하여 시험해보았다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아니나 다를까. 바로 고쳐졌다. 구글 크롬 주소표시줄 문제, 엣지 브라우저의 같은 현상, jdoodle.com 입력기의 문제, notepad++의 문제까지 전부 고쳐지는 것을 볼 수 있었다. 이번에도 당했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 문제를 해결할 수 있었다는 훈훈한 마무리와 함께 한컴 입력기를 꺼버리는 방법을 소개하고 이 글을 마친다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 아래와 같이 윈도우 설정을 연다. 시작 메뉴에서 &quot;설정&quot;이라고 검색하면 바로 나온다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;20201019223416.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;738&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cABK99/btqLkLQXvlD/ToSDVmF6c3TPeclIwewuX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cABK99/btqLkLQXvlD/ToSDVmF6c3TPeclIwewuX1/img.png&quot; data-alt=&quot;윈도우 10 입력기 설정 변경 (1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cABK99/btqLkLQXvlD/ToSDVmF6c3TPeclIwewuX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcABK99%2FbtqLkLQXvlD%2FToSDVmF6c3TPeclIwewuX1%2Fimg.png&quot; data-filename=&quot;20201019223416.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;738&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;윈도우 10 입력기 설정 변경 (1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 다음에 [시간 및 언어] 탭으로 이동한다. 그 후에는 아래와 같이 왼쪽의 [언어] 탭으로 이동한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;20201019223445.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;738&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MpTKx/btqLkgp6Cxk/fw1p5N2Xsn3gSZ2Y1EonL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MpTKx/btqLkgp6Cxk/fw1p5N2Xsn3gSZ2Y1EonL1/img.png&quot; data-alt=&quot;윈도우 10 입력기 설정 변경 (2)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MpTKx/btqLkgp6Cxk/fw1p5N2Xsn3gSZ2Y1EonL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMpTKx%2FbtqLkgp6Cxk%2Ffw1p5N2Xsn3gSZ2Y1EonL1%2Fimg.png&quot; data-filename=&quot;20201019223445.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;738&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;윈도우 10 입력기 설정 변경 (2)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;스크롤을 조금 내리다보면 기본 설정 언어 메뉴에 한국어 (다른 나라 언어를 사용한다면 그 언어)가 있을 것이다. 해당 언어를 클릭하면 나타나는 [옵션] 버튼을 누른다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;20201019223500.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;738&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kJqxf/btqLhQlay55/o25PpSmMGfVbpjqj5nZbKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kJqxf/btqLhQlay55/o25PpSmMGfVbpjqj5nZbKk/img.png&quot; data-alt=&quot;윈도우 10 입력기 설정 변경 (3)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kJqxf/btqLhQlay55/o25PpSmMGfVbpjqj5nZbKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkJqxf%2FbtqLhQlay55%2Fo25PpSmMGfVbpjqj5nZbKk%2Fimg.png&quot; data-filename=&quot;20201019223500.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;738&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;윈도우 10 입력기 설정 변경 (3)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위와 같이 스크롤을 조금 내리면 키보드 메뉴에 입력기가 여러개 있는 것을 볼 수 있다. 필자는 바로 한컴 입력기를 클릭하고 제거를 눌렀다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;화도 나고 자료도 없어서 직접 이 문제에 대해 써보았다. 좋은 하루 되시길 바랍니다.&lt;/p&gt;</description>
      <category>사소한문제해결</category>
      <category>입력기 문제</category>
      <author>orangecalculator</author>
      <guid isPermaLink="true">https://orangecalculator.tistory.com/17</guid>
      <comments>https://orangecalculator.tistory.com/17#entry17comment</comments>
      <pubDate>Mon, 19 Oct 2020 22:46:27 +0900</pubDate>
    </item>
    <item>
      <title>서버시간 확인 방법과 그 정확도에 관한 고찰</title>
      <link>https://orangecalculator.tistory.com/16</link>
      <description>&lt;p&gt;안녕하세요. orangecalculator입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. 개요&lt;/p&gt;
&lt;p&gt;최근에 서울대학교의 수강신청이 있었습니다. 그런데, 그 전까지는 그러려니 했지만 이번에 수강신청을 하면서 서버시간 확인 서비스인 navyism과 한국표준과학연구원(KRISS)에서 제공하는 표준시간 확인 서비스 utck 중 어떤 서비스가 좋을까라는 생각을 문득 하게 되었습니다. 먼저 제 주변 친구들에게 확인해본 결과 어떤 것이 우세하게 선호되는 것 같지는 않았고, 대부분의 친구들이 두 서비스 중 하나를 선호하는 것 같았습니다. 저는 컴퓨터를 배운 입장에서 두 서비스의 질이 어떤지 궁금해졌습니다. 그래서 한번 코드를 뜯어보았습니다.&lt;/p&gt;
&lt;p&gt;이 글에서는 navyism과 utck를 분석하기 위한 배경지식을 먼저 알아보겠습니다. 그 후에는 코드의 분석결과를 알아보고, 분석결과를 바탕으로 정확도에 대해 고찰해보도록 하겠습니다. 당부드릴 것은 완벽하게 확신하는 내용은 아니고, 추측도 포함되었음을 알려드립니다. 또한, 해당 글의 내용으로 인해 발생하는 수강신청 사고 등에 대해서는 책임져드리지 않음을 알려드립니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2. navyism 분석&lt;/p&gt;
&lt;p&gt;navyism은 인터넷 브라우저를 통해 제공되는 서비스입니다. 정확히는 웹사이트로 구현되어 있습니다. 웹사이트의 일반적인 구조는 일반적으로 서버에서 작동하는 백엔드와 인터넷 브라우저를 통해 실행되고 사용자와 상호작용하는 프론트엔드로 나뉩니다. 이 때, 저는 사용자의 입장이기 때문에 프론트엔드를 통해 해당 사이트의 방법을 추측해야 할 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UXgu7/btqGIJcyINb/4sVzPoWRPXK5ybBk0RgHK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UXgu7/btqGIJcyINb/4sVzPoWRPXK5ybBk0RgHK1/img.png&quot; data-alt=&quot;navyism 서비스의 현재 모습(광고 부분은 제가 넣었습니다)(출처: https://time.navyism.com/?host=sugang.snu.ac.kr)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UXgu7/btqGIJcyINb/4sVzPoWRPXK5ybBk0RgHK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUXgu7%2FbtqGIJcyINb%2F4sVzPoWRPXK5ybBk0RgHK1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;navyism 서비스의 현재 모습(광고 부분은 제가 넣었습니다)(출처: https://time.navyism.com/?host=sugang.snu.ac.kr)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;현재 프론트엔드는 기본적인 표현을 나타내는 HTML과 좀 더 다채롭게 꾸며주는 CSS, 그리고 오늘 논의에서 가장 중요한 javascript로 이루어져 있습니다. 이 때, 위의 스크린샷을 보면, 해당 스크린샷은&lt;/span&gt; 이 글을 쓰는 시간에 서비스의 현재 모습을 캡쳐한 것입니다. 위처럼 시간을 알려주고 있습니다. 물론 1초 간격으로 시간을 최신화시켜 보여줍니다. 어것이 핵심입니다. 사이트의 내용이 지속적으로 변합니다. 여기서 동적인 웹사이트를 구현하는 javascript를 사용하였을 것이라 추측할 수 있습니다. 확인 결과 해당 시간을 표시하는 동작이 스크립트를 통해 구현되어 있었습니다. 바로 한번 확인해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;해당 부분은 show라는 함수를 통해 구현되어 있었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;함수의 파라미터는 timeGap이라는 파라미터로, 특별한 값이 문자열 같은 걸로 전달되는 경우도 있어 보이지만 대부분은 정수로 전달되는 것으로 보입니다. 전달된 값은 이름 그대로 서버 시간과 현재 해당 코드를 실행 중인 컴퓨터 시간 사이의 시간의 차이를 초단위로 나타낸 것으로 보입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1597793117166&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function show(timeGap)
{
	//...
    
	var thisTime	= timeGap + time();
    
	//...
    
	if(lang == &quot;en&quot;)
	{
	//...
	}
	else
	{
		if((t[4] == '29' || t[4] == '59') &amp;amp;&amp;amp; !document.getElementById(&quot;nowarn_check&quot;).checked)
		{
			document.getElementById('time_area').innerHTML	= &quot;&amp;lt;font color=#cc0000&amp;gt;&quot; + t[0] + &quot;년 &quot; + t[1] + &quot;월 &quot; + t[2] + &quot;일&quot; + time_sep + t[3] + &quot;시 &quot; + t[4] + &quot;분 &quot; + t[5] + &quot;초&amp;lt;/font&amp;gt;&quot;;
		}
		else
		{
			document.getElementById('time_area').innerHTML	= t[0] + &quot;년 &quot; + t[1] + &quot;월 &quot; + t[2] + &quot;일&quot; + time_sep + t[3] + &quot;시 &quot; + t[4] + &quot;분 &quot; + t[5] + &quot;초&quot;;
		}
	}
    
	if(isFirstKick &amp;lt; 10)
	{
		isFirstKick++;

		var now		= new Date();

		setTimeout(&quot;show(&quot; + timeGap + &quot;)&quot;, 100 - now.getMilliseconds());
	}
	else
	{
		setTimeout(&quot;show(&quot; + timeGap + &quot;)&quot;, 100);
	}

	//...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시간 차이인 timeGap의 값을 time 함수의 결과에 더해서 서버시간을 계산하고 이 값을 thisTime이라는 변수에 저장합니다. 이 시간을 웹페이지에 표시해주면 되겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1597794122191&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	var thisTime	= timeGap + time();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이를 표시하기 위해 아주 긴 if-else 구문이 시작됩니다. 대부분의 시간은 맨 끝의 else문이 실행되는 것으로 보입니다. 대부분의 시간은 그냥 몇년 몇월 몇일 몇시 몇분 몇초까지만 보여주는데, 이 형태는 아래의 else문에서 사용하는 형태입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597794162313&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	if(lang == &quot;en&quot;)
	{
	//...
	}
	else
	{
		if((t[4] == '29' || t[4] == '59') &amp;amp;&amp;amp; !document.getElementById(&quot;nowarn_check&quot;).checked)
		{
			document.getElementById('time_area').innerHTML	= &quot;&amp;lt;font color=#cc0000&amp;gt;&quot; + t[0] + &quot;년 &quot; + t[1] + &quot;월 &quot; + t[2] + &quot;일&quot; + time_sep + t[3] + &quot;시 &quot; + t[4] + &quot;분 &quot; + t[5] + &quot;초&amp;lt;/font&amp;gt;&quot;;
		}
		else
		{
			document.getElementById('time_area').innerHTML	= t[0] + &quot;년 &quot; + t[1] + &quot;월 &quot; + t[2] + &quot;일&quot; + time_sep + t[3] + &quot;시 &quot; + t[4] + &quot;분 &quot; + t[5] + &quot;초&quot;;
		}
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래와 같이 확인해보면 정말로 time_area라는 id를 가지는 태그의 값이 업데이트되고 있는 것을 html 문서에서 확인할 수 있습니다. 이미지에서와 같이 크롬 브라우저에서 해당 스크립트가 작동되는 모습을 스크린샷으로 찍어보았습니다. 코드에서 확인한 것과 같이 time_area라는 id를 가진 div 태그가 보라색으로 빛나면서 시간이 최신화되고 있는 모습입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597793659385&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 해당 태그가 업데이트의 대상이 되는 시간 표시 태그 --&amp;gt;
&amp;lt;div id=&quot;time_area&quot;&amp;gt;2020년 08월 19일 08시 34분 10초&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4pB80/btqGRQW3b8O/FOMCpQKCqktzZC3cVVPTb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4pB80/btqGRQW3b8O/FOMCpQKCqktzZC3cVVPTb1/img.png&quot; data-alt=&quot;크롬브라우저 시간 표시 태그 업데이트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4pB80/btqGRQW3b8O/FOMCpQKCqktzZC3cVVPTb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4pB80%2FbtqGRQW3b8O%2FFOMCpQKCqktzZC3cVVPTb1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;크롬브라우저 시간 표시 태그 업데이트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;시간을 지속적으로 최신화시켜주어야 하기 때문에, 지속적으로 함수가 실행되도록 해주어야 하겠습니다. 여러가지 구현이 있을 수 있겠습니다. while문으로 계속 루프를 돌 수도 있겠습니다. 성능을 위해 중간에 sleep을 걸어줄 수도 있겠습니다. 다만 이 코드를 개발하신 분은 이러한 방법이 아니라, 0.1초 정도 뒤에 다시 해당 함수를 실행하도록 하는 방법을 사용하신 것 같습니다. 이를 위해 javascript에서 제공하는 함수인 setTimeout을 이용해 같은 파라미터인 timeGap을 이용해 함수를 실행하도록 하셨습니다. 이를 위한 코드가 아래와 같이 나타나있는 것으로 보입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1597794056639&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	if(isFirstKick &amp;lt; 10)
	{
		isFirstKick++;

		var now		= new Date();

		setTimeout(&quot;show(&quot; + timeGap + &quot;)&quot;, 100 - now.getMilliseconds());
	}
	else
	{
		setTimeout(&quot;show(&quot; + timeGap + &quot;)&quot;, 100);
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 이 show 함수를 적절한 파라미터와 함께 한번만 실행시켜주면 알아서 지속적으로 실행이 됩니다. 이 시작 부분은 어떻게 되어있을까요? 아래와 같이 html의 스크립트에 따로 적혀있었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1597794243209&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
	show(1597793026 - time() - 1);
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같이 html 문서 자체에 서버 시간을 먼저 불러와서 적어놓고 해당 문서를 사용자에게 주는 것으로 보입니다. 사실 해당 스크립트 태그를 다른 스크립트 태그에서 따로 삽입한 것이 아닐까도 생각해보았습니다. 그런데 이는 아닌 것으로 보였습니다. 왜냐하면 받아오기만 하고 해당 html 문서를 실행하지 않더라도 위와 같은 부분은 그대로 남아있었기 때문입니다. 아래와 같이 받아오면 되겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1597794615888&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/usr/bin/env python3
#FetchNavyismSNU.py

import urllib.request

navyismurl = urllib.request.urlopen(&quot;https://time.navyism.com/?host=sugang.snu.ac.kr&quot;)

navyismhtml = navyismurl.read()

navyismurl.close()

navyismfile = open(&quot;navyismsnu.html&quot;, &quot;wb&quot;)
navyismfile.write(navyismhtml)
navyismfile.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 하나 더 덧붙이자면 위의 time 함수는 시간을 반환하는 함수입니다. 해당 함수는 &lt;span style=&quot;color: #333333;&quot;&gt;컴퓨터 로컬 시간을 초단위로 받아오는 함수입니다. &lt;/span&gt;그런데 이 함수는 사실 내장함수는 아닙니다. 찾아보니 이 웹사이트를 개발하신 분이 php에 익숙하셨던 것인지, 아니면 php로 개발된 것을 번역하신 것인지는 잘 모르겠습니다만, 이 time 함수는 php에서 제공하는 내장함수 원형을 그대로 사용하는 것으로 보입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;3. utck 분석&lt;/p&gt;
&lt;p&gt;utck는 한국표준과학연구원(KRISS)에서 제공하는 프로그램으로 컴퓨터 시간을 KRISS 서버 시간과 비교하고1 동기화시키는 기능을 제공합니다. 아래 스크린샷은 utck 프로그램의 모습입니다. 비교 버튼은 컴퓨터 시간과 서버 시간을 비교하는 기능을 제공하고 동기 버튼은 여기서 더 나아가 컴퓨터 시간과 서버 시간을 동기화시키는 기능을 제공합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bohwsG/btqGX3H68Ns/bshpqQSK2S497kKQLatDj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bohwsG/btqGX3H68Ns/bshpqQSK2S497kKQLatDj0/img.png&quot; data-alt=&quot;UTCK 프로그램의 스크린샷&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bohwsG/btqGX3H68Ns/bshpqQSK2S497kKQLatDj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbohwsG%2FbtqGX3H68Ns%2FbshpqQSK2S497kKQLatDj0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;UTCK 프로그램의 스크린샷&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;해당 프로그램이 얼마나 정확한지 알아보기 위해 해당 프로그램의 내부를 분석하여 어떤 식으로 작동하는지 알아보았습니다. 먼저 해당 프로그램은 실행시키면 되는 프로그램이라서 컴파일 언어로 작성되었다고 볼 수 있겠습니다. 그래서 기계어로 이루어져있고 이를 분석하기 위해 기계어로 구성된 내부를 들여다보아야 합니다. 사실 저는 분석할 때 IDA Freeware를 사용합니다. 돈 없는 학생이라 기계어를 자동으로 번역까지 해서 C로 보여주는 기능이 있긴 하지만 쓸 수가 없습니다. 그래서 기계어로 설명해드리겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 프로그램도 어쨌든 서버와 통신을 해서 정보를 받아와야 하므로, 운영체제의 네트워크 모듈을 사용할 것입니다. 역시나 utck 주요기능 페이지에는 이와 같은 내용이 설명되어 있었습니다. KRISS의 설명에 따르면 SNTP 프로토콜을 이용해 시간 정보를 받아온다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8wJXg/btqGJuOEvGE/q7U7BhkQhAU9t5TGyRsUU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8wJXg/btqGJuOEvGE/q7U7BhkQhAU9t5TGyRsUU1/img.png&quot; data-alt=&quot;utck 주요기능 설명 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8wJXg/btqGJuOEvGE/q7U7BhkQhAU9t5TGyRsUU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8wJXg%2FbtqGJuOEvGE%2Fq7U7BhkQhAU9t5TGyRsUU1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;utck 주요기능 설명 페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 부분을 확인해보기 위해 Process Monitor라는 프로그램을 사용하였습니다. 해당 프로그램은 윈도우 제작사인 마이크로소프트에서도 문서를 제공하는 프로그램입니다. utck라는 이름으로 시작하는 프로세스를 검색해서 시스템콜들을 확인해보았습니다. 그 결과 아래와 같이 비교 버튼을 누르면 아래와 같은 로그가 잡히는 것을 볼 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rRHSa/btqGWS04Wno/4tNVLd9GmKqiworYQEbGP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rRHSa/btqGWS04Wno/4tNVLd9GmKqiworYQEbGP0/img.png&quot; data-alt=&quot;Process Monitor로 utck의 동작을 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rRHSa/btqGWS04Wno/4tNVLd9GmKqiworYQEbGP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrRHSa%2FbtqGWS04Wno%2F4tNVLd9GmKqiworYQEbGP0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Process Monitor로 utck의 동작을 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;그 내용을 좀 더 자세히 알아보기 위해 Microsoft Network Monitor를 사용하여 패킷의 내용을 확인해보았습니다. 먼저 필터를 UDP 포트를 이용해 설정한 후 패킷들을 잡아서 내용을 보았습니다. 아래와 같이 패킷은 2개를 보냈고, 2번째 패킷에 대해서는 응답이 오는 것을 확인할 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKqqU8/btqGYK2yPWQ/8GUa2t3FV7H6giuZIvEUK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKqqU8/btqGYK2yPWQ/8GUa2t3FV7H6giuZIvEUK0/img.png&quot; data-alt=&quot;Microsoft Network Monitor를 이용해 utck의 패킷 내용 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKqqU8/btqGYK2yPWQ/8GUa2t3FV7H6giuZIvEUK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKqqU8%2FbtqGYK2yPWQ%2F8GUa2t3FV7H6giuZIvEUK0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Microsoft Network Monitor를 이용해 utck의 패킷 내용 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;지금까지 종합해보면 SNTP 프로토콜이라는 것을 이용하여 시간을 받아온다는 것입니다. 사실 이정도만으로도 충분하지만, 저는 프로그램 내부까지 뜯어보았기 때문에 이를 소개하겠습니다. 다만, 기계어는 최소한으로만 보여드리겠습니다. 또, 짧은 wrapper 함수들은 C 언어로 번역해놓았으니 이를 보여드리겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 사용된 시스템콜을 보여드리겠습니다. 제가 관심을 가지는 사용된 시스템콜은 네트워킹과 관련된 시스템콜밖에 없었고, 사실 이와 관련된 시스템콜들은 쓰임새가 정형화되어 있습니다. 프로토타입만 보여드리자면 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1597822657634&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//reference: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket
//winsock2.h
SOCKET WSAAPI socket(
  int af,
  int type,
  int protocol
);

//reference: https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-gethostbyname
//winsock.h
hostent * gethostbyname(
  const char *name
);

//reference: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send
//winsock2.h
int WSAAPI send(
  SOCKET     s,
  const char *buf,
  int        len,
  int        flags
);

//reference: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-select
//winsock2.h
int WSAAPI select(
  int           nfds,
  fd_set        *readfds,
  fd_set        *writefds,
  fd_set        *exceptfds,
  const timeval *timeout
);

//reference: https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recv
//winsock2.h
int recv(
  SOCKET s,
  char   *buf,
  int    len,
  int    flags
);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 함수만이 아니고 여러 다른 함수들도 다 써야 완전한 통신이 되겠지만, 분석에 필요한 것은 이 정도입니다. 정리하면 소켓을 열어서 시간을 달라고 패킷을 보내고, 응답을 받는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 함수들에 대해서 모두 wrapper를 사용하는 것으로 확인되었습니다. 이 wrapper들의 동작을 바탕으로 분석을 해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 socket함수를 이용해 udp 소켓을 여는 것으로 확인되었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1597823756086&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//at 0x401C00
int opensocket(SOCKET *PSOCKET){
    *PSOCKET = socket(AF_INET, SOCK_DGRAM, NULL);
    return (*PSOCKET == -1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 다음 해당 소켓에 대한 설정을 해주기 위해 다음과 같이 해당 소켓을 설정해주는 wrapper를 사용합니다. 해당 wrapper에서는 gethostbyname이라는 함수를 사용합니다. 해당 함수는 도메인 네임을 DNS 서버를 통해 ip로 바꾸어주는 역할을 합니다. 그 이후에는 ip를 얻어내어 소켓의 통신 주소를 설정하는데 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1597823786146&quot; class=&quot;c++ arduino&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//at 0x401C20
int socketconfig(char *cp, u_short hostshort)

//...

at 0x401C62
push    esi             ; name
call    gethostbyname
test    eax, eax
jz      short loc_401C8D&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 이후에는 sntp 프로토콜에 맞는 패킷을 만들고 보내서 정보를 요청합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1597824283566&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//at 0x401CC0
int requestsntp(SOCKET *PSOCKET, const char *buf, int len){
    return (send(*PSOCKET, buf, len, NULL) != -1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;요청을 한 후에는 해당 서버에서 응답을 보내왔는지 확인을 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1597822935316&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//at 0x401D30
int isalive(SOCKET *PSOCKET, int *Pvalid, long msec){
    int res;
    
    fd_set readfds;
    readfds.fd_count = 1;
    readfds.fd_array[0] = *PSOCKET;
    
    timeval timeout;
    MILLISECONDTOTIMEVAL(msec, &amp;amp;timeout);
    //macro behavior description at reference
    //reference: https://stackoverflow.com/questions/1294885/convert-milliseconds-to-seconds-in-c
    
    res = select(NULL, &amp;amp;readfds, NULL, NULL, &amp;amp;timeout);
    if(res == -1) return 0;
    else{
        *Pvalid = (res != 0);
        return 1;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;서버에서 성공적으로 응답하였다면 아래와 같은 함수로 해당 데이터를 받아옵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1597824313915&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//at 0x401CF0
int recvsntp(SOCKET *PSOCKET, char *buf, int len){
    return recv(*PSOCKET, buf, len, NULL);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이와 같은 wrapper를 이용하여 서버의 도메인 이름을 주고 시간을 받아오는 큰 함수가 있는 것으로 확인되었습니다. 제가 살펴본 곳만 써드리겠습니다. 그런데 다 이해할 필요는 없고 이런 과정을 통해서 결국 서버시간을 받아오더랍니다. 물론 실패하면 에러를 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1597824650422&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//at 0x401DF0
int getservertime(char *cp, int, u_short hostshort)

//at 0x401E16
loc_401E16:
mov     ecx, ebx
call    sub_401C00
test    eax, eax
jz      loc_40201E

//at 0x401E25
mov     eax, dword ptr [esp+94h+hostshort]
mov     ecx, [esp+94h+cp]
push    eax             ; hostshort
push    ecx             ; cp
mov     ecx, ebx
call    sub_401C20
test    eax, eax
jz      loc_401EF3

//at 0x401E77
lea     eax, [esp+94h+buf]
mov     [esp+94h+var_4C], ecx
push    30h             ; len
push    eax             ; buf
mov     ecx, ebx
mov     [esp+9Ch+var_48], edx
call    sub_401CC0
test    eax, eax
jz      short loc_401EF3

//at 0x401E91
mov     ecx, [esi+4]
lea     edx, [esp+94h+var_80]
push    ecx
push    edx
mov     ecx, ebx
call    sub_401D30
test    eax, eax
jz      loc_401FFF

//at 0x401ED9
xor     eax, eax
lea     edi, [esp+94h+var_44]
push    44h             ; len
rep stosd
lea     ecx, [esp+98h+var_44]
push    ecx             ; buf
mov     ecx, ebx
call    sub_401CF0
test    eax, eax
jnz     short loc_401F24&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다면 이 함수를 부르는 부분을 찾으면 될 것입니다. 이 부분에서 드디어 제가 원하는 정보를 찾을 수 있었습니다. 잭팟입니다. 여기서는 저희가 원하는 값이 코드에 들어있는 것이 아니고 함수의 호출 과정을 따라서 쭉 인자가 전달되는 것으로 보였습니다. 그래서 코드 안에서 원하는 정보를 얻지는 못하고 x32dbp라는 프로그램을 이용해 아래 부분의 함수 호출부에 break를 걸고 확인해보았습니다. 그랬더니 함수 인자로 time.kriss.re.kr과 time2.kriss.re.kr를 각각 주는 것을 확인하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597825510229&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//at 0x408A40
//function start

//at 0x408AC1
mov     eax, [edi+28h]
lea     ecx, [esp+174h+var_138]
push    7Bh             ; hostshort
push    ecx             ; int
push    eax             ; cp
lea     ecx, [esp+180h+var_D8]
mov     byte ptr [esp+180h+var_4], 1
call    sub_401DF0
test    eax, eax
jz      short loc_408AED

//at 0x408AED
loc_408AED:
mov     eax, [edi+2Ch]
lea     edx, [esp+174h+var_138]
push    7Bh             ; hostshort
push    edx             ; int
push    eax             ; cp
lea     ecx, [esp+180h+var_D8]
call    sub_401DF0
test    eax, eax
jz      short loc_408B11

//...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b46hrO/btqGX6kyBxM/LfW5VN3mvaXkMYVWWr6XW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b46hrO/btqGX6kyBxM/LfW5VN3mvaXkMYVWWr6XW1/img.png&quot; data-alt=&quot;x32dbg로 utck의 내부 확인 1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b46hrO/btqGX6kyBxM/LfW5VN3mvaXkMYVWWr6XW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb46hrO%2FbtqGX6kyBxM%2FLfW5VN3mvaXkMYVWWr6XW1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;x32dbg로 utck의 내부 확인 1&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tLInN/btqGU1xkXZh/E0x06C0OcXRnHJQr5LKI80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tLInN/btqGU1xkXZh/E0x06C0OcXRnHJQr5LKI80/img.png&quot; data-alt=&quot;x32dbg로 utck의 내부 확인 1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tLInN/btqGU1xkXZh/E0x06C0OcXRnHJQr5LKI80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtLInN%2FbtqGU1xkXZh%2FE0x06C0OcXRnHJQr5LKI80%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;x32dbg로 utck의 내부 확인 1&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;결국 종합하면 utck는 time.kriss.re.kr 서버에 시간정보를 요청하고 이 요청이 실패할 경우에는 time2.kriss.re.kr 서버에 시간정보를 요청합니다. 나름 긴 과정이었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;4. 고찰&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;navyism의 경우 애초에 초단위로 정보를 주기 때문에 오차는 최소단위의 절반인 0.5초까지로 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;utck의 경우는 좀 더 생각해볼 것이 있습니다. 사실 KRISS에서는 오차가 0.5초까지라고 제시하였습니다. 그런데 제가 생각하기로는 이 값은 굉장히 보수적인 값입니다. 애초에 이 프로토콜은 밀리초단위로 정보를 주기 때문입니다. 이를 관찰해보기 위해 sntp의 간단한 동작을 아래와 같이 파이썬을 통해 확인해보았습니다. 밀리초까지 알려주는 것을 확인할 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597826153720&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import socket
import struct
import sys
import time
import datetime

TIME1970 = 2208988800

def try_sntp_client(NTP_SERVER, timeout=None):
    client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    client.sendto(b'\x1b' + b'\x00' * 47, (NTP_SERVER, 123))
    time_send = time.time()
    if timeout != None:
        client.settimeout(timeout)
    data, address = client.recvfrom(1024)
    time_recv = time.time()
    if len(data) &amp;gt; 0:
        print(f&quot;Response received from: {address}&quot;)
    timedat = struct.unpack(&quot;!2I&quot;, data[40:48])
    timestamp = timedat[0] - TIME1970 + timedat[1] / (2 ** 32)
    print(f&quot;\tServer Time: {datetime.datetime.fromtimestamp(timestamp)}&quot;)
    print(f&quot;\tLocal  Time: {datetime.datetime.fromtimestamp((time_send + time_recv) / 2)}&quot;)
    print(f&quot;\tSend-Recv Gap: {time_recv - time_send}&quot;)

if __name__ == '__main__':
    try:
        received = False
        try_sntp_client(&quot;time.kriss.re.kr&quot;, 1)
        received = True
    except Exception as e:
        print(str(e))
    if not received:
        try:
            try_sntp_client(&quot;time2.kriss.re.kr&quot;, 1)
        except Exception as e:
            print(str(e))

#reference: https://stackoverflow.com/questions/39466780/simple-sntp-python-script&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;결과는 어떨까요? 밀리초 단위의 정보도 확인할 수 있었습니다. 또 여기서 하나 더 확인할 수 있도록 제가 출력한 것이 있습니다. 바로 요청을 보내고 응답을 받을 때까지의 시간입니다. 아래와 같이 6(ms)가 걸린 것을 확인할 수 있었습니다. 여기서부터는 제 추측이지만, 서버에서는 요청이 도착하면 그 때 자신의 시간을 얻어서 패킷에 실어 보낼 것입니다. 따라서 요청을 보낸 후부터 6(ms) 후에 응답을 받는 사이의 서버 시간을 받게 될 것입니다. 즉, 보수적으로 오차를 잡아도 이 경우에는 6(ms)가 최대 오차라는 것이 제 생각입니다. 게다가, 요청을 보내고 응답을 받는 데 패킷의 전달 시간이 비슷할 것이라고 생각하기 때문에, 위의 코드에서도 그랬지만, 두 시간 사이의 시간을 Local Time으로 출력하였습니다. 그렇게 하면 최대 오차가 절반인 3(ms)까지 줄어들 수 있음을 의미합니다. 즉, 굉장히 정확하다고 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597826179231&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;timed out
Response received from: ('210.98.16.101', 123)
        Server Time: 2020-08-19 17:35:16.636651
        Local  Time: 2020-08-19 17:35:17.523097
        Send-Recv Gap: 0.005927085876464844&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;5. 결론&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;navyism측 서버에서 1초 단위로 정보를 보내주므로 설계의 한계로 인해 최대 오차를 0.5초까지 생각해야 할 것입니다. 반면 utck는 자신의 인터넷 환경에 따라 다르겠지만 수백 ms에서 수 ms까지 오차가 줄어들 수 있다고 생각합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;제가 이러한 분석을 시도해본건 수강신청에 어떤 것이 좋은지 궁금해서였습니다. 그런데 몇일간 생각을 해보니 사람의 반응속도가 있는데 이 정도 정확도에서 이러한 분석이 의미가 있을까라는 생각도 들지만, 어쩌면 차이가 날 수도 있을 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;감사합니다.&lt;/p&gt;</description>
      <category>Research</category>
      <category>navyism</category>
      <category>UTck</category>
      <author>orangecalculator</author>
      <guid isPermaLink="true">https://orangecalculator.tistory.com/16</guid>
      <comments>https://orangecalculator.tistory.com/16#entry16comment</comments>
      <pubDate>Sun, 16 Aug 2020 22:27:01 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ] 브루트 포스</title>
      <link>https://orangecalculator.tistory.com/15</link>
      <description>&lt;p&gt;오늘부터는 (시시한 거 그만하고) 슬슬 알고리즘을 해보도록 하겠습니다. 오늘의 주제는 브루트 포스입니다. 브루트 포스는 그냥 무식하게 하나하나 다 해보는 방법입니다. 한마디로 난 그런 거 잘 모르겠고 일단 부딪혀.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 2798]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;3개의 카드를 고르는 경우를 모두 해보는 방법입니다. 3개의 카드를 고르기 때문에 3중 for문입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1596066902783&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int nearestsum(const vector&amp;lt;int&amp;gt; &amp;amp; cards, int limit){
    int maxsum = 0;
    for(int i=0;i&amp;lt;cards.size();++i)
    for(int j=i+1;j&amp;lt;cards.size();++j)
    for(int k=j+1;k&amp;lt;cards.size();++k){
        int newsum = cards[i] + cards[j] + cards[k];
        if(maxsum &amp;lt; newsum &amp;amp;&amp;amp; newsum &amp;lt;= limit)
            maxsum = newsum;
    }
    return maxsum;
}

int main(){
    int N, M;
    scanf(&quot;%d%d&quot;,&amp;amp;N,&amp;amp;M);
    vector&amp;lt;int&amp;gt; cards(N);
    for(int k=0;k&amp;lt;N;++k)
        scanf(&quot;%d&quot;,&amp;amp;cards[k]);
    printf(&quot;%d&quot;,nearestsum(cards,M));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 2231]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;브루트 포스에 충실하게 숫자의 분해합을 구하는 함수를 짜고 하나씩 해보면서 값을 찾습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1596067422343&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

int dividesum(int N){
    int divsum = N;
    while(N != 0){
        divsum += (N % 10);
        N /= 10;
    }
    return divsum;
}

int main(){
    int N;
    scanf(&quot;%d&quot;,&amp;amp;N);
    int m = 1;
    while(dividesum(m) != N &amp;amp;&amp;amp; m&amp;lt;=N) m++;
    printf(&quot;%d&quot;, m&amp;lt;=N ? m : 0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 7568]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;처음에는 이게 뭔가 싶었습니다. 결국에는 이 문장에 충실하게 해 주면 되겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;N명의 집단에서 각 사람의 덩치 등수는 자신보다 더 &quot;큰 덩치&quot;의 사람의 수로 정해진다. -본문 발췌-&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;한 명씩 덩치가 큰 사람을 세어주면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1596067450603&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

typedef pair&amp;lt;int, int&amp;gt; physical_t;

bool comparephysical(const physical_t &amp;amp; a, const physical_t &amp;amp; b){
    return ((a.first &amp;gt; b.first) &amp;amp;&amp;amp; (a.second &amp;gt; b.second));
}

vector&amp;lt;int&amp;gt; physicalrank(const vector&amp;lt;physical_t&amp;gt; &amp;amp; P){
    vector&amp;lt;int&amp;gt; rank(P.size());
    for(int k=0;k&amp;lt;P.size();++k){
        int countsuperior = 0;
        const physical_t assess = P[k];
        for(const physical_t buddy : P){
            if(comparephysical(buddy, assess))
                countsuperior++;
        }
        rank[k] = countsuperior + 1;
    }
    return rank;
}

int main(){
    int N;
    scanf(&quot;%d&quot;,&amp;amp;N);
    vector&amp;lt;physical_t&amp;gt; P(N);
    for(int k=0;k&amp;lt;N;++k)
        scanf(&quot;%d%d&quot;, &amp;amp;P[k].first, &amp;amp;P[k].second);
    vector&amp;lt;int&amp;gt; rank = physicalrank(P);
    for(int k=0;k&amp;lt;N;++k) printf(&quot;%d &quot;,rank[k]);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 1018]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(0,0)에서 8x8을 잘랐을 때, (0,1)에서 8x8을 잘랐을 때,...&lt;/p&gt;
&lt;p&gt;이렇게 자른 다음에 한 번씩 필요한 색칠의 개수를 세어주면 되겠습니다. 8x8을 복사하지 않고 배열 상에서 값을 얻어오도록 하게 위해 getval이라는 wrapper를 사용하였습니다. 그리고 한 가지 더 있는데요, 왼쪽 위가 검은색인 경우랑 흰색인 경우에 모든 칸의 색깔이 반대이기 때문에 각 경우에 색칠해야 하는 블록의 개수를 세면 합이 반드시 8x8의 총 칸의 개수인 64개입니다. 이를 이용해 주었습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596067678025&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

typedef enum boardcolor_t{
    WHITE,
    BLACK,
    ERROR,
} boardcolor_t;

constexpr int BOARDSIZE = 8;
constexpr int BOARDBLOCKCOUNT = BOARDSIZE * BOARDSIZE;

typedef vector&amp;lt;vector&amp;lt;boardcolor_t&amp;gt;&amp;gt; chessboard_t;

class chessboardcut{
    public:
    const chessboard_t * Pboard;
    int offset_i, offset_j;
    int setoffset(int i, int j){
        offset_i = i;
        offset_j = j;
        return 0;
    }
    boardcolor_t getval(int i, int j){
        return (*Pboard)[offset_i+i][offset_j+j];
    }
    private:
    int colormodify(){
        int count = 0;
        for(int i=0;i&amp;lt;BOARDSIZE;++i)
        for(int j=0;j&amp;lt;BOARDSIZE;++j){
            // assume we will color upper left black
            boardcolor_t targetcolor = ((i+j)%2 == 0 ? BLACK : WHITE);
            if(targetcolor != getval(i, j))
                count++;
        }
        return (count &amp;lt;= (BOARDBLOCKCOUNT / 2) ? count : BOARDBLOCKCOUNT - count);
    }
    public:
    int mincolormodify(const chessboard_t &amp;amp; board){
        Pboard = &amp;amp;board;
        int sz_vert = board.size();
        int sz_hori = board[0].size();
        int minmodcount = BOARDBLOCKCOUNT;
        
        for(int i=0;i&amp;lt;sz_vert - BOARDSIZE + 1;++i)
        for(int j=0;j&amp;lt;sz_hori - BOARDSIZE + 1;++j){
            setoffset(i, j);
            int newmodcount = colormodify();
            if(minmodcount &amp;gt; newmodcount)
                minmodcount = newmodcount;
        }
        return minmodcount;
    }
};

int main(){
    int N, M;
    scanf(&quot;%d%d &quot;,&amp;amp;N,&amp;amp;M);
    chessboard_t board;
    board.resize(N);
    for(int i=0;i&amp;lt;N;++i){
        board[i].resize(M);
        for(int j=0;j&amp;lt;M;++j){
            switch(getchar()){
                case 'W': board[i][j] = WHITE; break;
                case 'B': board[i][j] = BLACK; break;
                default: board[i][j] = ERROR; break;
            }
        }
        getchar(); // flush '\n'
    }
    chessboardcut C;
    printf(&quot;%d&quot;, C.mincolormodify(board));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 1436]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;당연하게도 더 쉽고 빠른 방법이 있겠지만 브루트 포스에 충실하게 해 주었습니다. 1부터 하나씩 올리면서 확인해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596067733406&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

bool has666(int N){
    int countsix = 0;
    do {
        if(N % 10 == 6)
            countsix++;
        else
            countsix = 0;
        N /= 10;
        if(countsix &amp;gt;= 3)
            return true;
    } while(N != 0);
    return false;
}

int main(){
    int N;
    scanf(&quot;%d&quot;,&amp;amp;N);
    int nth_number;
    // stop at 10000th number
    for(int x=666, count=0;x&amp;lt;=2666799;x++){
        if(has666(x))
            count++;
        if(count == N){
            nth_number = x;
            break;
        }
    }
    printf(&quot;%d&quot;, nth_number);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;감사합니다.^^&lt;/p&gt;</description>
      <category>BOJ</category>
      <category>BOJ</category>
      <author>orangecalculator</author>
      <guid isPermaLink="true">https://orangecalculator.tistory.com/15</guid>
      <comments>https://orangecalculator.tistory.com/15#entry15comment</comments>
      <pubDate>Thu, 30 Jul 2020 09:10:06 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ] 재귀</title>
      <link>https://orangecalculator.tistory.com/13</link>
      <description>&lt;p&gt;바로 시작해보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 10872]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;switch-case문으로 구현해주었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1595682534705&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

unsigned int fact(unsigned int N){
    switch(N){
        case 0:
        case 1:
            return 1;
        default:
            return fact(N-1) * N;
    }
}

int main(){
    int N;
    scanf(&quot;%d&quot;,&amp;amp;N);
    printf(&quot;%d&quot;, fact(N));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 10870]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;switch-case문으로 구현해주었습니다. 2&lt;/p&gt;
&lt;pre id=&quot;code_1595682575822&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

unsigned fibb(unsigned N){
    switch(N){
        case 0:
            return 0;
        case 1:
        case 2:
            return 1;
        default:
            return fibb(N-1) + fibb(N-2);
    }
}

int main(){
    int N;
    scanf(&quot;%d&quot;,&amp;amp;N);
    printf(&quot;%d&quot;,fibb(N));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 2447]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문자열 버퍼를 만들어놓고 재귀적으로 저장하는 방식으로 구현해주었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1595682625298&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;

using namespace std;

class star10{
    public:
    vector&amp;lt;string&amp;gt; starbuf;
    star10(int N) : starbuf(N, string(N, ' ')){
        drawstar(N, 0, 0);
    }
    void printstar(){
        for(int k=0;k&amp;lt;starbuf.size();++k)
            printf(&quot;%s\n&quot;,starbuf[k].c_str());
    }
    private:
    int drawstar(int N, int x, int y){
        if(N == 1){
            starbuf[y][x] = '*';
        } else{
            N /= 3;
            drawstar(N, x+0,    y+0);
            drawstar(N, x+N,    y+0);
            drawstar(N, x+2*N,  y+0);
            drawstar(N, x+0,    y+N);
            drawstar(N, x+2*N,  y+N);
            drawstar(N, x+0,    y+2*N);
            drawstar(N, x+N,    y+2*N);
            drawstar(N, x+2*N,  y+2*N);
        }
        
        return 0;
    }
};

int main(){
    int N;
    scanf(&quot;%d&quot;,&amp;amp;N);
    star10 P(N);
    P.printstar();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 11729]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;재귀적으로 구현해주되 출발과 도착이 있고 남은 봉 하나도 함수 인자로 주어서 사용할 수 있게끔 하면 되겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1595682703663&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

class hanoitower{
    public:
    int N;
    int K;
    typedef pair&amp;lt;int, int&amp;gt; move_t;
    vector&amp;lt;move_t&amp;gt; proc;
    hanoitower(int N){
        this-&amp;gt;N = N;
        this-&amp;gt;K = performhanoi(N, 1, 3, 2);
    }
    int printhanoi(){
        for(const move_t &amp;amp; s : proc)
            printf(&quot;%d %d\n&quot;, s.first, s.second);
        return 0;
    }
    private:
    int performhanoi(int N, int from, int to, int aux){
        switch(N){
            case 0:
                return 0;
            case 1:
                {
                    proc.push_back({from, to});
                    return 1;
                }
            default:
                {
                    int movecount = 0;
                    movecount += performhanoi(N-1, from, aux, to);
                    proc.push_back({from, to});
                    movecount++;
                    movecount += performhanoi(N-1, aux, to, from);
                    return movecount;
                }
        }
        return -1;
    }
};

int main(){
    int N;
    scanf(&quot;%d&quot;,&amp;amp;N);
    
    hanoitower H(N);
    printf(&quot;%d\n&quot;, H.K);
    H.printhanoi();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;감사합니다.&lt;/p&gt;</description>
      <category>BOJ</category>
      <category>BOJ</category>
      <author>orangecalculator</author>
      <guid isPermaLink="true">https://orangecalculator.tistory.com/13</guid>
      <comments>https://orangecalculator.tistory.com/13#entry13comment</comments>
      <pubDate>Wed, 29 Jul 2020 08:08:20 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ 19533] [UCPC 2020 예선] 길이 문자열</title>
      <link>https://orangecalculator.tistory.com/14</link>
      <description>&lt;p&gt;오늘은 (제 기준) 초고난도 문제를 들고 왔습니다. 너무 어려웠어요. 물론 시간 안에 못 풀었습니다. 헤헤&lt;/p&gt;
&lt;p&gt;원래는 이 포스팅을 쓰지 않았겠지만 제가 삽질한 시간이 너무도 아깝기 때문에 쓰는 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시작해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/19533&quot;&gt;https://www.acmicpc.net/problem/19533&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1595903330558&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;19533번: 길이 문자열&quot; data-og-description=&quot;각 테스트 케이스마다 길이 $a\times 10^b$인 길이 문자열을 출력한다. 이때 $a\times 10^b \geq 21$이면 문자열의 맨 앞의 $17$글자만 출력 예시와 같은 형식으로 출력한다.&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/19533&quot; data-og-url=&quot;https://www.acmicpc.net/problem/19533&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Had1l/hyGWhZnCuV/4sPEj0WdTZ4B9yoPSvr3B0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/19533&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/19533&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Had1l/hyGWhZnCuV/4sPEj0WdTZ4B9yoPSvr3B0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;19533번: 길이 문자열&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;각 테스트 케이스마다 길이 $a\times 10^b$인 길이 문자열을 출력한다. 이때 $a\times 10^b \geq 21$이면 문자열의 맨 앞의 $17$글자만 출력 예시와 같은 형식으로 출력한다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본적으로 길이 문자열은 십진법 숫자들이 -로 구분되어있는 문자열이고 십진법 숫자의 앞에 0을 넣는 것은 불가능합니다. 이 문자열의 특징은 각 숫자에서 - 전까지 끊었을 때 그 숫자의 값이 문자열의 총길이라는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 생각을 해보면 어떤 길이의 길이 문자열이 있다면 해당 문자열의 맨 끝에 그 숫자가 써져있을 것입니다. 그래서 그 문자열의 뒷부분 숫자와 - 까지 알고 있는 상태입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(예시)&lt;/p&gt;
&lt;p&gt;길이 1234인 길이 문자열&lt;/p&gt;
&lt;p&gt;.....-????-????-????-1234&lt;/p&gt;
&lt;p&gt;문자열의 끝은 반드시 &quot;-1234&quot;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다면 끝의 -와 숫자를 빼면 또다시 길이 문자열이 될 것입니다. 그리고 여기서 중요한 것은 해당 문자열의 길이를 알고 있습니다. 방금 배제한 -와 숫자의 값을 알고 있으므로 이 부분의 길이를 알 수 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(예시)&lt;/p&gt;
&lt;p&gt;길이 1234인 길이 문자열&lt;/p&gt;
&lt;p&gt;.....-????-????-????-1234&lt;/p&gt;
&lt;p&gt;&quot;-1234&quot;를 삭제하면 1234 - 1 - 4 = 1229의 길이를 가지는 길이 문자열이 된다.&lt;/p&gt;
&lt;p&gt;해당 문자열은 다시 다음과 같은 모양을 띠는 것을 알 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;.....-????-????-1229&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이렇게 생각하면 귀납법을 통해 길이가 주어졌을 때 해당 길이 문자열이 존재하고 유일함을 알 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(정리) 임의의 자연수 N에 대해 길이 N의 길이 문자열이 존재하고 이는 유일하다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;좋습니다. 이제 위와 같이 생각해보면, 문제에서 답은 길이 문자열의 앞부분 일부만 요구하므로, 길이 N과 길이 N - 1 - (숫자 N의 길이)은 동등한 관계임을 알 수 있습니다. 그렇다면 (숫자 N의 길이)가 변하지 않는 한 같은 숫자를 계속 빼는 꼴이므로 해당 길이가 변하지 않는 범위 내에서는 나머지 연산을 통해 빠르게 동등관계이면서 작은 길이를 알아낼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(예시)&lt;/p&gt;
&lt;p&gt;길이 1234인 길이 문자열&lt;/p&gt;
&lt;p&gt;- 5의 길이만큼 반복적으로 뺄셈&lt;/p&gt;
&lt;p&gt;- 1234 - 1000 = 234의 범위 내에서 길이는 4로 유지&lt;/p&gt;
&lt;p&gt;=&amp;gt; 234 % 5 = 4를 남긴 1004와 동등&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;나머지 연산을 통해 동등관계를 계산할 때 숫자의 자릿수를 하나씩 줄일 수 있으므로 지수 수준의 빠른 연산이 가능합니다. 하지만 문제에서 요구하는 복잡도에서는 아직 부족합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 우리는 이렇게 나머지 연산을 통해 숫자를 계속 줄일 때, 그 결과가 10^n에 매우 근접한 범위에서 나오는 것을 알 수 있습니다. 따라서 이 부분을 메모해놓고 한 번만 나머지 연산을 한 후에 그 결과를 바로 가져다 쓰면 되겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서부터는 저도 UCPC 2020 주최 측의 해설을 참고하였습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;일단 위에서의 내용을 토대로 생각해보면 같은 길이를 가지는 범위 내에서는 같은 값을 빼면서 동등관계를 계산할 수 있으므로 해당 값만큼의 주기를 가진다고 볼 수 있습니다. 따라서 주기 내에서만 알고 있으면 되겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;주기는 1 + (해당 구간의 숫자의 길이)가 되겠습니다. 이를 토대로 (10^n - 1 - n) ~ (10^n) 구간을 저장해서 계산하면 되겠습니다. 이때 주기보다 1개 더 많은 숫자를 계산하였습니다. 이는 10^n으로 넘어가면서 주기가 1만큼 길어지고 이 숫자가 추가되기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;10^n 길이 문자열과 10^n - 1 길이 문자열은 끝을 제외하고 동등합니다. 자릿수가 1 커지면서 숫자의 차이를 흡수해버리기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(예시)&lt;/p&gt;
&lt;p&gt;999 길이 문자열&lt;/p&gt;
&lt;p&gt;...-???-996-999&lt;/p&gt;
&lt;p&gt;1000 길이 문자열&lt;/p&gt;
&lt;p&gt;...-???-996-1000&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따라서 &lt;span style=&quot;color: #333333;&quot;&gt;10^(n-1) ~ (10^n - 1) 구간의 주기에 &lt;span style=&quot;color: #333333;&quot;&gt;(10^n - 1)의 답이 (10^n)의 답으로 &lt;/span&gt;복사되는 모습을 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(예시)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;996의 답: 1-3-5-7-9-12-15-1...&lt;br /&gt;997의 답: 1-3-5-7-10-13-16-...&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;998의 답: -2-4-6-8-11-14-17...&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;999의 답: 1-3-5-7-9-12-15-1...&lt;br /&gt;1000의 답: 1-3-5-7-9-12-15-1...&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;999의 답과 1000의 답이 똑같음을 볼 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;또한 여기서 관찰할 수 있는 것은 같은 답을 가지는 숫자들을 하나의 그룹으로 묶었을 때 각 그룹의 숫자들이 연속되어 나옴을 볼 수 있습니다. 즉 16이 나타나는 997이 하나, 17이 나타나는 998이 하나, 그리고 15가 나타나는 숫자는 999, 1000, 996으로 주기를 가지는 점을 고려했을 때 연속되어 나온다는 점을 알 수 있습니다. 또한, 자릿수가 늘어날 때마다 주기가 늘어나는데, 이때 추가되는 숫자는 항상 이웃하는 숫자와 같은 숫자임을 알 수 있습니다. 결국, 자릿수가 계속 늘어나더라도 주기 내에서 같은 답을 가지는 그룹은 연속되어 나타납니다. 이 점을 활용하여 연속된 숫자의 시작 부분만 저장해놓으면 되겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이제 코드를 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1595905036190&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

using namespace std;

typedef struct{
    int mantissa;
    int exponent;
} bignum_t;

// length of decimal number
int declen(int N){
    int len = 0;
    while(N != 0){
        N /= 10;
        len++;
    }
    return len;
}

// length of decimal number wrapper for bignum_t
int declenbig(bignum_t bN){
    return declen(bN.mantissa) + bN.exponent;
}

// calculate 10**power
int power10(int power){
    int res = 1;
    for(int k=0;k&amp;lt;power;++k)
        res *= 10;
    return res;
}

// calculate bN % d for bignum_t type bN
int remainderbig(bignum_t bN, int d){
    long long rem = bN.mantissa % d;
    int exponent = bN.exponent;
    long long base = 10 % d;
    int res;
    if(exponent == 0) res = (rem % d);
    else{
        while(exponent &amp;gt; 1){
            if(exponent % 2 != 0){
                rem = (rem * base) % d;
                exponent--;
            }
            base = (base * base) % d;
            exponent /= 2;
        }
        res = ((rem * base) % d);
    }
    return res;
}

class lenstring{
    private:
    enum{
        PATTERN_17 = 0,
        PATTERN_18 = 1,
        PATTERN_19 = 2,
    } pattern_const_t;
    typedef struct{
        // 0: -2-4-6-8-11-14-17
        // 1: 1-3-5-7-9-12-15-18
        // 2: 1-3-5-7-10-13-16-19
        int start[3];
    } pattern_t;
    vector&amp;lt;pattern_t&amp;gt; pattern;
    int querycase(int power, int rem){
        int r = power+2, pat = -1;
        for(int k=0;k&amp;lt;3;++k){
            if(r &amp;gt; pattern[power].start[k] &amp;amp;&amp;amp; pattern[power].start[k] &amp;gt;= rem){
                r = pattern[power].start[k];
                pat = k;
            }
        }
        if(pat == -1){
            for(int k=0;k&amp;lt;3;++k){
                if(r &amp;gt; pattern[power].start[k]){
                    r = pattern[power].start[k];
                    pat = k;
                }
            }
        }
        
        int reduceval;
        switch(pat){
            case PATTERN_17: reduceval = 23; break;
            case PATTERN_18: reduceval = 21; break;
            case PATTERN_19: reduceval = 22; break;
            default: reduceval = -1; break;
        }
        return reduceval;
    }
    void printlenstring(int val){
        if(val&amp;gt;=21){
            val -= (((val - 21) / 3) * 3);
            switch(val){
                case 23:
                printf(&quot;-2-4-6-8-11-14-17...\n&quot;);break;
                case 21:
                printf(&quot;1-3-5-7-9-12-15-1...\n&quot;);break;
                case 22:
                printf(&quot;1-3-5-7-10-13-16-...\n&quot;);break;
                default:
                printf(&quot;calculation error\n&quot;);break;
            }
        } else{
            vector&amp;lt;int&amp;gt; viewvals;
            viewvals.reserve(8);
            while(val&amp;gt;1){
                viewvals.push_back(val);
                val -= (1 + (val&amp;gt;=10 ? 2 : 1));
            }
            if(val == 1) putchar('1');
            for(int k=viewvals.size()-1;k&amp;gt;=0;--k)
                printf(&quot;-%d&quot;, viewvals[k]);
            putchar('\n');
        }
    }
    public:
    lenstring(){
        //fix parameter using constraint
        const int N = 1e6+12;
        
        //calculate pattern
        /*
            pattern[k]
            10^k - (this value) ~ 10^k - (any other smallest value)
        */
        pattern.resize(N);
        pattern[2] = {{2, 1, 3}};
        
        for(int k=3;k&amp;lt;=N;++k){
            bignum_t bN;
            bN.mantissa = 9;
            bN.exponent = k-1;
            int shift = remainderbig(bN, k+1) - 1;
            for(int l=0;l&amp;lt;3;++l){
                pattern[k].start[l] = ((pattern[k-1].start[l] + shift) % (k+1)) + 1; // 1 ~ (k+1)
            }
        }
    }
    int viewhead(bignum_t bN){
        int reduceval; // must be dec of length 2 or smaller
        
        int totallen = declenbig(bN);
        if(totallen &amp;lt;= 2){
            reduceval = bN.mantissa * power10(bN.exponent);
        } else{
            bignum_t difference = bN;
            difference.mantissa -= power10(totallen-1 - bN.exponent);
            int rem = (totallen+1 - remainderbig(difference, totallen+1)) % (totallen+1);
            
            // 1 * 10^(totallen-1) - rem
            reduceval = querycase(totallen-1, rem);
        }
        
        printlenstring(reduceval);
        return reduceval;
        
    }
};

int main() {
    int T;
    scanf(&quot;%d&quot;,&amp;amp;T);
    lenstring P;
    for(int k=0;k&amp;lt;T;++k){
        bignum_t bN;
        scanf(&quot;%d%d&quot;, &amp;amp;bN.mantissa, &amp;amp;bN.exponent);
        P.viewhead(bN);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;lenstring 클래스의 생성자에서 &lt;span style=&quot;color: #333333;&quot;&gt;(10^n - 1 - n) ~ (10^n) 구간의 정보를 문제의 조건 내에서 전부 계산해놓습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;viewhead 함수가 가장 큰 그림을 담고 있는 함수입니다. 숫자가 작은 경우는 그냥 int로 변환하여 계산해주면 되겠습니다. 이때 구분을 위해 숫자의 총길이 totallen을 이용하였습니다. 해당 숫자의 자릿수를 유지하면서 가장 작은 숫자와의 차이 값을 이용해 나머지 연산 remainderbig을 통해 10^n에 근접한 숫자로 바꿔줍니다. 그 값을 이용해 querycase 함수를 통해 바로 답으로 변환해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 이후 그 값을 printlenstring을 통해 출력해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번 문제는 정말 심리적으로도 좀 압박이 될 만큼 어려웠습니다. 그리고 이 문제를 통해 알고리즘 최적화의 방법을 또 하나 제대로 배운 것 같습니다. 삽질을 한 부분은 여러 부분인데, 굉장히 찾기 어려웠던 부분이 remainderbig의 버그였습니다. int 숫자를 곱셈하는 과정에서 오버플로우가 발생해서 입력된 숫자의 지수가 굉장히 커졌을 때 버그가 있었습니다. 숫자의 지수가 커졌을 때는 정확한 답을 알아내는 것이 굉장히 어렵기 때문에 난이도가 더 높았던 것 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;감사합니다.&lt;/p&gt;</description>
      <category>BOJ</category>
      <author>orangecalculator</author>
      <guid isPermaLink="true">https://orangecalculator.tistory.com/14</guid>
      <comments>https://orangecalculator.tistory.com/14#entry14comment</comments>
      <pubDate>Tue, 28 Jul 2020 12:09:42 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ] 수학 2(2)</title>
      <link>https://orangecalculator.tistory.com/12</link>
      <description>&lt;p&gt;토요일 저녁이라 역시 뭐를 하기가 귀찮군요 허허.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 1085]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;직사각형의 벽까지의 거리의 최솟값을 구해주면 됩니다. min을 3번 쓰면 됩니다. min은 algorithm 헤더에 구현되어 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1595678985260&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;algorithm&amp;gt;

using namespace std;

int distoutside(int x, int y, int w, int h){
    return min(min(x, w-x), min(y, h-y));
}

int main(){
    int x, y, w, h;
    scanf(&quot;%d%d%d%d&quot;,&amp;amp;x,&amp;amp;y,&amp;amp;w,&amp;amp;h);
    printf(&quot;%d&quot;,distoutside(x,y,w,h));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 3009]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사각형에서 가로나 세로로 이웃한 점들은 그 줄의 좌표를 공유합니다. 좌표가 3개 주어지므로 x좌표 중 2개는 똑같고 나머지 하나가 원하는 값입니다. y좌표도 똑같습니다. 이때, 값이 int로 주어지기 때문에 xor 기법을 사용하면 아주 쉽게 구현할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1595679149004&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

typedef pair&amp;lt;int,int&amp;gt; point_t;

point_t rectpoint(const vector&amp;lt;point_t&amp;gt; &amp;amp; points){
    int x = 0, y = 0;
    for(point_t p : points){
        x ^= p.first;
        y ^= p.second;
    }
    return {x, y};
}

int main(){
    vector&amp;lt;point_t&amp;gt; points(3);
    for(int k=0;k&amp;lt;3;++k)
        scanf(&quot;%d%d&quot;, &amp;amp;points[k].first, &amp;amp;points[k].second);
    point_t res;
    res = rectpoint(points);
    printf(&quot;%d %d&quot;, res.first, res.second);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 4153]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;피타고라스의 정리를 써서 확인하면 됩니다. 가장 긴 변의 길이의 제곱을 구한 다음에 나머지 값의 제곱합과 같은지 비교하면 됩니다. double로 바꿔서 루트를 구해서 할 수도 있지만 정확하게 값이 같은지 비교하기 위해 제곱된 상태의 int로 비교해주는 것을 권장드립니다.&lt;/p&gt;
&lt;pre id=&quot;code_1595679289293&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;algorithm&amp;gt;

using namespace std;

bool isperpendicular(int a, int b, int c){
    if(a&amp;gt;b) swap(a, b);
    if(b&amp;gt;c) swap(b, c);
    return (a*a + b*b == c*c);
}

int main(){
    while(true){
        int A, B, C;
        scanf(&quot;%d%d%d&quot;,&amp;amp;A,&amp;amp;B,&amp;amp;C);
        if(A == 0 &amp;amp;&amp;amp; B == 0 &amp;amp;&amp;amp; C == 0) break;
        printf(&quot;%s\n&quot;, isperpendicular(A, B, C) ? &quot;right&quot; : &quot;wrong&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 3053]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;원의 넓이 공식을 구현해주면 되겠습니다. 택시 기하학의 경우는 원이 유클리드 공간의 정사각형의 모양과 같아집니다. 이때 정사각형의 대각선의 길이가 주어지는 상황이 되겠습니다. 잘 구해주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1595679355542&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;math.h&amp;gt;

double area_circle(double r){
    return M_PI * r * r;
}

double area_circle_taxi(double r){
    return r * r * 2;
}

int main(){
    double R;
    scanf(&quot;%lf&quot;,&amp;amp;R);
    printf(&quot;%.4f\n%.4f\n&quot;, area_circle(R), area_circle_taxi(R));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 1002]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;좌표가 다른 위치에 주어진다는 조건이 없습니다. 좌표가 같은 경우에는 반지름이 같으면 경우의 수가 무한개가 됩니다. 그렇지 않으면 불가능합니다. 좌표가 다른 경우에는 좌표 간 거리와 반지름들을 가지고 삼각형이 잘 형성되는지 아닌지를 보면 됩니다. 삼각형이 형성되면 답은 2개입니다. 일직선이면 1개입니다. 그렇지 않으면 불가능합니다. 이때, 삼각형이 형성되는지 보기 위해 합과 차를 둘 다 검사해야 합니다. 저는 부분에서 한번 틀렸습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1595679642111&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;math.h&amp;gt;

static inline int square(int x){
    return x * x;
}

int countpossibility(int x1, int y1, int r1, int x2, int y2, int r2){
    if(x1 == x2 &amp;amp;&amp;amp; y1 == y2){
        if(r1 != r2) return 0;
        else return -1;
    } else{
        int rsumsq = square(r1 + r2);
        int distsq = square(x1 - x2) + square(y1 - y2);
        if(rsumsq &amp;lt; distsq) return 0;
        else if(rsumsq == distsq) return 1;
        
        int rdiffsq = square(r1 - r2);
        if(rdiffsq &amp;lt; distsq) return 2;
        else if(rdiffsq == distsq) return 1;
        else return 0;
    }
}

int main(){
    int T;
    scanf(&quot;%d&quot;,&amp;amp;T);
    for(int k=0;k&amp;lt;T;++k){
        int x1, y1, r1, x2, y2, r2;
        scanf(&quot;%d%d%d%d%d%d&quot;, &amp;amp;x1, &amp;amp;y1, &amp;amp;r1, &amp;amp;x2, &amp;amp;y2, &amp;amp;r2);
        printf(&quot;%d\n&quot;, countpossibility(x1, y1, r1, x2, y2, r2));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;수고하셨습니다.&lt;/p&gt;</description>
      <category>BOJ</category>
      <category>BOJ</category>
      <author>orangecalculator</author>
      <guid isPermaLink="true">https://orangecalculator.tistory.com/12</guid>
      <comments>https://orangecalculator.tistory.com/12#entry12comment</comments>
      <pubDate>Sat, 25 Jul 2020 21:21:17 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ] 수학 2(1)</title>
      <link>https://orangecalculator.tistory.com/11</link>
      <description>&lt;p&gt;오늘은 수학 2단계의 전반부를 풀어보겠습니다. 소수에 관한 문제들입니다. 이번 시간에 소수에 관한 문제를 풀 때는 알고리즘을 2개만 알면 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 하나의 소수가 주어졌을 때 해당 수가 소수인지 아닌지 판단하는 경우입니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;bool isprime(int n){
    if(n==1) return false;
    for(int k=2;k*k&amp;lt;=n;++k)
        if(n % k == 0)
            return false;
    return true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;일일이 나머지가 0인 게 있는지 확인해보는 것입니다. 실제 검사를 할 때는 제곱이 n보다 작거나 같은 경우까지만 검사하면 됩니다. 그 이후에 약수가 있는 경우 그 이전에도 반드시 약수가 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그다음으로는 에라토스테네스의 체라는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1595620105901&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class primesift{
    public:
        vector&amp;lt;bool&amp;gt; isprime;
        primesift(int lim) : isprime(lim + 1, true){
            isprime[0] = false;
            isprime[1] = false;
            for(int p=2;p*p&amp;lt;=lim;++p){
                for(int k=2*p;k&amp;lt;=lim;k+=p)
                    isprime[k] = false;
            }
        }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같이 2중 for문으로 구해주었습니다. 바깥 for문은 첫 번째 알고리즘에서 하듯이 여러 숫자를 돌면서 검사를 해주는 겁니다. 내부 for문은 해당 숫자의 배수들에 대해서 소수가 아님을 확인하고 false로 바꿔주는 겁니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이렇게 설명했으면 더 이상 설명은 필요할 것 같지 않네요. 코드를 보고 마치겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 1978]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1595620218211&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

using namespace std;

bool isprime(int n){
    if(n==1) return false;
    for(int k=2;k*k&amp;lt;=n;++k)
        if(n % k == 0)
            return false;
    return true;
}

int main() {
    int N;
    scanf(&quot;%d&quot;,&amp;amp;N);
    int count = 0;
    for(int k=0;k&amp;lt;N;++k){
        int n;
        scanf(&quot;%d&quot;,&amp;amp;n);
        if(isprime(n))
            count++;
    }
    printf(&quot;%d&quot;,count);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 2581]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1595620240730&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

class primesift{
    public:
        vector&amp;lt;bool&amp;gt; isprime;
        primesift(int lim) : isprime(lim + 1, true){
            isprime[0] = false;
            isprime[1] = false;
            for(int p=2;p*p&amp;lt;=lim;++p){
                for(int k=2*p;k&amp;lt;=lim;k+=p)
                    isprime[k] = false;
            }
        }
        int intervalsum(const int L, const int R){
            int primesum = 0;
            for(int k=L;k&amp;lt;=R;++k){
                if(isprime[k]){
                    primesum += k;
                }
            }
            return primesum;
        }
        int intervalmin(const int L, const int R){
            for(int k=L;k&amp;lt;=R;++k){
                if(isprime[k]){
                    return k;
                }
            }
            return -1;
        }
};

int main(){
    int M, N;
    scanf(&quot;%d%d&quot;,&amp;amp;M,&amp;amp;N);
    
    primesift P(N);
    int ressum = P.intervalsum(M, N);
    if(ressum&amp;gt;0)
        printf(&quot;%d\n%d\n&quot;,ressum, P.intervalmin(M, N));
    else
        printf(&quot;-1\n&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 1929]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;정답 풀이들을 보니까 이거보다 더 최적화를 하신 분들이 있더라고요. 요약하면 구간합 알고리즘을 약간 섞어주면 됩니다. 구간합 알고리즘은 처음부터 누적합을 구해놓은 다음에 필요한 구간의 양 끝의 누적 값의 차이로 구하는 겁니다. 이렇게 구하면 각 구간을 일일이 검사할 필요 없이 한방에 구할 수 있게 됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1595620281728&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

class primesift{
    public:
        vector&amp;lt;bool&amp;gt; isprime;
        primesift(int lim) : isprime(lim + 1, true){
            isprime[0] = false;
            isprime[1] = false;
            for(int p=2;p*p&amp;lt;=lim;++p){
                for(int k=2*p;k&amp;lt;=lim;k+=p)
                    isprime[k] = false;
            }
        }
};

int main(){
    int M, N;
    scanf(&quot;%d%d&quot;,&amp;amp;M,&amp;amp;N);
    
    primesift P(N);
    for(int k=M;k&amp;lt;=N;++k){
        if(P.isprime[k]){
            printf(&quot;%d\n&quot;,k);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 4948]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1595620310073&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

class primesift{
    public:
        vector&amp;lt;bool&amp;gt; isprime;
        primesift(int lim) : isprime(lim + 1, true){
            isprime[0] = false;
            isprime[1] = false;
            for(int p=2;p*p&amp;lt;=lim;++p){
                for(int k=2*p;k&amp;lt;=lim;k+=p)
                    isprime[k] = false;
            }
        }
        int intervalcount(const int L, const int R){
            int primecount = 0;
            for(int k=L;k&amp;lt;=R;++k){
                if(isprime[k]){
                    primecount++;
                }
            }
            return primecount;
        }
};

int main(){
    vector&amp;lt;int&amp;gt; inputs;
    int IN, INMAX = 0;
    while(true){
        scanf(&quot;%d&quot;,&amp;amp;IN);
        if(IN == 0) break;
        inputs.push_back(IN);
        if(IN &amp;gt; INMAX)
            INMAX = IN;
    }
    
    primesift P(INMAX*2);
    for(const int IN : inputs){
        printf(&quot;%d\n&quot;,P.intervalcount(IN+1, IN*2));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 9020]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1595620491883&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

class primesift{
    public:
        vector&amp;lt;bool&amp;gt; isprime;
        primesift(int lim) : isprime(lim + 1, true){
            isprime[0] = false;
            isprime[1] = false;
            for(int p=2;p*p&amp;lt;=lim;++p){
                for(int k=2*p;k&amp;lt;=lim;k+=p)
                    isprime[k] = false;
            }
        }
        int goldbach(const int P){
            for(int n=P/2;n&amp;gt;=2;--n){
                if(isprime[n] &amp;amp;&amp;amp; isprime[P - n]){
                    return n;
                }
            }
            return -1;
        }
};

int main(){
    int T;
    scanf(&quot;%d&quot;,&amp;amp;T);
    
    vector&amp;lt;int&amp;gt; inputs(T);
    int IN, INMAX = 0;
    for(int k=0;k&amp;lt;T;++k){
        scanf(&quot;%d&quot;,&amp;amp;IN);
        inputs[k] = IN;
        if(INMAX &amp;lt; IN)
            INMAX = IN;
    }
    
    primesift P(INMAX);
    for(const int IN : inputs){
        int partition = P.goldbach(IN);
        printf(&quot;%d %d\n&quot;,partition, IN - partition);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;수고하셨습니다.&lt;/p&gt;</description>
      <category>BOJ</category>
      <category>BOJ</category>
      <author>orangecalculator</author>
      <guid isPermaLink="true">https://orangecalculator.tistory.com/11</guid>
      <comments>https://orangecalculator.tistory.com/11#entry11comment</comments>
      <pubDate>Sat, 25 Jul 2020 04:56:35 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ] 수학 1(2)</title>
      <link>https://orangecalculator.tistory.com/10</link>
      <description>&lt;p&gt;[BOJ 2869]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아... 설명하기 굉장히 어렵군요... 일단 하루가 지날 때마다 총 올라간 거리를 등반과 미끄러짐을 고려하여 구해줍니다. 마지막 날에 정상에 도착하면 끝이므로 미끄러지는 거리는 공짜로 먹고 들어간다고 볼 수 있습니다. 그래서 고려사항에서 빼줍니다. 이제 이 거리 이상 올라갈 때까지 걸리는 시간을 계산해주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1595520441020&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

int climbdays(int climb, int fall, int distance){
    int climbperday = climb - fall;
    int d = distance - fall;
    return (d / climbperday) + (d % climbperday != 0 ? 1 : 0);
}

int main(){
    int A, B, V;
    scanf(&quot;%d%d%d&quot;,&amp;amp;A,&amp;amp;B,&amp;amp;V);
    printf(&quot;%d&quot;,climbdays(A, B, V));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 10250]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아... 이제 문제가 점점 길어지고 있습니다. 사실 별거 없습니다. 각 층의 1호, 2호, ..., 순으로 꽉꽉 채워주면 됩니다. 쉽게 할 수 있을 거예요.&lt;/p&gt;
&lt;pre id=&quot;code_1595520914076&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

int allocroom(int H, int W, int N){
    int roominfloor = (N + H - 1) / H;
    int roomfloor = N - H * (roominfloor - 1);
    return roomfloor * 100 + roominfloor;
}

int main(){
    int T;
    scanf(&quot;%d&quot;,&amp;amp;T);
    int H, W, N;
    for(int k=0;k&amp;lt;T;++k){
        scanf(&quot;%d%d%d&quot;,&amp;amp;H,&amp;amp;W,&amp;amp;N);
        printf(&quot;%d\n&quot;,allocroom(H,W,N));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 2775]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;원래는 각 호의 인원을 모두 채운 걸 한 번에 계산하고 한 번에 출력하는 게 중복 없이 가장 효율적이겠지만, 그다지 조건이 힘들지 않아서 하나씩 하는 걸로 했습니다. 그냥 배열 하나에 계속 왼쪽에서 오른쪽으로 더해주면서 구하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1595520990779&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int totalresident(int k, int n){
    vector&amp;lt;int&amp;gt; residents(n+1);
    for(int i=1;i&amp;lt;=n;++i) residents[i] = i;
    for(int j=1;j&amp;lt;=k;++j)
        for(int i=2;i&amp;lt;=n;++i)
            residents[i] += residents[i-1];
    return residents[n];
}

int main(){
    int T;
    scanf(&quot;%d&quot;,&amp;amp;T);
    int k, n;
    for(int i=0;i&amp;lt;T;++i){
        scanf(&quot;%d%d&quot;,&amp;amp;k,&amp;amp;n);
        printf(&quot;%d\n&quot;,totalresident(k,n));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[BOJ 1011]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 문제는 int의 한계 때문에 삽질을 약간 했습니다. 답 자체는 별거 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1번 이동할 때 최대 거리는 1&lt;/p&gt;
&lt;p&gt;2번 이동할 때 최대 거리는 1+1&lt;/p&gt;
&lt;p&gt;3번 이동할 때 최대 거리는 1+2+1&lt;/p&gt;
&lt;p&gt;4번 이동할 때 최대 거리는 1+2+2+1&lt;/p&gt;
&lt;p&gt;5번 이동할 때 최대 거리는 1+2+3+2+1&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2n번 이동할 때 최대 거리는 1+2+...+n+n+...+2+1 = n(n+1)&lt;/p&gt;
&lt;p&gt;2n-1번 이동할 때 최대 거리는 1+2+...+n+...+2+1 = n*n&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 저는 2n번 이동할 때를 기준으로 n의 값을 구해준 후에 그걸로 하나를 빼줄지 말지 결정하는 방식으로 풀었습니다. 만약에 주어진 거리가 n*n과 n(n+1) 사이에 있다면 어떻게 될까요? 꼭대기에서부터 하나씩 빼주면 그 거리는 정확하게 맞출 수 있습니다. &lt;span style=&quot;color: #333333;&quot;&gt;1+2+...+n+n+(n-1)+...+2+1 -&amp;gt; &lt;/span&gt;1+2+...+n+(n-1)+(n-1)+...+2+1 -&amp;gt; &lt;span style=&quot;color: #333333;&quot;&gt;1+2+...+n+(n-1)+(n-2)+...+2+1와 같이 꼭대기에서부터 하나씩 빼주면서 내려가면 n*n까지 갈 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 삽질한 부분은 stepcount 함수의 distance와 n*n을 비교하는 과정에서 오버플로우가 일어날 수 있었던 것이었고 unsigned로 바꾸어서 해결했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1595521279945&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;math.h&amp;gt;

unsigned int stepcount(unsigned int distance){
    // solve equation n(n+1) = distance
    unsigned int n = ceil(sqrt(distance + 1./4.) - 1./2.);
    if(distance &amp;lt;= n*n) return 2 * n - 1;
    else return 2 * n;
}

int main(){
    int T;
    scanf(&quot;%d&quot;,&amp;amp;T);
    for(int k=0;k&amp;lt;T;++k){
        int x, y;
        scanf(&quot;%d%d&quot;,&amp;amp;x,&amp;amp;y);
        printf(&quot;%d\n&quot;,stepcount(y - x));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;수고하셨습니다.&lt;/p&gt;</description>
      <category>BOJ</category>
      <category>BOJ</category>
      <author>orangecalculator</author>
      <guid isPermaLink="true">https://orangecalculator.tistory.com/10</guid>
      <comments>https://orangecalculator.tistory.com/10#entry10comment</comments>
      <pubDate>Fri, 24 Jul 2020 01:23:07 +0900</pubDate>
    </item>
  </channel>
</rss>