picoCTF,Reverse Engineering,逆向类,42/55

题目站点链接 https://play.picoctf.org/

最初将所有题解放在一个帖子里,帖子太长了不便于阅读,
为了方便后期编辑和阅读。2023年02月10日,将帖子拆分,按照题目类型分为六类:

picoCTF-General Skills,基本技能类
picoCTF-Cryptography,密码类
picoCTF-Forensics,取证类
picoCTF-Web Exploitation,网页开发类
picoCTF-Reverse Engineering,逆向类
picoCTF-Binary Exploitation,二进制类

下面,给出题解,逐步完善中……
注意:很多题目flag是变化的,每一个账号解题得到的flag不一样,所以,下面帖子里的flag仅供参考,但解题思路方法是一样的。



2019 picoCTF

01、vault-door-training,50分

代码里有检测函数,checkPassword(),函数里直接写了flag。
在这里插入图片描述

picoCTF{w4rm1ng_Up_w1tH_jAv4_be8d9806f18}

02、vault-door-1,100分

打开文件就看到一个密码表,顺序变了,仔细的把顺序排好就行了,
我把数组拷贝到et里转至就行了

picoCTF{d35cr4mbl3_tH3_cH4r4cT3r5_75092e}

03、vault-door-3,200分

按字符位置做了转换,写一个逆向函数,就可以了。

#include <stdio.h>
char cipher[] = "jU5t_a_sna_3lpm18g947_u_4_m9r54f";
char buff[32];
int main( void )
{ int i;
 for ( i = 0 ; i < 32 ; i++ ) {
  buff[i] = 'F';
 }
 for ( i = 0 ; i < 8 ; i++ ) {
  buff[i] = cipher[i];
 }
 for ( i = 8 ; i < 16 ; i++ ) {
  buff[i] = cipher[23 - i];
 }
 for ( i = 16 ; i < 32 ; i+=2 ) {
  buff[i] = cipher[46 - i];
 }for ( i = 31 ; i >= 17 ; i -=2 ) {
  buff[i] = cipher[i];
        }
 printf( "%s\n", buff );
 return 0;
}

picoCTF{jU5t_a_s1mpl3_an4gr4m_4_u_79958f}

04、vault-door-4,250分

一小段java

class test {
    public static void main(String[] args) {
        byte[] myBytes = {
                106, 85, 53, 116, 95, 52, 95, 98,
                0x55, 0x6e, 0x43, 0x68, 0x5f, 0x30, 0x66, 0x5f,
                0142, 0131, 0164, 063, 0163, 0137, 070, 0146,
                '4', 'a', '6', 'c', 'b', 'f', '3', 'b',
        };
        StringBuilder str_flag = new StringBuilder();
        for (byte myByte : myBytes) str_flag.append((char) myByte);
        System.out.println("picoCTF{" + str_flag + "}");
    }
}

picoCTF{jU5t_4_bUnCh_0f_bYt3s_8f4a6cbf3b}

05、vault-door-5,300分

打开程序有一段密码验证。
JTYzJTMwJTZlJTc2JTMzJTcyJTc0JTMxJTZlJTY3JTVmJTY2JTcyJTMwJTZkJTVmJTYyJTYxJTM1JTY1JTVmJTM2JTM0JTVmJTY1JTMzJTMxJTM1JTMyJTYyJTY2JTM0
解码后是flag

picoCTF{c0nv3rt1ng_fr0m_ba5e_64_e3152bf4}

06、vault-door-6,350分

验证密码函数写了数组

ublic boolean checkPassword(String password) {
        if (password.length() != 32) {
            return false;
        }
        byte[] passBytes = password.getBytes();
        byte[] myBytes = {
            0x3b, 0x65, 0x21, 0xa , 0x38, 0x0 , 0x36, 0x1d,
            0xa , 0x3d, 0x61, 0x27, 0x11, 0x66, 0x27, 0xa ,
            0x21, 0x1d, 0x61, 0x3b, 0xa , 0x2d, 0x65, 0x27,
            0xa , 0x6c, 0x61, 0x6d, 0x37, 0x6d, 0x6d, 0x6d,
        };
        for (int i=0; i<32; i++) {
            if (((passBytes[i] ^ 0x55) - myBytes[i]) != 0) {
                return false;
            }
        }
        return true;
    }

把数组考出来,用python写了一个反函数,注意,a^b=c,c^b=a

myBytesArr = [0x3b, 0x65, 0x21, 0xa, 0x38, 0x0, 0x36, 0x1d, 0xa, 0x3d, 0x61, 0x27, 0x11, 0x66, 0x27, 0xa,
              0x21, 0x1d, 0x61, 0x3b, 0xa, 0x2d, 0x65, 0x27, 0xa, 0x6c, 0x61, 0x6d, 0x37, 0x6d, 0x6d, 0x6d, ]
flag = ''
for i in range(32):
    chr_number = myBytesArr[i] ^ 0x55
    print(str(i).zfill(2), myBytesArr[i], chr_number, chr(chr_number))
    flag = flag + chr(chr_number)
print('picoCTF{' + flag + '}')

特别注意,这个小段子,我一次编写运行成功。

picoCTF{n0t_mUcH_h4rD3r_tH4n_x0r_948b888}

07、vault-door-7,400分

<< 是向左移位操作,逆运算是>>,向右移位。程序里密码验证有密码数组,写一个逆函数

