picoCTF,Reverse Engineering,42/55
- 2019 picoCTF
- 01、vault-door-training,50分
- 02、vault-door-1,100分
- 03、vault-door-3,200分
- 04、vault-door-4,250分
- 05、vault-door-5,300分
- 06、vault-door-6,350分
- 07、vault-door-7,400分
- 08、vault-door-8,450分
- 09、asm1,200分
- 10、asm2,250分
- 11、asm3,300分
- 12、droids0,300分
- 13、droids1,350分
- 14、OTP Implementation,300分
- 2020 Mini-Competition
- 01、reverse_cipher,300分
- 2021 picoCTF
- 01、Transformation,20分
- 02、crackme-py,30分
- 03、keygenme-py,30分
- 04、ARMssembly 0,40分
- 05、Shop,50分
- 06、speeds and feeds,50分
- 07、ARMssembly 1,70分
- 08、ARMssembly 2,90分
- 09、Hurry up! Wait!,100分
- 10、gogo,110分
- 11、ARMssembly 3,130分
- 12、Let's get dynamic,150分
- 13、Easy as GDB,160分
- 14、ARMssembly 4,170分
- 15、Powershelly,180分
- 2021 redpwn
- 01、not crypto,150分
- 02、breadth,200分
- 2022 picoCTF
- 01、file-run1,100分
- 02、file-run2,100分
- 03、GDB Test Drive,100分
- 04、patchme.py,100分
- 05、Safe Opener,100分
- 06、unpackme.py,100分
- 07、bloat.py,200分
- 08、Fresh Java,200分
- 09、Bbbbloat,300分
- 10、unpackme,300分
题目站点链接 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] ^ 0x55print(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 #比较 0x2e0 与 0x280 比较<+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 asm3asm3:push ebpmov ebp,espxor eax,eaxmov ah,BYTE PTR [ebp+0xa]shl ax,0x10sub al,BYTE PTR [ebp+0xc]add ah,BYTE PTR [ebp+0xd]xor ax,WORD PTR [ebp+0x10]noppop ebpret
在编写一个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、asm1
、30、asm2
、32、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, xorflag = "a5d47ae6ffa911de9d2b1b7611c47a1c43202a32f0042246f822c82345328becd5b8ec4118660f9b8cdc98bd1a41141943a9"
enc = "lfmhjmnahapkechbanheabbfjladhbplbnfaijdajpnljecghmoafbljlaamhpaheonlmnpmaddhngbgbhobgnofjgeaomadbidl"key = ["0"] * 100for i in range(100):for j in "0123456789abcdef":key[i] = jp = 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: Subroutinedoesnotreturn * /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 mmapdef 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 += s1flag += s2print(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 decodeNOTE: encode and decode are the same operation in the ROT cipher family."""# Encryption keyrotate_const = 47# Storage for decoded secretdecoded = ""# decode loopfor 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 syscount = 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"continuecallee = functionManager.getFunctionAt(getAddress(str(codeUnit.getAddress(0))))if not callee.getName().startswith("FUN_"):# In practice - skip ada__calendar__delays__delay_for()continuefor cu in listing.getCodeUnits(callee.getBody(), True):# Iterate the instructions that the callee is composed ofif (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 printedcontinue# Check what's at "addr" and print itsys.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, EmptyMAX_FLAG_LEN = 0x200class Checkpoint(gdb.Breakpoint):def __init__(self, queue, target_hitcount, *args):super().__init__(*args)self.silent = Trueself.queue = queueself.target_hitcount = target_hitcountself.hit = 0def 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 Falseclass Solvepoint(gdb.Breakpoint):def __init__(self, *args):super().__init__(*args)self.silent = Trueself.hit = 0def stop(self):# gdb.execute("q")self.hit += 1return Falsegdb.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')breakexcept 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 sysprint("Randoms: ")
genNumbers = []
for i in range(1, 264 + 1, 1):y = (((i * 327) % 681) + 344) % 313genNumbers.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.lengthusedCounter = 0for 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 += 1else:finalOutput += "0"finalSplit[y] = "X"usedCounter += 1print(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 rowcurrentSplit = 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个不同的地方,2D4h
、9504Bh
、253753h
反编译查看地址
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}