欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

180828 逆向-网鼎杯(3-2)

程序员文章站 2022-05-19 13:39:13
...

I_like_pack

IDA加载一看啥都没有,再根据题目名显然是个壳
windows下脱壳相对而言麻烦一些,ExeInfoPe查壳啊、各种壳的针对性操作啊啥的
Linux下一方面系统开源随便魔改,另一方面有一个/proc/pid/mem的文件可以直接读取进程的内存,使得dump极为容易

本题放到系统下跑起来后发现如果输入会回显“NO”,而不输入的话大概三秒就会自动结束
这显然是alarm函数的功劳

如果仅是alarm函数的话,其实可以比拼一下手速,毕竟三秒钟还算在人类的反应速度内,另一方面也可以通过sh脚本来执行dump
试了一下cat /proc/pid/mem会报错,在这里有官方的说明,提供了三种方法
1. 获取maps,根据模块地址来读取程序的内存
2. open mem以后attach目标进程使其暂停,然后即可读
3. gcore pid

尝试了一下,其中第一种方法可以直接使用–因为只是读取mem文件
通过这个脚本

#! /usr/bin/env python
import re
maps_file = open("/proc/self/maps", 'r')
mem_file = open("/proc/self/mem", 'r', 0)
for line in maps_file.readlines():  # for each mapped region
    m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
    if m.group(3) == 'r':  # if this is a readable region
        start = int(m.group(1), 16)
        end = int(m.group(2), 16)
        mem_file.seek(start)  # seek to region start
        chunk = mem_file.read(end - start)  # read region contents
        print chunk,  # dump contents to standard output
maps_file.close()
mem_file.close()

操作方法如下:
后台起一个进程可以快速获得pid
然后通过上面的脚本来dump
180828 逆向-网鼎杯(3-2)

而第二、第三种方法由于依赖ptrace,对于使用ptrace(TRACE_ME)的进程就会报错
本题中的程序就有使用这个方法来反调试

对于trace_me,有两种方法可以绕过:
1. 直接用调试器启动子进程,断在ptrace之前然后跳过它的执行
不过这种方法会被alarm中断掉,当然也很好绕过,只需要同样跳过alarm的执行就好
缺点有两个,1是浪费时间,2是静态链接很难识别,本题是使用动态链接,相对还算好找
2. 通过LD_PRELOAD来覆盖函数
LD_PRELOAD可以指定加载库,此时如果库中有与其他动态链接库同名的函数将会覆盖,使得原函数失效

这里讲一下后者的操作方法:

unsigned int alarm(unsigned int seconds)
{
    ;
}
long ptrace()
{
    ;
}

将上述代码编译成so
gcc --shared fake.c -o fake.so
然后通过LD_PRELOAD加载
LD_PRELOAD=./fake.so ./re
此时即可发现alarm失效

然后通过ps或其他方法查到pid后,用gcore pid即可dump

然后通过字符串搜索即可找到main函数
180828 逆向-网鼎杯(3-2)
一个数组乱序比对,直接dump即可得到flag

a = [11, 8, 7, 7, 8, 12, 3, 2, 16, 6, 13, 5, 7, 16, 4, 1, 0, 15, 16, 8, 3, 6, 14, 16, 0, 8, 6, 9, 12, 14, 13, 11, 15, 7, 11,14]

for i in range(36):
    print(chr(Dword(a[i]*4+0x60f0e0)+45)),

最好的语言

这题问题太大了!
做之前我先去找了web队友过来严阵以待,打开以后根本不是PHP!

打开以后发现跟之前SUCTF的一题一毛一样,给了解析过后的pyc文本
当时写过*可以按照pyc格式进行解析和还原
这个pyc解析网上大概有两种
pyc解析1
pyc解析2

除了都把解析出的字节码删去以外,区别主要在两点
属性标题一种为argcount另一种为<argcount>xxx</argcount>
排列顺序一种将consts放在names之前,另一种将consts放在之后

我写的脚本仅能针对前者,而本题遇到的后者需要手动修改一下
不过其实难度也不大,通过正则替换还是比较容易的

脚本在这里

解析得到pyc,然后在线反编译即可得到python源码

import base64
from hashlib import md5
import random
import string
f = 'flag{*******}'

def _(b):
    o = ''.join(random.sample(string.digits, 4))
    s = ''
    for i in range(len(b)):
        s += chr(ord(b[i]) ^ ord(o[i % 4]))

    return s


def ____(a):
    ___ = md5()
    ___.update(a)
    return ___.digest()


e = _(f[:12]) + ____(f[12:19]) + _(f[19:])
print base64.b64encode(e)
e = 'U1VQU05pSHdqCEJrQu7FS7Vngk1OTQ58qqghXmt2AUdrcFBBUEU='

前后两段是通过随机数异或出来的,中间一段则是md5
分析一下可以知道_函数加密后长度不变,因此12~12+32
扔去解密得到613u21i
前一段由”flag”得到key=”5914”
后一段由结尾字符”}”得到key的第二位为8
其余位**,筛选出在ASCII范围内的,然后肉眼选择看起来像的

import base64
import random
import string
# "5914"
def foo_a(b):
    o = ''.join(random.sample(string.digits, 4))
    o = "5914"
    s = ''
    for i in range(len(b)):
        s += chr(ord(b[i]) ^ ord(o[i % 4]))

    return s

def foo_b(b):
    r = []
    for i in range(10):
        for j in range(10):
            if(j==i):
                continue
            for k in range(10):
                if(k==j or k==i):
                    continue
                if(k==8 or i==8 or j==8):
                    continue
                o = str(i) + "8" + str(j) + str(k)
                s = ''
                for l in range(len(b)):
                    if(ord(b[l]) ^ ord(o[l % 4])>127):
                        break
                    s += chr(ord(b[l]) ^ ord(o[l % 4]))
                else:
                    r.append((o, s))


    return r


e = b'U1VQU05pSHdqCEJrQu7FS7Vngk1OTQ58qqghXmt2AUdrcFBBUEU='
e = base64.b64decode(e)
print(e)
a = e[:12]
b = e[12:12+16]
c = e[12+16:]

print(foo_a(a.decode()),end='')
print("613u21i",end='')
print()
for i in (foo_b(c.decode())):
    print(i)