pass_arr = [1096770097, 1952395366, 1600270708, 1601398833, 1716808014, 1734304867, 942695730, 942748212]
pass_str = ''
for pass_int in pass_arr:
    pass_bin = bin(pass_int)
    pass_bin = pass_bin[2:].zfill(32)
    pass_str = pass_str + chr(int(pass_bin[:8], 2))
    pass_str = pass_str + chr(int(pass_bin[8:16], 2))
    pass_str = pass_str + chr(int(pass_bin[16:24], 2))
    pass_str = pass_str + chr(int(pass_bin[24:], 2))
    print(str(pass_int).zfill(10), pass_bin, pass_str)
print('picoCTF{'+pass_str+'}')

picoCTF{A_b1t_0f_b1t_sh1fTiNg_dc80e28124}

08、vault-door-8,450分

还是和以前一样编写逆函数,这次是java写的

class test {
    public static void main(String[] args) {
        char[] expected = {
                0xF4, 0xC0, 0x97, 0xF0, 0x77, 0x97, 0xC0, 0xE4,
                0xF0, 0x77, 0xA4, 0xD0, 0xC5, 0x77, 0xF4, 0x86,
                0xD0, 0xA5, 0x45, 0x96, 0x27, 0xB5, 0x77, 0xE0,
                0x95, 0xF1, 0xE1, 0xE0, 0xA4, 0xC0, 0x94, 0xA4
        };
        String flag = "picoCTF{" + String.valueOf(unscramble(String.valueOf(expected))) + "}";
        System.out.println(flag);
    }

    static public char[] unscramble(String input) {
        char[] a = input.toCharArray();
        for (int b = 0; b < a.length; b++) {
            char c = a[b];
            c = switchBits(c, 6, 7);
            c = switchBits(c, 2, 5);
            c = switchBits(c, 3, 4);
            c = switchBits(c, 0, 1);
            c = switchBits(c, 4, 7);
            c = switchBits(c, 5, 6);
            c = switchBits(c, 0, 3);
            c = switchBits(c, 1, 2);
            a[b] = c;
        }
        return a;
    }

    static public char switchBits(char c, int p1, int p2) {
        char mask1 = (char) (1 << p1);
        char mask2 = (char) (1 << p2);
        char bit1 = (char) (c & mask1);
        char bit2 = (char) (c & mask2);
        char rest = (char) (c & ~(mask1 | mask2));
        char shift = (char) (p2 - p1);
        char result = (char) ((bit1 << shift) | (bit2 >> shift) | rest);
        return result;
    }
}

picoCTF{s0m3_m0r3_b1t_sh1fTiNg_2e762b0ab}

09、asm1,200分

仔细的研究汇编命令,一条一条的看,

已知输入0X2E0 (736)
asm1:
        <+0>:   push   ebp   #进栈
        <+1>:   mov    ebp,esp   #esp寄存器(0x2e0)放入ebp
        <+3>:   cmp    DWORD PTR [ebp+0x8],0x3fb #内存地址ebp+0x8的数据(0x2e0) 与  0x3fb 比较
        <+10>:  jg     0x512 <asm1+37>  #比较结果为大于,跳转
        <+12>:  cmp    DWORD PTR [ebp+0x8],0x280 #比较 0x2e00x280 比较
        <+19>:  jne    0x50a <asm1+29>  #比较结果为不等于,跳转
        <+21>:  mov    eax,DWORD PTR [ebp+0x8] #内存地址ebp+0x8的数据(0x2e0) 放入 eax 
        <+24>:  add    eax,0xa   #eax中的数据+0xa
        <+27>:  jmp    0x529 <asm1+60>  #跳转到asm1+60
        <+29>:  mov    eax,DWORD PTR [ebp+0x8] #内存地址ebp+0x8的数据(0x2e0) 放入 eax
        <+32>:  sub    eax,0xa   #eax中的数据-0xa  =2d6
        <+35>:  jmp    0x529 <asm1+60>  #跳转到asm1+60
        <+37>:  cmp    DWORD PTR [ebp+0x8],0x559 #内存地址ebp+0x8的数据(0x2e0) 与  0x559 比较
        <+44>:  jne    0x523 <asm1+54>  #比较结果为不等于,跳转
        <+46>:  mov    eax,DWORD PTR [ebp+0x8] #内存地址ebp+0x8的数据(0x2e0) 放入 eax
        <+49>:  sub    eax,0xa   #eax中的数据-0xa          
        <+52>:  jmp    0x529 <asm1+60>  #跳转到asm1+60
        <+54>:  mov    eax,DWORD PTR [ebp+0x8] #内存地址ebp+0x8的数据(0x2e0) 放入 eax
        <+57>:  add    eax,0xa   #eax中的数据+0xa
        <+60>:  pop    ebp   #弹出栈   0x2d6
        <+61>:  ret        #结束 

然后一个巨坑,这个flag不要标志符,也就是不用flag{}包围
flag是
0x2d6
这个巨坑,不用flag{}包围。

10、asm2,250分

和asm1一样,一条一条语句的分析

注意flag不需要标志包围

0xa3

11、asm3,300分

题目提示是registers,寄存器问题。
给了asm3函数,传入三个值,获取返回值。
把asm3函数写成汇编源文件

.intel_syntax noprefix
.global asm3

asm3:
	push   ebp
	mov    ebp,esp
	xor    eax,eax
	mov    ah,BYTE PTR [ebp+0xa]
	shl    ax,0x10
	sub    al,BYTE PTR [ebp+0xc]
	add    ah,BYTE PTR [ebp+0xd]
	xor    ax,WORD PTR [ebp+0x10]
	nop
	pop    ebp
	ret    

在编写一个main的c语言函数调用asm3

#include <stdio.h>
int asm3(int, int, int);
int main(int argc, char* argv[])
{
    printf("0x%x\n", asm3(0xd73346ed,0xd48672ae,0xd3c8b139));
    return 0;
}

编译它们
gcc -masm=intel -m32 -c asm3_test.S -o test.o
gcc -m32 -c main.c -o main.o
gcc -m32 test.o main.o -o main

如果编译报 fatal error: bits/libc-header-start.h: 没有那个文件或目录类似错误,
安装gcc-multilib
apt install gcc-multilib
apt install g++-multilib

最后运行
./main
得到flag
在这里插入图片描述
注意:25、asm130、asm232、asm3,它们的flag格式都是特别的,没有不用flag{}包围。
就是这个:0xc36b

12、droids0,300分

安卓调试,用Profile or Debug APK菜单打开APK文件。
运行它,可以配置虚拟机,也可以用真实机。
我找了一台Xiaomi Mi MIX 2S,小米的机器。
安装APK,然后运行它。
在这里插入图片描述
正式机器运行效果是这样的。
在这里插入图片描述
点击 HELLO, I AM BUTTON按钮,在 Android Studio.Run输出就会有一条flag出现。

picoCTF{a.moose.once.bit.my.sister}

13、droids1,350分

安卓继续
打开APK,在资源文件里找
在这里插入图片描述
重要的地方我都用红框标注来了
在这里插入图片描述
picoCTF{pining.for.the.fjords}

14、OTP Implementation,300分

ghidra反编译
在这里插入图片描述
获得enc
python程序:

import subprocess
import re
from pwn import unhex, xor

flag = "a5d47ae6ffa911de9d2b1b7611c47a1c43202a32f0042246f822c82345328becd5b8ec4118660f9b8cdc98bd1a41141943a9"
enc = "lfmhjmnahapkechbanheabbfjladhbplbnfaijdajpnljecghmoafbljlaamhpaheonlmnpmaddhngbgbhobgnofjgeaomadbidl"

key = ["0"] * 100

for i in range(100):
    for j in "0123456789abcdef":
        key[i] = j
        p = subprocess.Popen(["ltrace", "-s", "1000", "./otp", "".join(key)], stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT)

        re_text = p.communicate()[0].decode()
        # print(re_text)
        brute = re.findall(r"strncmp\(\"(.*?)\".*\)",re_text)[0]
        print(brute)
        if brute[i] == enc[i]:
            break
print(xor(unhex("".join(key)), unhex(flag)).decode())

获得flag:

picoCTF{cust0m_jumbl3s_4r3nt_4_g0Od_1d3A_e3647c08}

2020 Mini-Competition

01、reverse_cipher,300分

反编译得到main函数。
在这里插入图片描述

void    main(void)
    {
    size_t    sVar1;
    char    local_58[23];
    char    local_41;
    int    local_2c;
    FILE * local_28;
    FILE * local_20;
    uint    local_14;
    int    local_10;
    char    local_9;

    local_20 = fopen("flag.txt", "r");
    local_28 = fopen("rev_this", "a");
    if (local_20 == (FILE *)0x0) {
    puts("No flag found, please make sure this is run on the server");
    }
    if (local_28 == (FILE * )0x0) {
    puts("please run this on the server");
    }
    sVar1 = fread(local_58, 0x18, 1, local_20);
    local_2c = (int)sVar1;
    if ((int)sVar1 < 1) {
    / * WARNING: Subroutine
    does
    not
    return * /
    exit(0);
}
for (local_10 = 0; local_10 < 8; local_10 = local_10 + 1) {
    local_9 = local_58[local_10];
    fputc((int)
    local_9, local_28);
    }
    for (local_14 = 8; (int)local_14 < 0x17; local_14 = local_14 + 1) {
    if ((local_14 & 1) == 0) {
    local_9 = local_58[(int)local_14] + '\x05';
    }
    else {
    local_9 = local_58[(int)local_14] + -2;
    }
    fputc((int)
    local_9, local_28);
    }
    local_9 = local_41;
    fputc((int)
    local_41, local_28);
    fclose(local_28);
    fclose(local_20);
    return;
    }

分析程序读取flag.txt经过一系列的运算,写入rev_this
写一个Python脚本

import os
import mmap


def memory_map(filename, access=mmap.ACCESS_READ):
    size = os.path.getsize(filename)
    fd = os.open(filename, os.O_RDONLY)
    return mmap.mmap(fd, size, access=access)


with memory_map("rev_this") as bin_file:
    for i in range(8):
        print(chr(bin_file[i]), end='')
    for i in range(8, 23):
        if (i & 1) == 0:
            print(chr(bin_file[i] - 5), end='')
        else:
            print(chr(bin_file[i] + 2), end='')
    print(chr(bin_file[23]))



picoCTF{r3v3rs36ad73964}

2021 picoCTF

01、Transformation,20分

下载一个文件,是乱码,用python的Open打开,

flagStr = open("enc", encoding="utf-8").read()
flag = ""
for i in range(0, len(flagStr)):
    s1 = chr((ord(flagStr[i]) >> 8))
    s2 = chr(flagStr[i].encode('utf-16be')[-1])
    flag += s1
    flag += s2

print(flag)

picoCTF{16_bits_inst34d_of_8_04c0760d}

02、crackme-py,30分

一个python程序,猜数字什么的,但是程序有一段代码没执行,就是下面这一段

bezos_cc_secret = "A:4@r%uL`M-^M0c0AbcM-MFE055a4ce`eN"

# Reference alphabet
alphabet = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ" + \
           "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"


def decode_secret(secret):
    """ROT47 decode

    NOTE: encode and decode are the same operation in the ROT cipher family.
    """

    # Encryption key
    rotate_const = 47

    # Storage for decoded secret
    decoded = ""

    # decode loop
    for c in secret:
        index = alphabet.find(c)
        original_index = (index + rotate_const) % len(alphabet)
        decoded = decoded + alphabet[original_index]

    print(decoded)

手动把这一段代码激活,在猜数字之后,加一行 decode_secret(bezos_cc_secret)
它就打印出flag了。

picoCTF{1|/|_4_p34||ut_dd2c4616}

03、keygenme-py,30分

仔细看了程序,一个星际旅行,一个星体的参数旅行测试什么的。
着重看了check_key部分,发现,flag的长度要一样,并且前面给定了一段,预计flag为picoCTF{1n_7h3_|<3y_of_********}
后面要比较
if key[i] != hashlib.sha256(username_trial).hexdigest()[4]:
看顺序是4、5、3、6、2、7、1、8
我直接加一个打印
print(hashlib.sha256(username_trial).hexdigest()[x]),注意x是位数
然后一个一个测试,得到了flag

picoCTF{1n_7h3_|<3y_of_54ef6292}

04、ARMssembly 0,40分

这是一段汇编,比较两个数,1830628817,1765227561
返回大数,将大数1830628817转换成16进制得到6D1D2DD1

picoCTF{6D1D2DD1}

05、Shop,50分

一个模拟购物小程序。
查了很久才知道,购买数字写负数,可以增加钱数。
在这里插入图片描述
初始只有40元,买了-4个的苹果,增加了60元,就有100元了。
购买那个Quiches奶酪,至少要-6个,每个10元,-6个可以增加60元。
这样,钱就够了,可以买到flag了,是下面这个串:
Flag is: [112 105 99 111 67 84 70 123 98 52 100 95 98 114 111 103 114 97 109 109 101 114 95 98 97 54 98 56 99 100 102 125]
解码就行了。
说实话,我没那么脑筋急转弯,我是查了别人的题解,才知道购买数量写负数的。

picoCTF{b4d_brogrammer_ba6b8cdf}

06、speeds and feeds,50分

这个导出一个文件
用一个cncview的系统打开,得到一行字,cncview数控机床,工业制造方面的绘图系统
在这里插入图片描述
picoCTF{num3r1cal_c0ntr0l_a067637b}

07、ARMssembly 1,70分

先把chall_1.S编译成chall_1.o,然后,写一个代码测试。
编译命令是gcc chall_1.S -o chall_1.o,注意大小写。
还有,一定要注意,这个程序是ARM构架,arrch系统,关于如何获得ARM构架环境,参见我的文章
《在windows10系统中,用qemu软件创建ARM构架虚拟机》
python代码如下:

import os
import sys

count = 0
while count < 3000:
    cmdStr = './chall_1.o ' + str(count)
    r = os.popen(cmdStr).readlines()
    rStr = "".join(r)
    rStr = rStr.strip()
    print(cmdStr, rStr)
    if rStr.find('win') > -1:
        sys.exit()
    count = count + 1

这样就得到了3个文件
在这里插入图片描述
python chall_1.py 来运行。
一开始,猜到1000都没有结果,有点失去信心了,后来朋友猜测,答案可能在1000以上,于是把循环上限调到3000,终于测出结果。
在这里插入图片描述
我的程序测出来是1813。转换成16进制715。我的朋友测出来是27,差别很大,每个账号的flag不一样。

picoCTF{00000715}

08、ARMssembly 2,90分

编译程序:gcc chall_2.S -o chall_2.o
运行程序,传入数字:./chall_2.o 3848786505
在这里插入图片描述
输出结果,得到一个大数字2956424923 转换成16进制得到 B03776DB。

picoCTF{B03776DB}

09、Hurry up! Wait!,100分

用ghidra,分析了0000298a函数,然后分析298a调用的每一个函数。
在这里插入图片描述
每一个函数都调用了 ada__text_io__put__4函数在这里插入图片描述
ada__text_io__put__4函数有两个参数,第一个参数在改变,每次不一样……在这里插入图片描述

在这里插入图片描述
一个一个的找出来,这些字母……

1:00002616:p
2:000024aa:i
3:00002372:c
4:000025e2:o
5:00002852:C
6:00002886:T
7:000028ba:F
8:00002922:{
9:000023a6:d
10:00002136:1
11:00002206:5
12:0000230a:a
13:00002206:5
14:0000257a:m
15:000028ee:_
16:0000240e:f
17:000026e6:t
18:00002782:w
19:000028ee:_
20:000023a6:d
21:0000240e:f
22:0000233e:b
23:000023a6:d
24:00002372:c
25:00002206:5
26:000023a6:d
27:00002956:}

一个一个字母找,太麻烦了。网上大神写了脚本……
用脚本管理,建立一个脚本,然后可以自动调试……
在这里插入图片描述
在这里插入图片描述

import sys
def getAddress(offset):
    return currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(offset)

listing = currentProgram.getListing()
functionManager = currentProgram.getFunctionManager()

main_func = getGlobalFunctions("FUN_0010298a")[0]

# Iterate the instructions that FUN_0010298a() is composed of
for codeUnit in listing.getCodeUnits(main_func.getBody(), True):

	if not codeUnit.toString().startswith("CALL"):
        # Ignore anything that isn't a "call"
		continue

	callee = functionManager.getFunctionAt(getAddress(str(codeUnit.getAddress(0))))
	if not callee.getName().startswith("FUN_"):
        # In practice - skip ada__calendar__delays__delay_for()
		continue

	for cu in listing.getCodeUnits(callee.getBody(), True):
        # Iterate the instructions that the callee is composed of
		if (not cu.toString().startswith("LEA RAX")):
            # Ignore anything that isn't LEA RAX, [addr]
            #  since that's the instruction that loads the flag character to be printed
			continue
		
        # Check what's at "addr" and print it
		sys.stdout.write(chr(getByte(getAddress(str(cu.getScalar(1))))))

print("\n over")

运行效果如下:
在这里插入图片描述
这个解题方法我都是抄来的,自己复现了一遍,注意,ghidra代码不能有中文,不管是注释、还是打印、显示,全部都不能有中文

picoCTF{d15a5m_ftw_dfbdc5d}

10、gogo,110分

太麻烦,比较难……
用 Ghidra 逆向,找到了函数checkPassword

void main.checkPassword(int param_1,uint param_2)

{
  uint *puVar1;
  uint uVar2;
  int iVar3;
  int *in_GS_OFFSET;
  undefined4 local_40;
  undefined4 local_3c;
  undefined4 local_38;
  undefined4 local_34;
  undefined4 local_30;
  undefined4 local_2c;
  undefined4 local_28;
  undefined4 local_24;
  byte local_20 [28];
  undefined4 uStack4;
  
  puVar1 = (uint *)(*(int *)(*in_GS_OFFSET + -4) + 8);
  if (&stack0x00000000 < (undefined *)*puVar1 || &stack0x00000000 == (undefined *)*puVar1) {
    uStack4 = 0x80d4b72;
    runtime.morestack_noctxt();
    main.checkPassword();
    return;
  }
  if ((int)param_2 < 0x20) {
    os.Exit(0);
  }
  FUN_08090b18();
  local_40 = 0x38313638;
  local_3c = 0x31663633;
  local_38 = 0x64336533;
  local_34 = 0x64373236;
  local_30 = 0x37336166;
  local_2c = 0x62646235;
  local_28 = 0x39383338;
  local_24 = 0x65343132;
  FUN_08090fe0();
  uVar2 = 0;
  iVar3 = 0;
  while( true ) {
    if (0x1f < (int)uVar2) {
      if (iVar3 == 0x20) {
        return;
      }
      return;
    }
    if ((param_2 <= uVar2) || (0x1f < uVar2)) break;
    if ((*(byte *)(param_1 + uVar2) ^ *(byte *)((int)&local_40 + uVar2)) == local_20[uVar2]) {
      iVar3 = iVar3 + 1;
    }
    uVar2 = uVar2 + 1;
  }
  runtime.panicindex();
  do {
    invalidInstructionException();
  } while( true );
}

其中这个

  local_40 = 0x38313638;
  local_3c = 0x31663633;
  local_38 = 0x64336533;
  local_34 = 0x64373236;
  local_30 = 0x37336166;
  local_2c = 0x62646235;
  local_28 = 0x39383338;
  local_24 = 0x65343132;

看起来就那么特别,把后面的数值接下来
3831363831663633643365336437323637336166626462353938333865343132
解码
81681f63d3e3d72673afbdb59838e412
可是没什么用

用gdb调试,试了很多种方法,后来,查看逆向地址
在这里插入图片描述
异或操作的地方,0x080d4b21,0x080d4b28
设置断点b * 0x080d4b28
hexdump $esp+4查看内存的值
在这里插入图片描述
得到,861836f13e3d627dfa375bdb8389214e,正好和前面那个解码是倒过来的
紧接着后面的数据就是异或操作的数据,也就是密码
在这里插入图片描述
把两段数据都取出来,做异或,就可以得到密码了,写了下面的程序。

from pwnlib.util.fiddling import unhex, xor
a = unhex('3836313833366631336533643632376466613337356264623833383932313465')
b = unhex('4a53475d414503545d025a0a5357450d05005d555410010e4155574b45504601')
print(a)
print(b)
print(xor(a, b))

得到如下结果

b'861836f13e3d627dfa375bdb8389214e'
b'JSG]AE\x03T]\x02Z\nSWE\r\x05\x00]UT\x10\x01\x0eAUWKEPF\x01'
b'reverseengineericanbarelyforward'

明显结果是可识别的一句有意义的文字,
翻译成中文是逆向工程让我无法前进
程序在输入密码后,还要求输入key,
显然,直接输入861836f13e3d627dfa375bdb8389214e无效。
查看main.ambush函数

void main.ambush(undefined4 param_1,undefined4 param_2)
{
  uint *puVar1;
  char cVar2;
  uint uVar3;
  int *in_GS_OFFSET;
  int local_88;
  uint local_84;
  uint local_80;
  undefined local_70 [16];
  undefined4 local_60;
  undefined4 local_5c;
  undefined4 local_58;
  undefined4 local_54;
  undefined4 local_50;
  undefined4 local_4c;
  undefined4 local_48;
  undefined4 local_44;
  undefined local_40 [32];
  undefined local_20 [12];
  undefined local_14 [16];
  undefined4 uStack4;
  
  puVar1 = (uint *)(*(int *)(*in_GS_OFFSET + -4) + 8);
  if (local_14 < (undefined *)*puVar1 || local_14 == (undefined *)*puVar1) {
    uStack4 = 0x80d4e4b;
    runtime.morestack_noctxt();
    main.ambush();
    return;
  }
  runtime.stringtoslicebyte(local_40,param_1,param_2);
  crypto/md5.Sum(local_88,local_84,local_80);
  FUN_08091008();
  FUN_08090b18();
  local_60 = 0x38313638;
  local_5c = 0x31663633;
  local_58 = 0x64336533;
  local_54 = 0x64373236;
  local_50 = 0x37336166;
  local_4c = 0x62646235;
  local_48 = 0x39383338;
  local_44 = 0x65343132;
  uVar3 = 0;
  while( true ) {
    if (0xf < (int)uVar3) {
      return;
    }
    encoding/hex.EncodeToString(local_70,0x10,0x10);
    if (local_84 <= uVar3) break;
    cVar2 = *(char *)(uVar3 + local_88);
    local_88 = 0x20;
    runtime.slicebytetostring(local_20,&local_60,0x20);
    if (local_80 <= uVar3) break;
    if (cVar2 != *(char *)(local_84 + uVar3)) {
      os.Exit(0);
    }
    uVar3 = uVar3 + 1;
  }
  runtime.panicindex();
  do {
    invalidInstructionException();
  } while( true );
}

也有那一段文字,一样的,861836f13e3d627dfa375bdb8389214e
其中

crypto/md5.Sum(local_88,local_84,local_80);

表示MD5加密。 cyberchef 解密没成功
找到一个在线的MD5解密网站
https://pmd5.com/
类似网站有很多,大家可以试试别的。
在这里插入图片描述
解码为goldfish
至此就基本结束了。连上服务器,输入密码:
reverseengineericanbarelyforward
key:
goldfish
在这里插入图片描述
picoCTF{p1kap1ka_p1c05729981f}

11、ARMssembly 3,130分

把文件chall3_S运行起来就行了,48 转换成16进制00000030

picoCTF{00000030}

12、Let’s get dynamic,150分

编译运行,用gdb调试。
break *(main+385),在main+385的地方设置断点
x/s $rsi查看寄存器rsi内容。
在这里插入图片描述
picoCTF{dyn4m1c_4n4ly1s_1s_5up3r_us3ful_56e35b54}

13、Easy as GDB,160分

用gdb调用python,不要搞到了。gdb -q -x mycode.py

import gdb
import string
from queue import Queue, Empty

MAX_FLAG_LEN = 0x200


class Checkpoint(gdb.Breakpoint):
    def __init__(self, queue, target_hitcount, *args):
        super().__init__(*args)
        self.silent = True
        self.queue = queue
        self.target_hitcount = target_hitcount
        self.hit = 0

    def stop(self):
        res = []
        self.hit += 1
        # print(f"\nhit {self.hit}/{self.target_hitcount}")
        if self.hit == self.target_hitcount:
            al = gdb.parse_and_eval("$al")
            dl = gdb.parse_and_eval("$dl")
            self.queue.put(al == dl)
        return False


class Solvepoint(gdb.Breakpoint):
    def __init__(self, *args):
        super().__init__(*args)
        self.silent = True
        self.hit = 0

    def stop(self):
        # gdb.execute("q")
        self.hit += 1
        return False


gdb.execute("set disable-randomization on")
gdb.execute("delete")
gdb.execute("file brute")

sp = Solvepoint("*0x56555a71")
queue = Queue()

flag = ""
ALPHABET = string.ascii_letters + string.digits + "{}_"

for i in range(len(flag), MAX_FLAG_LEN):
    for c in ALPHABET:
        bp = Checkpoint(queue, len(flag) + 1, '*0x5655598e')
        gdb.execute("run <<< " + flag + c)
        try:
            result = queue.get(timeout=1)
            bp.delete()
            if result:
                flag += c
                # print("\n\n{}\n\n".format(flag))
                print('\033[1;31m' + "\n\n{}\n\n".format(flag) + '\033[0m')
                break
        except Empty:
            print("Error: Empty queue!")
            gdb.execute("q")

    if sp.hit > 0:
        print("Found flag: {}".format(flag))
        gdb.execute("q")

picoCTF{I_5D3_A11DA7_e5458cbf}

14、ARMssembly 4,170分

启动ARM虚拟机
gcc chall_4.S -o chall_4.o
编译生成程序。
传入题目给定的数字,就会返回数字
在这里插入图片描述
转换16进制得到ec4e2911

picoCTF{ec4e2911}

15、Powershelly,180分

下载得到两个文件,是一个windows的shell脚本程序,程序是读一个input.txt,生成一个output.txt,题目给出了输出文件output.txt。

脚本阅读有点麻烦,其中:
-eq :equal(相等)
-ne :not equal(不等)
-gt :greater than(大于)
-ge :greater than or equal(大于或等于)
-lt :less than(小于)
-le :less than or equal(小于或等于)

$input = ".\input.txt"

$out = Get-Content -Path $input
$enc = [System.IO.File]::ReadAllBytes("$input")
$encoding = [system.Text.Encoding]::UTF8
$total = 264
$t = ($total + 1) * 5
$numLength = ($total * 30 ) + $t
if ($out.Length -gt 5 -or $enc.count -ne $numLength)
{
  Write-Output "Wrong format 5"
  Exit
}

else
{
  for($i=0; $i -lt $enc.count ; $i++)
  {
    if (($enc[$i] -ne 49) -and ($enc[$i] -ne 48) -and ($enc[$i] -ne 10) -and ($enc[$i] -ne 13) -and ($enc[$i] -ne 32))
    {
      Write-Output "Wrong format 1/0/"
      Exit
    }
  }
}

从这一段可以看出,输入文件必须是5行。
用enc获得以字节读取文件数组,并且enc的长度做了精确限定。
初步判定:
enc是5行、每组6个字符、每行264组,并且是二进制0和1。

接着,程序把文件字符放入blocks变量,进行一系列的变换,写入output.txt

我们要做的就是逆向,从output.txt得到input.txt

找了一个逆向程序:

import sys

print("Randoms: ")
genNumbers = []
for i in range(1, 264 + 1, 1):
    y = (((i * 327) % 681) + 344) % 313
    genNumbers.append(y)

print(genNumbers)

print("Seeds: ")
seeds = []
for i in range(1, 264 + 1, 1):
    seeds.append((i * 127) % 500)
# print(seeds)
contents = []
with open("output.txt") as f:
    contents = f.read().split("\n")

result = 0
finalArray = []
for blockCount in range(0, len(contents)):
    output = int(contents[blockCount]) ^ result ^ genNumbers[blockCount]
    output = bin(output)[2:].zfill(60)
    # print(y)

    # print(output)
    finalOutput = ""
    finalSplit = []
    for x in range(0, len(output), 2):
        finalSplit.append(output[x:x + 2])

    # finalSplit.length = $raw.length
    usedCounter = 0
    for x in range(0, len(finalSplit), 1):
        y = (x * seeds[blockCount]) % len(finalSplit)
        # print(y[x])
        # print("Length " + str(len(finalSplit)))
        current = finalSplit[y]
        if current == "X":
            while current == "X":
                y = (y + 1) % len(finalSplit)
                current = finalSplit[y]

        if current == "11":
            finalOutput += "1"
            finalSplit[y] = "X"
            usedCounter += 1
        else:
            finalOutput += "0"
            finalSplit[y] = "X"
            usedCounter += 1
    print(finalSplit)
    print(usedCounter)

    result = int(contents[blockCount])

    finalArray.append(finalOutput)

columnOutput = []
for x in range(0, len(finalArray), 1):
    current = finalArray[x]
    counter = 0
    # print(current)
    for y in range(0, len(current), 6):  # go down each row
        currentSplit = current[y:y + 6].zfill(6)

        if x == 0:
            columnOutput.append([currentSplit])
        else:
            columnOutput[counter].append(currentSplit)
            counter += 1
        # print(columnOutput)

with open("input.txt", "w") as f:
    for x in columnOutput:
        print(len(x))
        joined = ' '.join(x)
        f.write(joined + "\r\n")
with open("input.txt", "rb") as f:
    print(len(f.read()))

# print(columnOutput[0])

生成了input.txt在这里插入图片描述将0和1按点阵排列,写一个代码:

with open("input.txt") as fs:
    contents = fs.read().split('\n')
for content in contents:
    content = content.strip()
    if content != '':
        # print(content)
        v = ''
        for point in content:
            if point == '0':
                v = v + '█'
            else:
                v = v + ' '
        print(v)

得到输出:
在这里插入图片描述
这个输出,正好拼接成一个二进制数列……
把它们抄下来,按照8位,分成一段,是这样的:
01110000 01101001 01100011 01101111 01000011 01010100 01000110 01111011 00110010 00110000 00110001 00111000 01101000 01101001 01100111 01101000 01110111 01000000 01111001 01011111 00110010 01011111 01110000 01101111 01110111 00110011 01110010 00100100 01101000 01100101 01101100 00100001 01111101

这个数列其实不用显示5行,从第一行就能判断,连续4个黑色块是0,连续2个黑色块是1,换算就是,100001为0,110011为1,也可以写代码,判断第一行就可以了……,有时间在搞……

把这个长数列放到010editor中,得到flag。
在这里插入图片描述
这题很精彩,绕了几道弯,我也是查了大量资料,才做出来的。也有借鉴网上大神的代码。
特别是用点阵拼出二进制,更是想不到。

picoCTF{2018highw@y_2_pow3r$hel!}

2021 redpwn

01、not crypto,150分

Ghidra 反编译,获得一个很长的函数。其中:memcmp函数比较输入数据的
在这里插入图片描述
gdb调试,b memcmp断点,在554000段。
Ghidra查看内存找到函数,在5553b9
在这里插入图片描述
gdb调试,在b *0x5555555553b9断点
在这里插入图片描述
运行程序后,在0x5555555553b9断点,看到flag,在rdi寄存器。
x/s $rdi查看寄存器。

picoCTF{c0mp1l3r_0pt1m1z4t10n_15_pur3_w1z4rdry_but_n0_pr0bl3m?}

02、breadth,200分

下载得到两个文件,V1和V2版本。
ida逆向一个可以看到有很多flag,
在这里插入图片描述
估计V1和V2的不同的地方就是flag修改的地方。
用010editor比较两个文件有3个不同的地方,2D4h9504Bh253753h
在这里插入图片描述
反编译查看地址
2D4在文件开头没什么意义
9504B是数据部分。
在这里插入图片描述
双击字符串,得到完整的flag
在这里插入图片描述
picoCTF{VnDB2LUf1VFJkdfDJtdYtFlMexPxXS6X}

2022 picoCTF

01、file-run1,100分

下载文件运行,就行了

picoCTF{U51N6_Y0Ur_F1r57_F113_47cf2b7b}

02、file-run2,100分

下载文件用二进制打开,搜索到了

picoCTF{F1r57_4rgum3n7_f65ed63e}

03、GDB Test Drive,100分

$ chmod +x gdbme
$ gdb gdbme
(gdb) layout asm
(gdb) break *(main+99)
(gdb) run
(gdb) jump *(main+104)

gdb 设置两个断点,然后……

picoCTF{d3bugg3r_dr1v3_7776d758}

04、patchme.py,100分

验证密码在程序里,写入密码,程序返回一个flag
picoCTF{p47ch1ng_l1f3_h4ck_21d62e33}

05、Safe Opener,100分

代码里有密码比较语句

String encodedkey = "cGwzYXMzX2wzdF9tM18xbnQwX3RoM19zYWYz";
        if (password.equals(encodedkey)) {
            System.out.println("Sesame open");
            return true;
        }

用CyberChef解码就行了。

picoCTF{pl3as3_l3t_m3_1nt0_th3_saf3}

06、unpackme.py,100分

密码写在程序里,一下就看到了,在代码里加一行,print(plain),打印出plain,里面有一行验证flag就是了

picoCTF{175_chr157m45_85f5d0ac}

07、bloat.py,200分

arg444 = arg132()
# arg432 = arg232()
# arg133(arg432)
# arg112()
arg423 = arg111(arg444)
print(arg423)

picoCTF{d30bfu5c4710n_f7w_5e14b257}

08、Fresh Java,200分

用反编译工具jd-gui反编译,有一个密码验证函数,是按位验证的

if (str.length() != 34) {
      System.out.println("Invalid key");
      return;
    } 
    if (str.charAt(33) != '}') {
      System.out.println("Invalid key");
      return;
    } 
    if (str.charAt(32) != 'd') {
      System.out.println("Invalid key");
      return;
……
    if (str.charAt(3) != 'o') {
      System.out.println("Invalid key");
      return;
    } 
    if (str.charAt(2) != 'c') {
      System.out.println("Invalid key");
      return;
    } 
    if (str.charAt(1) != 'i') {
      System.out.println("Invalid key");
      return;
    } 
    if (str.charAt(0) != 'p') {
      System.out.println("Invalid key");
      return;
    } 
    System.out.println("Valid key");
  }

取出重要的句子,举例如下:

if (str.charAt(0) != 'p') {
if (str.charAt(1) != 'i') {
if (str.charAt(10) != '0') {
if (str.charAt(11) != 'l') {
if (str.charAt(12) != '1') {
if (str.charAt(13) != 'n') {
if (str.charAt(14) != 'g') {
if (str.charAt(15) != '_') {
if (str.charAt(16) != 'r') {
if (str.charAt(17) != '3') {
if (str.charAt(18) != 'q') {
if (str.charAt(19) != 'u') {
if (str.charAt(2) != 'c') {
if (str.charAt(20) != '1') {
if (str.charAt(21) != 'r') {
if (str.charAt(22) != '3') {
if (str.charAt(23) != 'd') {
if (str.charAt(24) != '_') {
if (str.charAt(25) != '2') {
if (str.charAt(26) != 'b') {
if (str.charAt(27) != 'f') {
if (str.charAt(28) != 'e') {
if (str.charAt(29) != '1') {
if (str.charAt(3) != 'o') {
if (str.charAt(30) != 'a') {
if (str.charAt(31) != '0') {
if (str.charAt(32) != 'd') {
if (str.charAt(33) != '}') {
if (str.charAt(4) != 'C') {
if (str.charAt(5) != 'T') {
if (str.charAt(6) != 'F') {
if (str.charAt(7) != '{') {
if (str.charAt(8) != '7') {
if (str.charAt(9) != '0') {

排好顺序,取出字符就OK了,注意,上面的排序没有严格按照数字大小排序,大家自己调整。

picoCTF{700l1ng_r3qu1r3d_2bfe1a0d}

09、Bbbbloat,300分

反编译:
在这里插入图片描述
16进制86187 就是十进制 549255 。就是这个数字
在这里插入图片描述

picoCTF{cu7_7h3_bl047_44f74a60}

10、unpackme,300分

UPX 可以有效地对可执行文件进行压缩,并且压缩后的文件可以直接由系统执行,支持多系统和平台。
使用 UPX 来压缩可执行文件是一种减少发布包大小的有效方式。数字是754635,测了一下upx压缩或者不压缩,程序都能运行,输入754635,都能得出flag

picoCTF{up><_m3_f7w_5769b54e}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值